web3-agent-kit 1.0.0__tar.gz → 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {web3_agent_kit-1.0.0/web3_agent_kit.egg-info → web3_agent_kit-1.2.0}/PKG-INFO +1 -1
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/pyproject.toml +1 -1
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/__init__.py +24 -1
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/__init__.py +39 -0
- web3_agent_kit-1.2.0/src/airdrop/dashboard.py +533 -0
- web3_agent_kit-1.2.0/src/airdrop/discovery.py +696 -0
- web3_agent_kit-1.2.0/src/airdrop/faucet.py +586 -0
- web3_agent_kit-1.2.0/src/airdrop/onchain.py +724 -0
- web3_agent_kit-1.2.0/src/airdrop/referral.py +523 -0
- web3_agent_kit-1.2.0/src/airdrop/scheduler.py +702 -0
- web3_agent_kit-1.2.0/src/security/__init__.py +883 -0
- web3_agent_kit-1.2.0/tests/test_airdrop_new_modules.py +648 -0
- web3_agent_kit-1.2.0/tests/test_security.py +434 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0/web3_agent_kit.egg-info}/PKG-INFO +1 -1
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/web3_agent_kit.egg-info/SOURCES.txt +9 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/LICENSE +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/README.md +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/setup.cfg +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/setup.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/agent.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/base.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/executor/__init__.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/executor/base_executor.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/executor/browser.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/executor/captcha_solver.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/executor/cli.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/executor/galxe_exec.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/executor/gleam_exec.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/executor/intract_exec.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/executor/layer3_exec.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/executor/plugin_registry.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/executor/port3_exec.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/executor/questn.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/executor/social_exec.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/executor/taskon.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/executor/zealy_exec.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/galxe.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/gleam.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/multi_wallet.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/social.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/tracker.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/airdrop/zealy.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/api/__init__.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/api/models.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/api/routes/__init__.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/api/routes/approval.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/api/routes/bridge.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/api/routes/dca.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/api/routes/gas.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/api/routes/portfolio.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/api/routes/swap.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/api/routes/wallet.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/api/routes/watcher.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/api/routes/yield_opt.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/approval_manager.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/bridge.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/chain.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/dca_bot.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/defi/__init__.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/gas_optimizer.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/llm.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/multi_wallet.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/plugins/__init__.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/plugins/examples/gas_tracker.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/portfolio.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/sniper.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/utils/__init__.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/wallet.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/wallet_watcher.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/src/yield_optimizer.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/tests/test_airdrop.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/tests/test_api.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/tests/test_bridge.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/tests/test_core.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/tests/test_dca_bot.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/tests/test_defi.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/tests/test_executor.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/tests/test_llm.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/tests/test_multi_wallet.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/tests/test_new_features.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/tests/test_platforms.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/tests/test_plugins.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/tests/test_portfolio.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/tests/test_sniper.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/tests/test_yield_optimizer.py +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/web3_agent_kit.egg-info/dependency_links.txt +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/web3_agent_kit.egg-info/requires.txt +0 -0
- {web3_agent_kit-1.0.0 → web3_agent_kit-1.2.0}/web3_agent_kit.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "web3-agent-kit"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.2.0"
|
|
8
8
|
description = "Open-source framework for building autonomous AI agents that interact with blockchain networks"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Web3 Agent Kit — Open-source framework for autonomous Web3 AI agents."""
|
|
2
2
|
|
|
3
|
-
__version__ = "
|
|
3
|
+
__version__ = "1.2.0"
|
|
4
4
|
__author__ = "Maulana"
|
|
5
5
|
|
|
6
6
|
from .agent import Agent, AgentConfig
|
|
@@ -28,6 +28,18 @@ from .dca_bot import DCABot, DCAOrder, DCAResult, Interval, DCAStatus
|
|
|
28
28
|
from .gas_optimizer import GasOptimizer, GasEstimate, GasRecommendation, GasPriority
|
|
29
29
|
from .wallet_watcher import WalletWatcher, WatchedWallet, WalletAlert, AlertType, AlertSeverity
|
|
30
30
|
from .approval_manager import ApprovalManager, TokenApproval, RevokeResult, ApprovalRisk
|
|
31
|
+
from .security import (
|
|
32
|
+
TokenAnalyzer,
|
|
33
|
+
SecurityConfig,
|
|
34
|
+
SecurityReport,
|
|
35
|
+
TokenInfo,
|
|
36
|
+
TaxInfo,
|
|
37
|
+
LiquidityInfo,
|
|
38
|
+
HolderInfo,
|
|
39
|
+
ContractAudit,
|
|
40
|
+
RiskLevel as SecurityRiskLevel,
|
|
41
|
+
ContractPattern,
|
|
42
|
+
)
|
|
31
43
|
|
|
32
44
|
__all__ = [
|
|
33
45
|
# Core
|
|
@@ -86,4 +98,15 @@ __all__ = [
|
|
|
86
98
|
"TokenApproval",
|
|
87
99
|
"RevokeResult",
|
|
88
100
|
"ApprovalRisk",
|
|
101
|
+
# Security
|
|
102
|
+
"TokenAnalyzer",
|
|
103
|
+
"SecurityConfig",
|
|
104
|
+
"SecurityReport",
|
|
105
|
+
"TokenInfo",
|
|
106
|
+
"TaxInfo",
|
|
107
|
+
"LiquidityInfo",
|
|
108
|
+
"HolderInfo",
|
|
109
|
+
"ContractAudit",
|
|
110
|
+
"SecurityRiskLevel",
|
|
111
|
+
"ContractPattern",
|
|
89
112
|
]
|
|
@@ -25,6 +25,14 @@ from .social import (
|
|
|
25
25
|
from .tracker import AirdropTracker, AirdropReward, AirdropSummary
|
|
26
26
|
from .multi_wallet import AirdropFarmer, SybilAvoidanceConfig, FarmResult, WalletFarmProgress
|
|
27
27
|
|
|
28
|
+
# New modules
|
|
29
|
+
from .discovery import CampaignDiscovery, DiscoveryConfig, DiscoveredCampaign
|
|
30
|
+
from .onchain import OnChainAirdropFarmer, OnChainConfig, TransactionResult, Chain, DeFiProtocol, FARMING_PLANS
|
|
31
|
+
from .scheduler import AirdropScheduler, SchedulerConfig, ScheduledTask, ScheduleFrequency
|
|
32
|
+
from .dashboard import PointsDashboard, DashboardConfig, PlatformPoints, PointsSnapshot
|
|
33
|
+
from .referral import ReferralManager, ReferralLink, ReferralPlatform, ReferralStats
|
|
34
|
+
from .faucet import FaucetClaimer, FaucetConfig, ClaimResult, FAUCETS
|
|
35
|
+
|
|
28
36
|
__all__ = [
|
|
29
37
|
# Base
|
|
30
38
|
"BaseAirdropPlatform",
|
|
@@ -62,4 +70,35 @@ __all__ = [
|
|
|
62
70
|
"SybilAvoidanceConfig",
|
|
63
71
|
"FarmResult",
|
|
64
72
|
"WalletFarmProgress",
|
|
73
|
+
# Discovery
|
|
74
|
+
"CampaignDiscovery",
|
|
75
|
+
"DiscoveryConfig",
|
|
76
|
+
"DiscoveredCampaign",
|
|
77
|
+
# On-chain
|
|
78
|
+
"OnChainAirdropFarmer",
|
|
79
|
+
"OnChainConfig",
|
|
80
|
+
"TransactionResult",
|
|
81
|
+
"Chain",
|
|
82
|
+
"DeFiProtocol",
|
|
83
|
+
"FARMING_PLANS",
|
|
84
|
+
# Scheduler
|
|
85
|
+
"AirdropScheduler",
|
|
86
|
+
"SchedulerConfig",
|
|
87
|
+
"ScheduledTask",
|
|
88
|
+
"ScheduleFrequency",
|
|
89
|
+
# Dashboard
|
|
90
|
+
"PointsDashboard",
|
|
91
|
+
"DashboardConfig",
|
|
92
|
+
"PlatformPoints",
|
|
93
|
+
"PointsSnapshot",
|
|
94
|
+
# Referral
|
|
95
|
+
"ReferralManager",
|
|
96
|
+
"ReferralLink",
|
|
97
|
+
"ReferralPlatform",
|
|
98
|
+
"ReferralStats",
|
|
99
|
+
# Faucet
|
|
100
|
+
"FaucetClaimer",
|
|
101
|
+
"FaucetConfig",
|
|
102
|
+
"ClaimResult",
|
|
103
|
+
"FAUCETS",
|
|
65
104
|
]
|
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
"""Points Dashboard — track airdrop points across all platforms.
|
|
2
|
+
|
|
3
|
+
Aggregates points from Galxe, Zealy, Layer3, and other platforms
|
|
4
|
+
into a unified dashboard with historical tracking.
|
|
5
|
+
|
|
6
|
+
Usage::
|
|
7
|
+
|
|
8
|
+
from web3_agent_kit.airdrop.dashboard import PointsDashboard
|
|
9
|
+
|
|
10
|
+
dashboard = PointsDashboard()
|
|
11
|
+
dashboard.sync_all(wallet="0x...")
|
|
12
|
+
dashboard.print_summary()
|
|
13
|
+
dashboard.export_json("points_report.json")
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import logging
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from datetime import datetime, timezone
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any, Optional
|
|
24
|
+
|
|
25
|
+
import requests
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class PlatformPoints:
|
|
32
|
+
"""Points balance on a single platform."""
|
|
33
|
+
platform: str
|
|
34
|
+
points: int = 0
|
|
35
|
+
rank: int = 0
|
|
36
|
+
level: int = 0
|
|
37
|
+
tier: str = ""
|
|
38
|
+
campaigns_completed: int = 0
|
|
39
|
+
campaigns_active: int = 0
|
|
40
|
+
last_activity: Optional[datetime] = None
|
|
41
|
+
streak_days: int = 0
|
|
42
|
+
referrals: int = 0
|
|
43
|
+
referral_points: int = 0
|
|
44
|
+
badges: list[str] = field(default_factory=list)
|
|
45
|
+
metadata: dict = field(default_factory=dict)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def total_with_referrals(self) -> int:
|
|
49
|
+
"""Total points including referrals."""
|
|
50
|
+
return self.points + self.referral_points
|
|
51
|
+
|
|
52
|
+
def to_dict(self) -> dict:
|
|
53
|
+
return {
|
|
54
|
+
"platform": self.platform,
|
|
55
|
+
"points": self.points,
|
|
56
|
+
"rank": self.rank,
|
|
57
|
+
"level": self.level,
|
|
58
|
+
"tier": self.tier,
|
|
59
|
+
"campaigns_completed": self.campaigns_completed,
|
|
60
|
+
"campaigns_active": self.campaigns_active,
|
|
61
|
+
"streak_days": self.streak_days,
|
|
62
|
+
"referrals": self.referrals,
|
|
63
|
+
"referral_points": self.referral_points,
|
|
64
|
+
"badges": self.badges,
|
|
65
|
+
"last_activity": self.last_activity.isoformat() if self.last_activity else None,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class PointsSnapshot:
|
|
71
|
+
"""A point-in-time snapshot of all platform points."""
|
|
72
|
+
timestamp: datetime
|
|
73
|
+
platforms: dict[str, PlatformPoints] = field(default_factory=dict)
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def total_points(self) -> int:
|
|
77
|
+
"""Total points across all platforms."""
|
|
78
|
+
return sum(p.points for p in self.platforms.values())
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def total_campaigns(self) -> int:
|
|
82
|
+
"""Total campaigns completed across all platforms."""
|
|
83
|
+
return sum(p.campaigns_completed for p in self.platforms.values())
|
|
84
|
+
|
|
85
|
+
def to_dict(self) -> dict:
|
|
86
|
+
return {
|
|
87
|
+
"timestamp": self.timestamp.isoformat(),
|
|
88
|
+
"total_points": self.total_points,
|
|
89
|
+
"total_campaigns": self.total_campaigns,
|
|
90
|
+
"platforms": {k: v.to_dict() for k, v in self.platforms.items()},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class DashboardConfig:
|
|
96
|
+
"""Configuration for the points dashboard."""
|
|
97
|
+
# Wallet address
|
|
98
|
+
wallet_address: str = ""
|
|
99
|
+
# Platforms to track
|
|
100
|
+
platforms: list[str] = field(default_factory=lambda: [
|
|
101
|
+
"galxe", "zealy", "layer3", "questn", "taskon", "intract", "port3"
|
|
102
|
+
])
|
|
103
|
+
# API keys (optional, for authenticated endpoints)
|
|
104
|
+
galxe_api_key: str = ""
|
|
105
|
+
# History file
|
|
106
|
+
history_path: Optional[str] = None
|
|
107
|
+
# Proxy
|
|
108
|
+
proxy: Optional[str] = None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class PointsDashboard:
|
|
112
|
+
"""Track airdrop points across all platforms.
|
|
113
|
+
|
|
114
|
+
Aggregates points from multiple platforms, tracks history,
|
|
115
|
+
and provides reporting/export capabilities.
|
|
116
|
+
|
|
117
|
+
Example::
|
|
118
|
+
|
|
119
|
+
dashboard = PointsDashboard(config)
|
|
120
|
+
dashboard.sync_all(wallet="0x721e885...A522")
|
|
121
|
+
dashboard.print_summary()
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(self, config: Optional[DashboardConfig] = None):
|
|
125
|
+
"""Initialize the dashboard.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
config: Dashboard configuration.
|
|
129
|
+
"""
|
|
130
|
+
self.config = config or DashboardConfig()
|
|
131
|
+
self.session = requests.Session()
|
|
132
|
+
self.session.headers.update({
|
|
133
|
+
"User-Agent": (
|
|
134
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
|
135
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
|
136
|
+
"Chrome/120.0.0.0 Safari/537.36"
|
|
137
|
+
),
|
|
138
|
+
})
|
|
139
|
+
if self.config.proxy:
|
|
140
|
+
self.session.proxies = {
|
|
141
|
+
"http": self.config.proxy,
|
|
142
|
+
"https": self.config.proxy,
|
|
143
|
+
}
|
|
144
|
+
self._current: Optional[PointsSnapshot] = None
|
|
145
|
+
self._history: list[PointsSnapshot] = []
|
|
146
|
+
self._load_history()
|
|
147
|
+
logger.info("PointsDashboard initialized")
|
|
148
|
+
|
|
149
|
+
def sync_all(self, wallet: Optional[str] = None) -> PointsSnapshot:
|
|
150
|
+
"""Sync points from all configured platforms.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
wallet: Wallet address (overrides config).
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Current points snapshot.
|
|
157
|
+
"""
|
|
158
|
+
address = wallet or self.config.wallet_address
|
|
159
|
+
if not address:
|
|
160
|
+
raise ValueError("Wallet address required")
|
|
161
|
+
|
|
162
|
+
platforms: dict[str, PlatformPoints] = {}
|
|
163
|
+
|
|
164
|
+
syncers = {
|
|
165
|
+
"galxe": self._sync_galxe,
|
|
166
|
+
"zealy": self._sync_zealy,
|
|
167
|
+
"layer3": self._sync_layer3,
|
|
168
|
+
"questn": self._sync_questn,
|
|
169
|
+
"taskon": self._sync_taskon,
|
|
170
|
+
"intract": self._sync_intract,
|
|
171
|
+
"port3": self._sync_port3,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for platform in self.config.platforms:
|
|
175
|
+
syncer = syncers.get(platform)
|
|
176
|
+
if not syncer:
|
|
177
|
+
continue
|
|
178
|
+
try:
|
|
179
|
+
points = syncer(address)
|
|
180
|
+
if points:
|
|
181
|
+
platforms[platform] = points
|
|
182
|
+
logger.info(f"{platform}: {points.points} points")
|
|
183
|
+
except Exception as e:
|
|
184
|
+
logger.error(f"Failed to sync {platform}: {e}")
|
|
185
|
+
|
|
186
|
+
self._current = PointsSnapshot(
|
|
187
|
+
timestamp=datetime.now(timezone.utc),
|
|
188
|
+
platforms=platforms,
|
|
189
|
+
)
|
|
190
|
+
self._history.append(self._current)
|
|
191
|
+
self._save_history()
|
|
192
|
+
|
|
193
|
+
logger.info(f"Sync complete: {self._current.total_points} total points")
|
|
194
|
+
return self._current
|
|
195
|
+
|
|
196
|
+
def get_current(self) -> Optional[PointsSnapshot]:
|
|
197
|
+
"""Get the current points snapshot.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Current snapshot, or None if not synced.
|
|
201
|
+
"""
|
|
202
|
+
return self._current
|
|
203
|
+
|
|
204
|
+
def get_history(self, limit: int = 30) -> list[PointsSnapshot]:
|
|
205
|
+
"""Get historical snapshots.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
limit: Max snapshots to return.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
List of historical snapshots.
|
|
212
|
+
"""
|
|
213
|
+
return self._history[-limit:]
|
|
214
|
+
|
|
215
|
+
def get_growth(self, days: int = 7) -> dict:
|
|
216
|
+
"""Calculate points growth over time.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
days: Number of days to look back.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Growth dict with per-platform and total deltas.
|
|
223
|
+
"""
|
|
224
|
+
if len(self._history) < 2:
|
|
225
|
+
return {"total_delta": 0, "platforms": {}}
|
|
226
|
+
|
|
227
|
+
now = self._history[-1]
|
|
228
|
+
cutoff = datetime.now(timezone.utc).timestamp() - (days * 86400)
|
|
229
|
+
|
|
230
|
+
# Find snapshot closest to cutoff
|
|
231
|
+
old = self._history[0]
|
|
232
|
+
for snap in self._history:
|
|
233
|
+
if snap.timestamp.timestamp() >= cutoff:
|
|
234
|
+
old = snap
|
|
235
|
+
break
|
|
236
|
+
|
|
237
|
+
growth: dict[str, Any] = {
|
|
238
|
+
"days": days,
|
|
239
|
+
"total_delta": now.total_points - old.total_points,
|
|
240
|
+
"total_delta_pct": 0.0,
|
|
241
|
+
"platforms": {},
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if old.total_points > 0:
|
|
245
|
+
growth["total_delta_pct"] = round(
|
|
246
|
+
(growth["total_delta"] / old.total_points) * 100, 1
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
for platform in set(
|
|
250
|
+
list(now.platforms.keys()) + list(old.platforms.keys())
|
|
251
|
+
):
|
|
252
|
+
new_pts = now.platforms.get(platform, PlatformPoints(platform)).points
|
|
253
|
+
old_pts = old.platforms.get(platform, PlatformPoints(platform)).points
|
|
254
|
+
delta = new_pts - old_pts
|
|
255
|
+
growth["platforms"][platform] = {
|
|
256
|
+
"current": new_pts,
|
|
257
|
+
"previous": old_pts,
|
|
258
|
+
"delta": delta,
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return growth
|
|
262
|
+
|
|
263
|
+
def get_leaderboard_position(self) -> list[dict]:
|
|
264
|
+
"""Get estimated leaderboard positions.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
List of platform rankings.
|
|
268
|
+
"""
|
|
269
|
+
if not self._current:
|
|
270
|
+
return []
|
|
271
|
+
|
|
272
|
+
positions = []
|
|
273
|
+
for platform, points in self._current.platforms.items():
|
|
274
|
+
positions.append({
|
|
275
|
+
"platform": platform,
|
|
276
|
+
"points": points.points,
|
|
277
|
+
"rank": points.rank,
|
|
278
|
+
"tier": points.tier,
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
positions.sort(key=lambda x: x["points"], reverse=True)
|
|
282
|
+
return positions
|
|
283
|
+
|
|
284
|
+
def print_summary(self) -> str:
|
|
285
|
+
"""Print a formatted summary.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Formatted summary string.
|
|
289
|
+
"""
|
|
290
|
+
if not self._current:
|
|
291
|
+
return "Not synced yet. Call sync_all() first."
|
|
292
|
+
|
|
293
|
+
lines = [
|
|
294
|
+
"╔══════════════════════════════════════════════════════════╗",
|
|
295
|
+
"║ 🏆 AIRDROP POINTS DASHBOARD ║",
|
|
296
|
+
"╠══════════════════════════════════════════════════════════╣",
|
|
297
|
+
f"║ Wallet: {self.config.wallet_address[:20]}...{' ' * 20}║",
|
|
298
|
+
f"║ Synced: {self._current.timestamp.strftime('%Y-%m-%d %H:%M UTC')}{' ' * 25}║",
|
|
299
|
+
"╠══════════════════════════════════════════════════════════╣",
|
|
300
|
+
]
|
|
301
|
+
|
|
302
|
+
for platform, points in sorted(
|
|
303
|
+
self._current.platforms.items(),
|
|
304
|
+
key=lambda x: x[1].points,
|
|
305
|
+
reverse=True,
|
|
306
|
+
):
|
|
307
|
+
bar_len = min(30, points.points // 100)
|
|
308
|
+
bar = "█" * bar_len + "░" * (30 - bar_len)
|
|
309
|
+
lines.append(
|
|
310
|
+
f"║ {platform:12} │ {points.points:>8,} pts │ {bar} ║"
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
lines.extend([
|
|
314
|
+
"╠══════════════════════════════════════════════════════════╣",
|
|
315
|
+
f"║ TOTAL: {self._current.total_points:>10,} points"
|
|
316
|
+
f" │ {self._current.total_campaigns} campaigns completed"
|
|
317
|
+
f" ║",
|
|
318
|
+
"╚══════════════════════════════════════════════════════════╝",
|
|
319
|
+
])
|
|
320
|
+
|
|
321
|
+
summary = "\n".join(lines)
|
|
322
|
+
print(summary)
|
|
323
|
+
return summary
|
|
324
|
+
|
|
325
|
+
def export_json(self, path: Optional[str] = None) -> str:
|
|
326
|
+
"""Export dashboard data to JSON.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
path: Optional file path to save.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
JSON string.
|
|
333
|
+
"""
|
|
334
|
+
data = {
|
|
335
|
+
"wallet": self.config.wallet_address,
|
|
336
|
+
"exported_at": datetime.now(timezone.utc).isoformat(),
|
|
337
|
+
"current": self._current.to_dict() if self._current else None,
|
|
338
|
+
"history_count": len(self._history),
|
|
339
|
+
"growth_7d": self.get_growth(7),
|
|
340
|
+
"growth_30d": self.get_growth(30),
|
|
341
|
+
}
|
|
342
|
+
json_str = json.dumps(data, indent=2, default=str)
|
|
343
|
+
|
|
344
|
+
if path:
|
|
345
|
+
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
|
346
|
+
Path(path).write_text(json_str)
|
|
347
|
+
logger.info(f"Exported dashboard to {path}")
|
|
348
|
+
|
|
349
|
+
return json_str
|
|
350
|
+
|
|
351
|
+
def export_markdown(self, path: Optional[str] = None) -> str:
|
|
352
|
+
"""Export dashboard as markdown report.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
path: Optional file path to save.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
Markdown string.
|
|
359
|
+
"""
|
|
360
|
+
if not self._current:
|
|
361
|
+
return "# No data synced yet"
|
|
362
|
+
|
|
363
|
+
lines = [
|
|
364
|
+
"# 🏆 Airdrop Points Report",
|
|
365
|
+
"",
|
|
366
|
+
f"**Wallet:** `{self.config.wallet_address}`",
|
|
367
|
+
f"**Synced:** {self._current.timestamp.strftime('%Y-%m-%d %H:%M UTC')}",
|
|
368
|
+
"",
|
|
369
|
+
"## Summary",
|
|
370
|
+
"",
|
|
371
|
+
f"- **Total Points:** {self._current.total_points:,}",
|
|
372
|
+
f"- **Campaigns Completed:** {self._current.total_campaigns}",
|
|
373
|
+
"",
|
|
374
|
+
"## Platform Breakdown",
|
|
375
|
+
"",
|
|
376
|
+
"| Platform | Points | Rank | Campaigns | Streak |",
|
|
377
|
+
"|----------|--------|------|-----------|--------|",
|
|
378
|
+
]
|
|
379
|
+
|
|
380
|
+
for platform, points in sorted(
|
|
381
|
+
self._current.platforms.items(),
|
|
382
|
+
key=lambda x: x[1].points,
|
|
383
|
+
reverse=True,
|
|
384
|
+
):
|
|
385
|
+
lines.append(
|
|
386
|
+
f"| {platform} | {points.points:,} | "
|
|
387
|
+
f"#{points.rank or '-'} | {points.campaigns_completed} | "
|
|
388
|
+
f"{points.streak_days}d |"
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Growth section
|
|
392
|
+
growth = self.get_growth(7)
|
|
393
|
+
if growth["total_delta"] != 0:
|
|
394
|
+
lines.extend([
|
|
395
|
+
"",
|
|
396
|
+
"## 7-Day Growth",
|
|
397
|
+
"",
|
|
398
|
+
f"- **Total Delta:** +{growth['total_delta']:,} points",
|
|
399
|
+
])
|
|
400
|
+
for platform, data in growth["platforms"].items():
|
|
401
|
+
if data["delta"] != 0:
|
|
402
|
+
lines.append(
|
|
403
|
+
f"- **{platform}:** +{data['delta']:,} "
|
|
404
|
+
f"({data['current']:,} total)"
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
md = "\n".join(lines)
|
|
408
|
+
|
|
409
|
+
if path:
|
|
410
|
+
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
|
411
|
+
Path(path).write_text(md)
|
|
412
|
+
logger.info(f"Exported markdown to {path}")
|
|
413
|
+
|
|
414
|
+
return md
|
|
415
|
+
|
|
416
|
+
# ─── Platform Syncers ─────────────────────────────────────────
|
|
417
|
+
|
|
418
|
+
def _sync_galxe(self, wallet: str) -> Optional[PlatformPoints]:
|
|
419
|
+
"""Sync Galxe points."""
|
|
420
|
+
try:
|
|
421
|
+
# Galxe GraphQL query for user points
|
|
422
|
+
query = """
|
|
423
|
+
query UserPoints($address: String!) {
|
|
424
|
+
user(address: $address) {
|
|
425
|
+
galxeScore {
|
|
426
|
+
score
|
|
427
|
+
rank
|
|
428
|
+
}
|
|
429
|
+
participatedCampaignCount
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
"""
|
|
433
|
+
resp = self.session.post(
|
|
434
|
+
"https://graphigo.prd.galaxy.eco/query",
|
|
435
|
+
json={"query": query, "variables": {"address": wallet}},
|
|
436
|
+
timeout=15,
|
|
437
|
+
)
|
|
438
|
+
if resp.status_code == 200:
|
|
439
|
+
data = resp.json()
|
|
440
|
+
user = data.get("data", {}).get("user", {})
|
|
441
|
+
score = user.get("galxeScore", {})
|
|
442
|
+
return PlatformPoints(
|
|
443
|
+
platform="galxe",
|
|
444
|
+
points=score.get("score", 0),
|
|
445
|
+
rank=score.get("rank", 0),
|
|
446
|
+
campaigns_completed=user.get("participatedCampaignCount", 0),
|
|
447
|
+
)
|
|
448
|
+
except Exception as e:
|
|
449
|
+
logger.error(f"Galxe sync error: {e}")
|
|
450
|
+
return None
|
|
451
|
+
|
|
452
|
+
def _sync_zealy(self, wallet: str) -> Optional[PlatformPoints]:
|
|
453
|
+
"""Sync Zealy points."""
|
|
454
|
+
# Zealy doesn't have a public API for user points
|
|
455
|
+
# Would need to scrape or use hidden API
|
|
456
|
+
return PlatformPoints(platform="zealy")
|
|
457
|
+
|
|
458
|
+
def _sync_layer3(self, wallet: str) -> Optional[PlatformPoints]:
|
|
459
|
+
"""Sync Layer3 points."""
|
|
460
|
+
try:
|
|
461
|
+
resp = self.session.get(
|
|
462
|
+
f"https://layer3.xyz/api/users/{wallet}/stats",
|
|
463
|
+
timeout=15,
|
|
464
|
+
)
|
|
465
|
+
if resp.status_code == 200:
|
|
466
|
+
data = resp.json()
|
|
467
|
+
return PlatformPoints(
|
|
468
|
+
platform="layer3",
|
|
469
|
+
points=data.get("xp", 0),
|
|
470
|
+
campaigns_completed=data.get("questsCompleted", 0),
|
|
471
|
+
)
|
|
472
|
+
except Exception as e:
|
|
473
|
+
logger.error(f"Layer3 sync error: {e}")
|
|
474
|
+
return None
|
|
475
|
+
|
|
476
|
+
def _sync_questn(self, wallet: str) -> Optional[PlatformPoints]:
|
|
477
|
+
"""Sync QuestN points."""
|
|
478
|
+
return PlatformPoints(platform="questn")
|
|
479
|
+
|
|
480
|
+
def _sync_taskon(self, wallet: str) -> Optional[PlatformPoints]:
|
|
481
|
+
"""Sync TaskOn points."""
|
|
482
|
+
return PlatformPoints(platform="taskon")
|
|
483
|
+
|
|
484
|
+
def _sync_intract(self, wallet: str) -> Optional[PlatformPoints]:
|
|
485
|
+
"""Sync Intract points."""
|
|
486
|
+
return PlatformPoints(platform="intract")
|
|
487
|
+
|
|
488
|
+
def _sync_port3(self, wallet: str) -> Optional[PlatformPoints]:
|
|
489
|
+
"""Sync Port3 points."""
|
|
490
|
+
return PlatformPoints(platform="port3")
|
|
491
|
+
|
|
492
|
+
# ─── Persistence ──────────────────────────────────────────────
|
|
493
|
+
|
|
494
|
+
def _load_history(self) -> None:
|
|
495
|
+
"""Load history from file."""
|
|
496
|
+
if not self.config.history_path:
|
|
497
|
+
return
|
|
498
|
+
path = Path(self.config.history_path)
|
|
499
|
+
if not path.exists():
|
|
500
|
+
return
|
|
501
|
+
try:
|
|
502
|
+
data = json.loads(path.read_text())
|
|
503
|
+
for snap_data in data.get("snapshots", []):
|
|
504
|
+
platforms = {}
|
|
505
|
+
for k, v in snap_data.get("platforms", {}).items():
|
|
506
|
+
platforms[k] = PlatformPoints(
|
|
507
|
+
platform=k,
|
|
508
|
+
points=v.get("points", 0),
|
|
509
|
+
rank=v.get("rank", 0),
|
|
510
|
+
campaigns_completed=v.get("campaigns_completed", 0),
|
|
511
|
+
)
|
|
512
|
+
self._history.append(PointsSnapshot(
|
|
513
|
+
timestamp=datetime.fromisoformat(snap_data["timestamp"]),
|
|
514
|
+
platforms=platforms,
|
|
515
|
+
))
|
|
516
|
+
logger.info(f"Loaded {len(self._history)} historical snapshots")
|
|
517
|
+
except Exception as e:
|
|
518
|
+
logger.warning(f"Failed to load history: {e}")
|
|
519
|
+
|
|
520
|
+
def _save_history(self) -> None:
|
|
521
|
+
"""Save history to file."""
|
|
522
|
+
if not self.config.history_path:
|
|
523
|
+
return
|
|
524
|
+
path = Path(self.config.history_path)
|
|
525
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
526
|
+
try:
|
|
527
|
+
data = {
|
|
528
|
+
"wallet": self.config.wallet_address,
|
|
529
|
+
"snapshots": [s.to_dict() for s in self._history[-100:]],
|
|
530
|
+
}
|
|
531
|
+
path.write_text(json.dumps(data, indent=2, default=str))
|
|
532
|
+
except Exception as e:
|
|
533
|
+
logger.warning(f"Failed to save history: {e}")
|