Skill: a00012025/kryptogo-meme-trader (version 2.6.0)

--- name: kryptogo-meme-trader version: "2.6.0" description: "[DEPRECATED 2026-05-04] Analyze and trade meme coins using KryptoGO's on-chain cluster analysis platform. NOTE: The kg-xyz analysis backend (wallet-data.kryptogo.app) is shutting down on 2026-05-04 — cluster analysis, wallet labels, signal dashboard, and DCA/limit order tools will stop working after that date. Swap execution via the OKX DEX aggregator continues to function." author: KryptoGO license: MIT homepage: https://www.kryptogo.xyz docs: user_guide: en: https://kryptogo.notion.site/Product-Guide-EN-26c3499de8a28179aafacb68304458ea zh-tw: https://kryptogo.notion.site/kryptogo-xyz-usage-guide zh-cn: https://kryptogo.notion.site/kryptogo-xyz-productguide-zhcn whitepaper: https://wallet-static.kryptogo.com/public/whitepaper/kryptogo-xyz-whitepaper-v1.0.pdf tags: - solana - trading - meme-coins - defi - agent-trading - on-chain-analysis - cluster-analysis - kryptogo platform: solana api_base: https://wallet-data.kryptogo.app metadata: openclaw: requires: env: - KRYPTOGO_API_KEY - SOLANA_PRIVATE_KEY - SOLANA_WALLET_ADDRESS bins: - python3 - pip - openclaw network: - wallet-data.kryptogo.app permissions: - filesystem:write:~/.openclaw/workspace/.env - filesystem:write:~/.openclaw/workspace/memory/ runtime_installs: - "pip: solders, requests (installed by scripts/setup.py on first run)" primaryEnv: KRYPTOGO_API_KEY security: default_mode: supervised trade_confirmation: required_by_default autonomous_trading: opt_in credential_access: environment_variables_only credential_file_read: setup_script_only credential_file_read_note: "Only scripts/setup.py reads and writes ~/.openclaw/workspace/.env for initial keypair generation and address repair. All other scripts access credentials exclusively via pre-loaded environment variables." local_signing_only: true --- # KryptoGO Meme Trader Agent Skill > ## SHUTDOWN NOTICE — kg-xyz Analysis Backend > > **The KryptoGO XYZ analysis backend (`wallet-data.kryptogo.app`) will be discontinued on 2026-05-04.** > > After this date, all tools that depend on cluster analysis, wallet labels, signal dashboards, and DCA/limit order reconstruction will no longer function. The swap execution path (OKX DEX aggregator + local transaction signing) is independent of the kg-xyz backend and will continue to work. > > Per-tool impact is annotated inline below. Scripts and MCP tool implementations have been kept in place so they fail gracefully (backend will return HTTP errors) and so a revert remains simple if plans change. ## Overview This skill enables an AI agent to **analyze and trade** meme coins through the KryptoGO platform, combining deep on-chain cluster analysis with trade execution. > **Degraded functionality starting 2026-05-04:** cluster analysis, wallet/token labels, signal dashboard, DCA/limit order reconstruction, and portfolio P&L enrichment will stop working when the kg-xyz backend shuts down. Swap execution continues. **Analysis** (multi-chain: Solana, BSC, Base, Monad): wallet clustering, accumulation/distribution detection, address behavior labels, network-wide accumulation signals (Pro/Alpha tier). **Trading** (Solana only): portfolio monitoring with PnL tracking, swap execution via DEX aggregator, local transaction signing (private key never leaves the machine). **Default mode is supervised** — all trades require user confirmation. Autonomous trading is available as opt-in. See `references/autonomous-trading.md` for autonomous mode, cron setup, and learning system details. --- ## When to Use - User asks to analyze a meme coin or token on Solana/BSC/Base/Monad - User asks to trade, buy, or sell tokens - User asks to scan for trending tokens or market opportunities - User asks to monitor portfolio positions or check PnL - Cron-triggered periodic portfolio monitoring and signal scanning ## When NOT to Use - BTC/ETH/major L1 macro analysis, NFTs, cross-chain bridging, non-DEX transactions, non-Solana trading --- ## Setup Flow ### 1. Get API Key 1. Go to [kryptogo.xyz/account](https://www.kryptogo.xyz/account) and create an API key 2. Add to `~/.openclaw/workspace/.env`: ```bash echo [REDACTED_SECRET] >> ~/.openclaw/workspace/.env && chmod 600 ~/.openclaw/workspace/.env ``` > **Do NOT paste your API key directly in chat.** Always set secrets via `.env` file. ### 2. Generate Agent Wallet ```bash python3 scripts/setup.py ``` Creates a Solana keypair, saves to `.env` with chmod 600, prints public address to fund. ### 3. Fund the Wallet Send SOL to the agent's public address (minimum 0.1 SOL). ### Security Rules - **NEVER** print, log, or include private keys in any message or CLI argument - **NEVER** accept secrets pasted directly in chat — instruct users to set them in `.env` - **NEVER** use the Read tool on `.env` — load credentials via `source` command only - Runtime scripts do NOT read `.env` directly — all credentials are accessed via environment variables only, which must be pre-loaded by the caller (`source ~/.openclaw/workspace/.env`) - **Exception:** `scripts/setup.py` reads and writes `.env` for initial keypair generation and address repair — this is the only script that touches credential files - Private key stays in memory only during local signing — never sent to any server --- ## Authentication All endpoints require: `Authorization: Bearer sk_live_<48 hex chars>` | Tier | Daily API Calls | Trading Fee | Signal Dashboard | KOL Finder | |-------|-----------------|-------------|------------------|------------| | Free | 100 calls/day | 1% | No | No | | Pro | 1,000 calls/day | 0.5% | Yes | Yes | | Alpha | 5,000 calls/day | 0% | Yes | Yes | --- ## Agent Behavior ### Session Initialization On every session start (including heartbeat/cron), the agent MUST load credentials BEFORE running any scripts: ```bash source ~/.openclaw/workspace/.env ``` This is REQUIRED — scripts do not read `.env` directly. All credentials are accessed via environment variables only. ### Default Mode: Supervised By default, the agent operates in **supervised mode**: it analyzes tokens, presents recommendations, and waits for user approval before executing any trade. Stop-loss/take-profit conditions are reported to the user but not auto-executed. To enable autonomous trading, set `require_trade_confirmation: false` in preferences. See `references/autonomous-trading.md` for full details. ### Persistence (CRITICAL) **IMMEDIATELY after submitting a transaction, the agent MUST:** 1. Write trade details to `memory/trading-journal.json` with `status: "OPEN"` 2. Include: `token_symbol`, `token_address`, `entry_price`, `position_size_sol`, `tx_hash`, `timestamp` ### User Preferences Store in `memory/trading-preferences.json`: | Preference | Default | Description | |------------|---------|-------------| | `max_position_size` | 0.1 SOL | Max SOL per trade | | `max_open_positions` | 5 | Max concurrent open positions | | `max_daily_trades` | 20 | Max trades per day | | `stop_loss_pct` | 30% | Notify/sell when loss exceeds this | | `take_profit_pct` | 100% | Notify/sell when gain exceeds this | | `min_market_cap` | $500K | Skip tokens below this | | `scan_count` | 10 | Trending tokens per scan | | `risk_tolerance` | "conservative" | "conservative" (skip medium risk), "moderate" (ask on medium), "aggressive" (auto-trade medium) | | `require_trade_confirmation` | true | Set to false for autonomous mode | | `chains` | ["solana"] | Chains to scan | --- ## Safety Guardrails ### Trading Limits (Hard Caps) | Limit | Default | Overridable? | |-------|---------|--------------------| | Max single trade | 0.1 SOL | Yes, via `max_position_size` | | Max concurrent positions | 5 | Yes, via `max_open_positions` | | Max daily trade count | 20 | Yes, via `max_daily_trades` | | Price impact abort | >10% | No — always abort | | Price impact warn | >5% | No — always warn | If any limit is hit, the agent **must stop and notify the user**. ### Credential Isolation Runtime scripts in this skill do NOT read `.env` files directly. All credentials are accessed via environment variables only, which must be pre-loaded by the caller (`source ~/.openclaw/workspace/.env`). This ensures no runtime script can independently access or exfiltrate credential files. **Exception:** `scripts/setup.py` reads and writes `.env` — it loads existing keys to avoid regeneration, backs up `.env` before changes, and writes new keypair entries. This is the only script that touches credential files, and it runs only during initial setup or explicit `--force` regeneration. --- ## Automated Monitoring (Cron) ### Quick Setup ```bash # Supervised mode (default): analysis + notifications, no auto-execution source ~/.openclaw/workspace/.env && bash scripts/cron-examples.sh setup-default # Autonomous mode (opt-in): auto-buys and auto-sells source ~/.openclaw/workspace/.env && bash scripts/cron-examples.sh setup-autonomous # Remove all cron jobs bash scripts/cron-examples.sh teardown ``` | Job | Interval | Default Behavior | |-----|----------|---------| | `stop-loss-tp` | 5 min | Report triggered conditions, do NOT auto-sell | | `discovery-scan` | 1 hour | Analyze and send recommendations, do NOT auto-buy | For full cron configuration, manual setup, heartbeat alternative, and monitoring workflow details, see `references/autonomous-trading.md`. --- ## On-Chain Analysis Framework (7-Step Pipeline) > ⚠️ **Unavailable after 2026-05-04:** Steps 1–6 all hit `wallet-data.kryptogo.app` and will stop working when the kg-xyz backend shuts down. Step 7 (execute trade via `swap.py`) continues to work, since the swap path routes through the OKX DEX aggregator and signs locally. ### Step 1: Token Overview & Market Cap Filter `/token-overview?address=<mint>&chain_id=<id>` — get name, price, market cap, holders, risk_level. Skip if market cap < `min_market_cap`. ### Step 2: Cluster Analysis `/analyze/<mint>?chain_id=<id>` — wallet clusters, top holders, metadata. - ≥30-35% = "controlled" — major entity present - ≥50% = high concentration risk - Single cluster >50% → skip (rug pull risk) > **Free tier limitation:** Cluster analysis only returns the top 2 clusters. To see full cluster data, upgrade at [kryptogo.xyz/pricing](https://www.kryptogo.xyz/pricing). ### Step 3: Cluster Trend (Multi-Timeframe) `/analyze-cluster-change/<mint>` — `cluster_ratio` + `changes` across 15m/1h/4h/1d/7d. Core insight: **Price and cluster holdings DIVERGING** is the key signal. - Rising price + falling cluster % = distribution (bearish) - Falling price + rising cluster % = accumulation (bullish) ### Step 4: Address Labels + Sell Pressure Verification 1. `/token-wallet-labels` → identify dev/sniper/bundle wallets 2. `/balance-history` for each risky address → check if still holding 3. Compute `risky_ratio` = active risky holdings / total cluster holdings 4. >30% = high risk, 10-30% = medium, <10% = low > Labels represent *behavioral history*, not current holdings. Always verify via `/balance-history`. ### Step 5: Deep Dive (Optional) `/balance-history`, `/balance-increase/<mint>`, `/top-holders-snapshot/<mint>`, `/analyze-dca-limit-orders/<mint>`, `/cluster-wallet-connections` ### Step 6: Decision Apply Bullish Checklist from `references/decision-framework.md`. ### Step 7: Execute Trade **Use `scripts/swap.py` for execution** — handles wallet_address injection, error checking, and journal logging. > ✓ **Continues to work after 2026-05-04:** swap execution goes through the OKX DEX aggregator with local signing and is independent of the kg-xyz backend. ```bash source ~/.openclaw/workspace/.env && python3 scripts/swap.py <token_mint> 0.1 source ~/.openclaw/workspace/.env && python3 scripts/swap.py <token_mint> <amount> --sell ``` --- ## API Quick Reference > ⚠️ **After 2026-05-04:** Every endpoint below is served by `wallet-data.kryptogo.app` and will be shut down. The swap tool (`swap_tokens` / `scripts/swap.py`) bypasses these endpoints and routes to the OKX DEX aggregator directly, so it continues to work. | Endpoint | Method | Purpose | After 2026-05-04 | |----------|--------|---------|------------------| | `/agent/account` | GET | Check tier & quota | ⚠️ Unavailable | | `/agent/trending-tokens` | GET | Scan trending tokens | ⚠️ Unavailable | | `/agent/portfolio` | GET | Wallet portfolio + PnL | ⚠️ Unavailable (P&L enrichment); balances still fetchable via Solana RPC | | `/agent/swap` | POST | Build unsigned swap tx (Solana only) | ⚠️ Unavailable (swap now routes through OKX DEX; see `scripts/swap.py`) | | `/agent/submit` | POST | Submit signed tx (Solana only) | ⚠️ Unavailable (submit directly to Solana RPC instead) | | `/token-overview` | GET | Token metadata & market data | ⚠️ Unavailable | | `/analyze/:token_mint` | GET | Full cluster analysis | ⚠️ Unavailable | | `/analyze-cluster-change/:token_mint` | GET | Cluster ratio trends | ⚠️ Unavailable | | `/balance-history` | POST | Time-series balance data | ⚠️ Unavailable | | `/wallet-labels` | POST | Behavior labels | ⚠️ Unavailable | | `/token-wallet-labels` | POST | Token-specific labels | ⚠️ Unavailable | | `/signal-dashboard` | GET | Curated accumulation signals (Pro+) | ⚠️ Unavailable | > Full request/response details: see `references/api-reference.md` --- ## Multi-Chain Support | Chain | chain_id | Analysis | Trading | |-------|----------|----------|---------| | Solana | `501` | Yes | Yes | | BSC | `56` | Yes | No | | Base | `8453` | Yes | No | | Monad | `143` | Yes | No | --- ## Error Handling | Code | Meaning | Action | |------|---------|--------| | 400 | Bad Request | Check parameters | | 401 | Unauthorized | Check API key | | 402 | Quota Exceeded | Wait for daily reset or upgrade | | 403 | Forbidden | Requires higher tier | | 502/504 | Server error | Retry once after 10s | --- ## Operational Scripts All scripts require credentials to be pre-loaded: `source ~/.openclaw/workspace/.env` before running. > **Post-shutdown (2026-05-04):** `portfolio.sh`, `trending.sh`, `analysis.sh`, and `test-api.sh` all call the kg-xyz backend and will begin returning HTTP errors. `swap.py` continues to work because it uses the OKX DEX aggregator and signs locally. Scripts are kept in place so that the skill fails gracefully and a revert remains trivial if the backend comes back. ```bash source ~/.openclaw/workspace/.env && bash scripts/portfolio.sh # Portfolio check (⚠️ breaks after 2026-05-04) source ~/.openclaw/workspace/.env && bash scripts/trending.sh # Trending tokens (⚠️ breaks after 2026-05-04) source ~/.openclaw/workspace/.env && bash scripts/analysis.sh # Full analysis dashboard (⚠️ breaks after 2026-05-04) source ~/.openclaw/workspace/.env && python3 scripts/swap.py <mint> 0.1 # Buy (✓ continues to work) source ~/.openclaw/workspace/.env && python3 scripts/swap.py <mint> <amt> --sell # Sell (✓ continues to work) source ~/.openclaw/workspace/.env && bash scripts/test-api.sh # API connectivity test (⚠️ breaks after 2026-05-04) ``` --- ## Learning & Adaptation The agent improves over time by recording trades, analyzing outcomes, and adjusting strategy. Every trade is logged to `memory/trading-journal.json`, losses trigger post-mortems, and periodic reviews propose parameter changes. For full details on the learning system, trade journal format, post-mortem process, and strategy reviews, see `references/autonomous-trading.md`. --- ## Core Concepts | Concept | Key Insight | |---------|-------------| | **Cluster** | Group of wallets controlled by same entity | | **Cluster Ratio** | % of supply held by clusters. ≥30% = controlled, ≥50% = high risk | | **Developer** | Deployed the token. Highest dump risk | | **Sniper** | Bought within 1s of creation. Sell pressure if not cleared | | **Smart Money** | Realized profit >$100K. Accumulation often precedes price moves | | **Accumulation** | Cluster % rising + price consolidating = bullish | | **Distribution** | Price rising + cluster % falling = bearish | > Full concepts guide: see `references/concepts.md` --- ## Best Practices 1. Always check `/agent/account` first to confirm tier and quota 2. Always check `/agent/portfolio` on startup to detect existing positions 3. Never expose private keys in logs, messages, or CLI arguments 4. Validate price impact before submitting — abort >10%, warn >5% 5. Sign and submit promptly — blockhash expires after ~60 seconds 6. Persist state to `memory/trading-state.json` after every action 7. Log every trade to journal — no exceptions 8. Read `memory/trading-lessons.md` before scanning — avoid repeating known bad patterns --- ## File Structure ``` kryptogo-meme-trader/ ├── SKILL.md ← You are here ├── package.json ├── .env.example ├── references/ │ ├── api-reference.md ← Full API docs │ ├── concepts.md ← Core concepts │ ├── decision-framework.md ← Entry/exit strategies │ └── autonomous-trading.md ← Autonomous mode, cron, learning system ├── scripts/ │ ├── setup.py ← First-time setup │ ├── cron-examples.sh ← Cron configurations │ ├── portfolio.sh / trending.sh / analysis.sh / test-api.sh │ ├── swap.py ← Swap executor │ └── trading-preferences.example.json └── examples/ ├── trading-workflow.py └── deep-analysis-workflow.py ```

path: package.json
content: {
  "name": "kryptogo-meme-trader",
  "version": "2.6.0",
  "security": {
    "default_mode": "supervised",
    "trade_confirmation": "required_by_default",
    "autonomous_trading": "opt_in",
    "credential_file_read": false,
    "local_signing_only": true
  }
}


path: scripts/cron-examples.sh
content: #!/usr/bin/env bash
# KryptoGO Meme Trader — OpenClaw Cron Configurations
#
# Usage:
#   bash scripts/cron-examples.sh setup-default      # Supervised: analysis + notifications (recommended)
#   bash scripts/cron-examples.sh setup-autonomous    # Autonomous: auto-buy + auto-sell (opt-in)
#   bash scripts/cron-examples.sh teardown            # Remove all trading cron jobs
#
# Or copy individual `openclaw cron add` commands from the strategies below.

set -euo pipefail

# ============================================================================
# COMMAND: setup-default (supervised, analysis-only)
# ============================================================================

if [[ "${1:-}" == "setup-default" ]]; then
  echo "Setting up default trading cron jobs (supervised mode)..."
  echo "  Trades will NOT be auto-executed. The agent will analyze and send recommendations."
  echo ""

  echo "→ Adding stop-loss-tp (every 5 min)..."
  openclaw cron add \
    --every 5m \
    --name "stop-loss-tp" \
    --message "Run the monitoring script: 'source ~/.openclaw/workspace/.env && python3 skills/kryptogo-meme-trader/scripts/monitor.py'. Check portfolio for stop-loss or take-profit conditions. Report triggered conditions to the user. Do NOT auto-execute sells."

  echo ""
  echo "→ Adding discovery-scan (every 1 hour)..."
  openclaw cron add \
    --every 1h \
    --name "discovery-scan" \
    --timeout-seconds 900 \
    --message "Load the kryptogo-meme-trader skill. Source .env. Execute the full discovery workflow:
1. Read memory/trading-preferences.json for current parameters.
2. Read memory/trading-lessons.md (if exists) to avoid known bad patterns.
3. Check open positions count — skip scanning if at max_open_positions.
4. Scan for candidates:
   - Pro/Alpha tier: call /signal-dashboard (sort_by=signal_count, page_size=10).
   - Free tier: call /agent/trending-tokens with min_market_cap filter.
5. Run top candidates through the 7-step analysis pipeline.
6. Send recommendations to the user. Do NOT auto-execute buys.
7. Update memory/trading-state.json with scan timestamp.
8. If any trades were closed since last scan: run post-trade reflection and update trading-lessons.md.
Report ALL findings to the user. If nothing happened, stay silent."

  echo ""
  echo "✓ Default setup complete (supervised mode). Two cron jobs active:"
  echo "  • stop-loss-tp    — every 5 min  (portfolio monitoring, notify only)"
  echo "  • discovery-scan  — every 1 hour (token discovery, recommend only)"
  echo ""
  echo "Run 'openclaw cron list' to verify."
  exit 0
fi

# ============================================================================
# COMMAND: setup-autonomous (opt-in, auto-execution enabled)
# ============================================================================

if [[ "${1:-}" == "setup-autonomous" ]]; then
  echo ""
  echo "WARNING: Autonomous mode enables automatic trade execution."
  echo "Only use with a dedicated low-value wallet."
  echo ""

  echo "Setting up autonomous trading cron jobs..."
  echo ""

  echo "→ Adding stop-loss-tp (every 5 min)..."
  openclaw cron add \
    --every 5m \
    --name "stop-loss-tp" \
    --message "Run the monitoring script: 'source ~/.openclaw/workspace/.env && python3 skills/kryptogo-meme-trader/scripts/monitor.py'. Report the output."

  echo ""
  echo "→ Adding discovery-scan (every 30 min)..."
  openclaw cron add \
    --every 30m \
    --name "discovery-scan" \
    --timeout-seconds 900 \
    --message "Load the kryptogo-meme-trader skill. Source .env. Execute the full discovery workflow:
1. Read memory/trading-preferences.json for current parameters.
2. Read memory/trading-lessons.md (if exists) to avoid known bad patterns.
3. Check open positions count — skip buying if at max_open_positions.
4. Scan for candidates:
   - Pro/Alpha tier: call /signal-dashboard (sort_by=signal_count, page_size=10).
   - Free tier: call /agent/trending-tokens with min_market_cap filter.
5. Run top candidates through the 7-step analysis pipeline.
6. For tokens passing ALL criteria in the Bullish Checklist: execute buy with max_position_size.
   For medium risk: ask user for confirmation (unless risk_tolerance=aggressive).
7. Log any new trades to memory/trading-journal.json.
8. Update memory/trading-state.json with scan timestamp.
9. If any trades were closed since last scan: run post-trade reflection and update trading-lessons.md.
10. If 20+ trades or 7+ days since last review: trigger Strategy Review.
Report ALL actions to the user. If nothing happened, stay silent."

  echo ""
  echo "✓ Autonomous setup complete. Two cron jobs active:"
  echo "  • stop-loss-tp    — every 5 min  (exit monitoring + auto-sell)"
  echo "  • discovery-scan  — every 30 min (new token discovery + auto-buy)"
  echo ""
  echo "Run 'openclaw cron list' to verify."
  exit 0
fi

# ============================================================================
# COMMAND: teardown
# ============================================================================

if [[ "${1:-}" == "teardown" ]]; then
  echo "Removing all trading cron jobs..."
  openclaw cron remove stop-loss-tp 2>/dev/null || true
  openclaw cron remove discovery-scan 2>/dev/null || true
  openclaw cron remove daily-portfolio-summary 2>/dev/null || true
  openclaw cron remove trading-scan-15m 2>/dev/null || true
  openclaw cron remove signal-monitor 2>/dev/null || true
  openclaw cron remove portfolio-monitor 2>/dev/null || true
  openclaw cron remove conservative-scan 2>/dev/null || true
  echo "✓ All trading cron jobs removed."
  exit 0
fi

# ============================================================================
# If no command given, print usage and strategy reference
# ============================================================================

cat <<'USAGE'
Usage:
  bash scripts/cron-examples.sh setup-default      # Supervised: analysis + notify (recommended)
  bash scripts/cron-examples.sh setup-autonomous    # Autonomous: auto-buy + auto-sell (opt-in)
  bash scripts/cron-examples.sh teardown            # Remove all trading cron jobs

Below are additional strategy examples you can copy and run individually.
USAGE

echo ""

# ============================================================================
# STRATEGY 1: Default — Supervised Dual Cron (RECOMMENDED)
# ============================================================================
# Two jobs: fast portfolio monitoring + slower discovery cycle
# Both in supervised mode: notify user, do NOT auto-execute
#
# Install with: bash scripts/cron-examples.sh setup-default

# ============================================================================
# STRATEGY 2: Daily Summary Only
# ============================================================================
# No trading, just a morning report

# openclaw cron add \
#   --cron "0 1 * * *" \
#   --name "daily-portfolio-summary" \
#   --message "Load the kryptogo-meme-trader skill. Call /agent/portfolio with the agent wallet address from .env. Send me a daily summary including: all open positions with current PnL (% and USD), total portfolio value, any positions that hit stop-loss or take-profit thresholds overnight. Format as a clean summary, not raw JSON."

# ============================================================================
# STRATEGY 3: Conservative (ask before trading)
# ============================================================================
# Scan and analyze, but NEVER auto-trade — always ask first

# openclaw cron add \
#   --every 1h \
#   --name "conservative-scan" \
#   --message "Load the kryptogo-meme-trader skill. Scan trending tokens and run analysis. Do NOT execute any trades. Instead, if you find tokens that pass the bullish checklist, send me a summary with your recommendation and ask for confirmation before trading."

# ============================================================================
# UTILITY COMMANDS
# ============================================================================

# openclaw cron list                          # List all active cron jobs
# openclaw cron remove <name>                 # Remove a specific job
# bash scripts/cron-examples.sh teardown      # Remove ALL trading jobs


path: scripts/monitor.py
content: #!/usr/bin/env python3
import json
import os
import subprocess
import sys
import requests

# Credentials must be pre-loaded in the environment (source ~/.openclaw/workspace/.env)
[REDACTED_SECRET]"KRYPTOGO_API_KEY")
WALLET = os.environ.get("SOLANA_WALLET_ADDRESS")

if not API_KEY or not WALLET:
    print("ERROR: Missing KRYPTOGO_API_KEY or SOLANA_WALLET_ADDRESS.")
    print("Run 'source ~/.openclaw/workspace/.env' before running this script.")
    sys.exit(1)

# Config
PREFS_PATH = os.path.expanduser("~/.openclaw/workspace/memory/trading-preferences.json")
STOP_LOSS_PCT = 30
TAKE_PROFIT_PCT = 100

if os.path.exists(PREFS_PATH):
    try:
        with open(PREFS_PATH) as f:
            prefs = json.load(f)
            STOP_LOSS_PCT = prefs.get("stop_loss_pct", 30)
            TAKE_PROFIT_PCT = prefs.get("take_profit_pct", 100)
    except:
        pass

print(f"Monitor Config: SL={STOP_LOSS_PCT}%, TP={TAKE_PROFIT_PCT}%")

# Fetch Portfolio
try:
    resp = requests.get(
        "https://wallet-data.kryptogo.app/agent/portfolio",
        headers={"Authorization": f"Bearer {API_KEY}"},
        params={"wallet_address": WALLET},
        timeout=10
    )
    if resp.status_code != 200:
        print(f"Error fetching portfolio: {resp.status_code} {resp.text}")
        exit(1)

    data = resp.json()
    tokens = data.get("tokens", [])
    
    # Filter for active tokens
    active_positions = [t for t in tokens if float(t.get("balance", 0)) > 0]
    
    if not active_positions:
        print("No open positions.")
        exit(0)
        
    for pos in active_positions:
        symbol = pos.get("symbol", "Unknown")
        mint = pos.get("mint")
        balance = float(pos.get("balance", 0))
        
        # PnL from API
        # API returns realized/unrealized PnL in USD. 
        # We need unrealized PnL percentage.
        # Check if API provides pct directly, otherwise calc.
        
        unrealized_pnl_usd = float(pos.get("unrealized_pnl", 0))
        usd_value = float(pos.get("usd_value", 0))
        
        # Calculate cost basis to get %
        cost_basis = usd_value - unrealized_pnl_usd
        
        pnl_pct = 0
        if cost_basis > 0:
            pnl_pct = (unrealized_pnl_usd / cost_basis) * 100
        
        # Fallback: if cost_basis is 0 or weird, maybe use journal? 
        # For now rely on API logic.
            
        print(f"Position: {symbol} | Value: ${usd_value:.2f} | PnL: {pnl_pct:.2f}% (${unrealized_pnl_usd:.2f})")
        
        action = None
        
        # CLUSTER DUMP CHECK
        try:
            cluster_resp = requests.get(
                f"https://wallet-data.kryptogo.app/analyze-cluster-change/{mint}",
                headers={"Authorization": f"Bearer {API_KEY}"},
                timeout=5
            )
            if cluster_resp.status_code == 200:
                c_data = cluster_resp.json()
                cluster_ratio = c_data.get("cluster_ratio", 0)
                changes = c_data.get("changes", {})
                change_1h = changes.get("1h", 0)
                change_15m = changes.get("15m", 0)
                
                # Rule 1: Hard Dump (1h drop > 5%)
                if change_1h <= -0.05:
                    print(f"  ⚠️ ALERT: Cluster dumping! 1h change: {change_1h*100:.1f}%")
                    action = "CLUSTER_DUMP"
                
                # Rule 1.5: Flash Dump (15m drop > 2.5%) - Faster reaction for new coins
                if change_15m <= -0.025:
                    print(f"  ⚠️ ALERT: Flash dump detected! 15m change: {change_15m*100:.1f}%")
                    action = "CLUSTER_FLASH_DUMP"
                
                # Rule 2: Loss of Control (Ratio < 15%)
                if cluster_ratio < 0.15 and pnl_pct < 20: # Only if profit is low
                     print(f"  ⚠️ ALERT: Cluster control lost! Ratio: {cluster_ratio*100:.1f}%")
                     action = "CLUSTER_LOST"
        except Exception:
            pass

        if pnl_pct <= -STOP_LOSS_PCT:
            action = "STOP_LOSS"
        elif pnl_pct >= TAKE_PROFIT_PCT:
            action = "TAKE_PROFIT"
            
        if action:
            print(f"⚠️ Triggering {action} for {symbol} ({mint})...")
            
            # Call swap.py to execute sell
            # Ensure path is absolute or relative to workspace root
            script_path = os.path.expanduser("~/.openclaw/workspace/skills/kryptogo-meme-trader/scripts/swap.py")
            
            cmd = [
                "python3", 
                script_path, 
                mint, 
                str(balance), 
                "--sell"
            ]
            print(f"Running: {' '.join(cmd)}")
            subprocess.run(cmd)
            
except Exception as e:
    print(f"Error checking portfolio: {e}")


path: scripts/swap.py
content: #!/usr/bin/env python3
"""
KryptoGO Meme Trader — Swap Executor

Builds, signs locally, and submits a token swap transaction via the
KryptoGO Agent Trading API (Solana only).

Usage:
  python3 scripts/swap.py <output_mint> [amount_sol]
  python3 scripts/swap.py <output_mint> [amount_sol] --sell
  python3 scripts/swap.py <output_mint> [amount_sol] --slippage 500

Examples:
  # Buy 0.1 SOL worth of a token
  python3 scripts/swap.py So11...token_mint_address 0.1

  # Sell tokens back to SOL (--sell flag)
  python3 scripts/swap.py So11...token_mint_address 1000000 --sell

  # Custom slippage (basis points, default: 300 = 3%)
  python3 scripts/swap.py So11...token_mint_address 0.1 --slippage 500

Environment (must be sourced before running):
  KRYPTOGO_API_KEY       - API key for authentication
  SOLANA_WALLET_ADDRESS  - Agent wallet public address
  SOLANA_PRIVATE_KEY     - Agent wallet private key (base58, never sent to server)

SECURITY:
  - This script does NOT read .env directly. Credentials must be loaded into
    the environment by the caller (e.g., `source ~/.openclaw/workspace/.env`).
  - Private key is used ONLY for local signing
  - Key is never sent to any server or logged to output
  - Transaction is signed locally with `solders` library
"""

import argparse
import base64
import json
import os
import sys
import time
import datetime
from decimal import Decimal, getcontext

import requests

# Set decimal precision high enough for crypto
getcontext().prec = 28

# ---------------------------------------------------------------------------
# Environment
# ---------------------------------------------------------------------------

API_BASE = "https://wallet-data.kryptogo.app"
[REDACTED_SECRET]"KRYPTOGO_API_KEY")
WALLET = os.environ.get("SOLANA_WALLET_ADDRESS")
PRIVATE_KEY = os.environ.get("SOLANA_PRIVATE_KEY")

SOL_MINT = [REDACTED_SECRET]

if not all([API_KEY, WALLET, PRIVATE_KEY]):
    sys.exit("ERROR: Missing required env vars. Run 'source ~/.openclaw/workspace/.env' before running this script.")

HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------

parser = argparse.ArgumentParser(description="KryptoGO Swap Executor")
parser.add_argument("token_mint", help="Token mint address to buy or sell")
parser.add_argument(
    "amount",
    type=str,  # Capture as string to preserve precision for Decimal
    nargs="?",
    default="0.1",
    help="Amount in SOL (buy) or token units (sell). Default: 0.1 SOL",
)
parser.add_argument(
    "--sell",
    action="store_true",
    help="Sell tokens back to SOL (default is buy)",
)
parser.add_argument(
    "--slippage",
    type=int,
    default=500,
    help="Slippage tolerance in basis points (default: 500 = 5%%)",
)
parser.add_argument(
    "--max-impact",
    type=float,
    default=10.0,
    help="Max price impact %% before aborting (default: 10)",
)
args = parser.parse_args()

# ---------------------------------------------------------------------------
# Step 1: Build swap transaction
# ---------------------------------------------------------------------------

# Fetch token info for journal
token_info = {}
try:
    resp = requests.get(
        f"{API_BASE}/token-overview",
        params={"address": args.token_mint},
        headers=HEADERS,
        timeout=5
    )
    if resp.status_code == 200:
        token_info = resp.json()
except Exception as e:
    print(f"Warning: Could not fetch token info: {e}")

token_symbol = token_info.get("symbol", "UNKNOWN")
decimals = token_info.get("decimals")
current_price = token_info.get("price", 0)

# Use Decimal for precise arithmetic
amount_dec = Decimal(args.amount)

if args.sell:
    if decimals is None:
        sys.exit("ABORT: Could not determine token decimals. Refusing to sell with unknown precision.")
    input_mint = args.token_mint
    output_mint = SOL_MINT
    # Exact calculation: amount * 10^decimals
    amount_raw = int(amount_dec * (Decimal(10) ** decimals))
    print(f"=== SELL: {args.amount} {token_symbol} ({amount_raw} raw units) → SOL ===")
else:
    input_mint = SOL_MINT
    output_mint = args.token_mint
    # Exact calculation: amount * 10^9 (SOL decimals)
    amount_raw = int(amount_dec * Decimal(1_000_000_000))
    print(f"=== BUY: {args.amount} SOL ({amount_raw} lamports) → {token_symbol} (price: ${current_price}) ===")

print("\nStep 1: Building swap transaction...")
swap_resp = requests.post(
    f"{API_BASE}/agent/swap",
    headers=HEADERS,
    json={
        "input_mint": input_mint,
        "output_mint": output_mint,
        "amount": amount_raw,
        "slippage_bps": args.slippage,
        "wallet_address": WALLET,
    },
)

if swap_resp.status_code != 200:
    sys.exit(f"Swap build failed (HTTP {swap_resp.status_code}): {swap_resp.text}")

swap_data = swap_resp.json()
fee_payer = swap_data.get("fee_payer")
price_impact = float(swap_data.get("quote", {}).get("price_impact_pct", 0))

print(f"  Fee payer: {fee_payer}")
print(f"  Price impact: {price_impact}%")
print(f"  Platform fee: {swap_data.get('platform_fee_lamports', '?')} lamports")

# Verify fee payer matches our wallet
if fee_payer and fee_payer != WALLET:
    sys.exit(f"ABORT: Fee payer mismatch! Expected {WALLET}, got {fee_payer}")

# Check price impact
if abs(price_impact) > args.max_impact:
    sys.exit(f"ABORT: Price impact {price_impact}% exceeds max {args.max_impact}%")
elif abs(price_impact) > 5:
    print(f"  ⚠ WARNING: Price impact {price_impact}% is high (>5%)")

# Extract estimated output amount for journal
estimated_out_amount = 0
try:
    quote = swap_data.get("quote", {})
    # Look for outAmount in quote (structure varies, usually outAmount or amount_out)
    raw_out = quote.get("outAmount") or quote.get("amount_out") or quote.get("out_amount")
    if raw_out and decimals is not None:
        estimated_out_amount = float(raw_out) / (10 ** decimals)
except Exception:
    pass

# ---------------------------------------------------------------------------
# Step 2: Sign locally
# ---------------------------------------------------------------------------

print("\nStep 2: Signing transaction locally...")
from solders.keypair import Keypair
from solders.transaction import VersionedTransaction

tx_bytes = base64.b64decode(swap_data["transaction"])
tx = VersionedTransaction.from_bytes(tx_bytes)
keypair = Keypair.from_base58_string(PRIVATE_KEY)
signed_tx = VersionedTransaction(tx.message, [keypair])
signed_tx_b64 = base64.b64encode(bytes(signed_tx)).decode()
print("  Signed successfully.")

# ---------------------------------------------------------------------------
# Step 3: Submit
# ---------------------------------------------------------------------------

print("\nStep 3: Submitting signed transaction...")
submit_resp = requests.post(
    f"{API_BASE}/agent/submit",
    headers=HEADERS,
    json={"signed_transaction": signed_tx_b64},
)

if submit_resp.status_code != 200:
    sys.exit(f"Submit failed (HTTP {submit_resp.status_code}): {submit_resp.text}")

result = submit_resp.json()
tx_hash = result.get("tx_hash", result.get("signature", "?"))
print(f"\n  Status: {result.get('status', '?')}")
print(f"  Tx Hash: {tx_hash}")
print(f"  Explorer: https://solscan.io/tx/{tx_hash}")

# ---------------------------------------------------------------------------
# Step 4: Persist to Trading Journal
# ---------------------------------------------------------------------------

journal_path = os.path.expanduser("~/.openclaw/workspace/memory/trading-journal.json")

# Ensure directory exists
os.makedirs(os.path.dirname(journal_path), exist_ok=True)

# Load existing journal
try:
    if os.path.exists(journal_path):
        with open(journal_path, "r") as f:
            journal_data = json.load(f)
    else:
        journal_data = {"trades": []}
except Exception as e:
    print(f"Warning: Could not read journal file: {e}. Creating new.")
    journal_data = {"trades": []}

# Construct log entry
timestamp_ms = int(time.time() * 1000)
iso_time = datetime.datetime.now().isoformat()

if not args.sell:
    # BUY -> Create OPEN position
    
    # Calculate effective entry price in SOL (not USD)
    entry_price_sol = 0
    if estimated_out_amount > 0:
        entry_price_sol = float(amount_dec) / estimated_out_amount
        
    new_trade = {
        "id": f"{iso_time}_{token_symbol}",
        "token_mint": args.token_mint,
        "symbol": token_symbol,
        "chain_id": "501", # Default Solana
        "action": "BUY",
        "amount_sol": float(amount_dec),
        "token_amount": estimated_out_amount,
        "price_at_entry_sol": entry_price_sol,
        "price_at_entry_usd": current_price, # USD price from API
        "tx_hash": tx_hash,
        "status": "OPEN",
        "timestamp": iso_time,
        "timestamp_ms": timestamp_ms
    }
    journal_data["trades"].append(new_trade)
    print(f"\nLogged BUY trade to journal: {new_trade['id']}")
    print(f"  Entry Price: {entry_price_sol:.9f} SOL / ${current_price} USD")

else:
    # SELL -> Find open position and close it (or just log sell event)
    # Simple logic: Log a separate SELL entry. Advanced logic: Update corresponding BUY.
    # For now, append a SELL entry so humans can see it.
    sell_entry = {
        "id": f"{iso_time}_{token_symbol}_SELL",
        "token_mint": args.token_mint,
        "symbol": token_symbol,
        "chain_id": "501",
        "action": "SELL",
        "amount_sol": 0, # Unknown without price at exit
        "tx_hash": tx_hash,
        "status": "CLOSED",
        "timestamp": iso_time,
        "timestamp_ms": timestamp_ms
    }
    journal_data["trades"].append(sell_entry)
    print(f"\nLogged SELL trade to journal: {sell_entry['id']}")

# Save journal
try:
    with open(journal_path, "w") as f:
        json.dump(journal_data, f, indent=2)
    print(f"Journal updated at {journal_path}")
except Exception as e:
    print(f"ERROR: Failed to write journal: {e}")


path: scripts/portfolio.sh
content: #!/usr/bin/env bash
# KryptoGO Meme Trader — Portfolio Check
#
# Shows wallet balance, token holdings, and PnL summary.
#
# Usage:
#   bash scripts/portfolio.sh                    # Uses SOLANA_WALLET_ADDRESS from .env
#   bash scripts/portfolio.sh <wallet_address>   # Check a specific wallet

set -euo pipefail

# Credentials must be pre-loaded in the environment (source ~/.openclaw/workspace/.env)
if [ -z "${KRYPTOGO_API_KEY:-}" ]; then
  echo "ERROR: Run 'source ~/.openclaw/workspace/.env' before running this script."
  exit 1
fi

BASE="https://wallet-data.kryptogo.app"
AUTH="Authorization: Bearer $KRYPTOGO_API_KEY"
WALLET="${1:-$SOLANA_WALLET_ADDRESS}"

curl -s "$BASE/agent/portfolio?wallet_address=$WALLET" -H "$AUTH" | python3 -c "
import sys, json
d = json.load(sys.stdin)
print(f\"Wallet: {d.get('wallet', '?')}\")
print(f\"SOL Balance: {d.get('sol_balance', '0')} SOL\")
print(f\"Realized PnL: {d.get('total_realized_pnl', '0')}\")
print(f\"Unrealized PnL: {d.get('total_unrealized_pnl', '0')}\")
tokens = d.get('tokens', [])
if tokens:
    print(f'Holdings: {len(tokens)} tokens')
    for t in tokens[:10]:
        sym = t.get('symbol', '?')
        bal = t.get('balance', '?')
        pnl = t.get('unrealized_pnl', '?')
        print(f'  - {sym}: {bal} (PnL: {pnl})')
else:
    print('Holdings: None (empty wallet)')
"


path: scripts/setup.py
content: #!/usr/bin/env python3
"""
KryptoGO Meme Trader - Agent Wallet Setup

This script:
  1. Checks Python 3.10+ and installs required packages (solders, requests)
  2. Generates a Solana keypair if SOLANA_PRIVATE_KEY is not already in .env
  3. Saves the private key (base58) and public key to .env
  4. Sets .env permissions to chmod 600
  5. Prints the public address for the user to fund

Usage:
  python3 scripts/setup.py            # Normal setup (preserves existing key)
  python3 scripts/setup.py --force    # Regenerate keypair (backs up old key)

SECURITY:
  - The private key is stored in .env with restricted permissions (600).
  - NEVER share, log, or commit the .env file.
  - The private key never leaves the local machine during trading.
"""

import argparse
import os
import shutil
import stat
import subprocess
import sys
from datetime import datetime, timezone

# ---------------------------------------------------------------------------
# 1. Check Python version
# ---------------------------------------------------------------------------

MIN_PYTHON = (3, 10)

if sys.version_info < MIN_PYTHON:
    sys.exit(
        f"ERROR: Python {MIN_PYTHON[0]}.{MIN_PYTHON[1]}+ is required. "
        f"You have Python {sys.version_info.major}.{sys.version_info.minor}."
    )

print(f"Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro} -- OK")

# ---------------------------------------------------------------------------
# 2. Parse CLI arguments
# ---------------------------------------------------------------------------

parser = argparse.ArgumentParser(
    description="KryptoGO Meme Trader - Agent Wallet Setup"
)
parser.add_argument(
    "--force",
    action="store_true",
    help="Force regenerate the Solana keypair even if one already exists. "
    "The old key is backed up to .env.backup.<timestamp> before overwriting.",
)
args = parser.parse_args()

# ---------------------------------------------------------------------------
# 3. Install required packages
# ---------------------------------------------------------------------------

REQUIRED_PACKAGES = ["solders", "requests"]


def install_packages():
    """Install missing packages."""
    for pkg in REQUIRED_PACKAGES:
        try:
            __import__(pkg)
            print(f"  {pkg} -- already installed")
        except ImportError:
            print(f"  {pkg} -- installing...")
            subprocess.check_call(
                [sys.executable, "-m", "pip", "install", pkg],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
            )
            print(f"  {pkg} -- installed")


print("\nChecking dependencies...")
install_packages()

# Now import after ensuring packages are installed
from solders.keypair import Keypair  # noqa: E402

# ---------------------------------------------------------------------------
# 4. Load or create .env
# ---------------------------------------------------------------------------

ENV_FILE = os.path.expanduser("~/.openclaw/workspace/.env")
# Store credentials in the OpenClaw workspace root — NOT the skill directory.
# This ensures heartbeat/cron sessions can find the .env automatically.


def load_env(path):
    """Load .env file into a dict, preserving order and comments."""
    lines = []
    values = {}
    if os.path.exists(path):
        with open(path) as f:
            for line in f:
                lines.append(line)
                stripped = line.strip()
                if stripped and not stripped.startswith("#") and "=" in stripped:
                    key, _, val = stripped.partition("=")
                    values[key.strip()] = val.strip()
    return lines, values


def save_env(path, lines):
    """Write .env lines back to file."""
    with open(path, "w") as f:
        f.writelines(lines)
    # Restrict permissions: owner read/write only
    os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)


print(f"\n.env location: {ENV_FILE}")
lines, env_values = load_env(ENV_FILE)

# ---------------------------------------------------------------------------
# 5. Check API key
# ---------------------------------------------------------------------------

[REDACTED_SECRET]"KRYPTOGO_API_KEY", "")
if not api_key or api_key == "sk_live_your_key_here":
    print(
        "\nWARNING: KRYPTOGO_API_KEY is not set in .env.\n"
        "  Get your API key at: https://www.kryptogo.xyz/account\n"
        "  Then add it to .env:  KRYPTOGO_API_KEY=sk_live_..."
    )
else:
    # Do not print any part of the key — even masked suffixes can leak info
    print("KRYPTOGO_API_KEY: set ✓")

# ---------------------------------------------------------------------------
# 6. Generate Solana keypair (or load existing one)
# ---------------------------------------------------------------------------

existing_private_key = env_values.get("SOLANA_PRIVATE_KEY", "")


def _derive_address(base58_private_key):
    """Reconstruct a Keypair from a base58-encoded private key and return the public address."""
    kp = Keypair.from_base58_string(base58_private_key)
    return str(kp.pubkey())


def _backup_env(env_path):
    """Copy the current .env to .env.backup.<UTC timestamp>."""
    ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
    backup_path = f"{env_path}.backup.{ts}"
    shutil.copy2(env_path, backup_path)
    # Preserve restricted permissions on the backup
    os.chmod(backup_path, stat.S_IRUSR | stat.S_IWUSR)
    return backup_path


def _write_keypair_to_env(new_private_key, new_public_key, env_lines, env_path):
    """Write a keypair into the .env file, updating existing entries or appending."""
    found_private = False
    found_public = False
    new_lines = []

    for line in env_lines:
        stripped = line.strip()
        if stripped.startswith("SOLANA_PRIVATE_KEY="):
            new_lines.append(f"SOLANA_PRIVATE_KEY={new_private_key}\n")
            found_private = True
        elif stripped.startswith("SOLANA_WALLET_ADDRESS="):
            new_lines.append(f"SOLANA_WALLET_ADDRESS={new_public_key}\n")
            found_public = True
        else:
            new_lines.append(line)

    if not found_private:
        new_lines.append(f"SOLANA_PRIVATE_KEY={new_private_key}\n")
    if not found_public:
        new_lines.append(f"SOLANA_WALLET_ADDRESS={new_public_key}\n")

    # If .env didn't exist at all, also add API key placeholder
    if not env_lines:
        new_lines.insert(0, "KRYPTOGO_API_KEY=sk_live_your_key_here\n")

    save_env(env_path, new_lines)


if existing_private_key and not args.force:
    # ---- Existing key detected: load it, derive address, skip generation ----
    derived_address = _derive_address(existing_private_key)

    # Ensure SOLANA_WALLET_ADDRESS is consistent (auto-heal if missing/stale)
    stored_address = env_values.get("SOLANA_WALLET_ADDRESS", "")
    if stored_address != derived_address:
        print(f"\nRepairing SOLANA_WALLET_ADDRESS (was: '{stored_address}', expected: '{derived_address}')")
        _write_keypair_to_env(existing_private_key, derived_address, lines, ENV_FILE)

    print(f"\nSolana wallet already configured: {derived_address}")
    print("  Private key: present in .env (unchanged)")
    print(f"\n  To regenerate, re-run with --force:")
    print(f"    python3 scripts/setup.py --force")

elif existing_private_key and args.force:
    # ---- Force regeneration: warn, back up, then create new key ----
    old_address = _derive_address(existing_private_key)
    print(
        f"\n{'='*60}\n"
        f"  WARNING: --force flag detected.\n"
        f"  This will REPLACE the existing Solana keypair.\n"
        f"  Old wallet address: {old_address}\n"
        f"\n"
        f"  ANY FUNDS on the old address will become INACCESSIBLE\n"
        f"  unless you restore from the backup.\n"
        f"{'='*60}"
    )

    # Back up current .env before overwriting
    backup_path = _backup_env(ENV_FILE)
    print(f"\n  Old .env backed up to: {backup_path}")

    keypair = Keypair()
    new_private_key = str(keypair)  # base58-encoded private key
    new_public_key = str(keypair.pubkey())

    _write_keypair_to_env(new_private_key, new_public_key, lines, ENV_FILE)

    print(f"\n  New private key: saved to .env (NEVER share this)")
    print(f"  New public address: {new_public_key}")
    print(f"\n  .env permissions set to 600 (owner read/write only)")
    print(f"\n  Fund this address with SOL to start trading:")
    print(f"    {new_public_key}")
    print(f"\n  To restore the old key, copy the backup back:")
    print(f"    cp '{backup_path}' '{ENV_FILE}'")

else:
    # ---- No existing key: first-time generation ----
    print("\nGenerating new Solana keypair...")
    keypair = Keypair()
    new_private_key = str(keypair)  # base58-encoded private key
    new_public_key = str(keypair.pubkey())

    _write_keypair_to_env(new_private_key, new_public_key, lines, ENV_FILE)

    print(f"  Private key: saved to .env (NEVER share this)")
    print(f"  Public address: {new_public_key}")
    print(f"\n  .env permissions set to 600 (owner read/write only)")
    print(f"\n  Fund this address with SOL to start trading:")
    print(f"    {new_public_key}")

# ---------------------------------------------------------------------------
# 7. Summary
# ---------------------------------------------------------------------------

print("\n--- Setup complete ---")
print("Next steps:")
print("  1. Fund the agent wallet with SOL (min 0.1 SOL for gas + trading capital)")
print("  2. Set KRYPTOGO_API_KEY in .env if not already done")
print("  3. Tell the agent to scan for opportunities, or set up heartbeat — see SKILL.md")


path: scripts/trending.sh
content: #!/usr/bin/env bash
# KryptoGO Meme Trader — Trending Token Scanner
#
# Lists top trending tokens by 24h volume on Solana.
#
# Usage:
#   bash scripts/trending.sh           # Default: top 10 by volume on Solana
#   bash scripts/trending.sh 20        # Top 20
#   bash scripts/trending.sh 10 56     # Top 10 on BSC (chain_id=56)

set -euo pipefail

# Credentials must be pre-loaded in the environment (source ~/.openclaw/workspace/.env)
if [ -z "${KRYPTOGO_API_KEY:-}" ]; then
  echo "ERROR: Run 'source ~/.openclaw/workspace/.env' before running this script."
  exit 1
fi

BASE="https://wallet-data.kryptogo.app"
AUTH="Authorization: Bearer $KRYPTOGO_API_KEY"
PAGE_SIZE="${1:-10}"
CHAIN_ID="${2:-501}"

curl -s "$BASE/agent/trending-tokens?chain_id=$CHAIN_ID&page_size=$PAGE_SIZE&sort_by=volume" -H "$AUTH" | python3 -c "
import sys, json
d = json.load(sys.stdin)
tokens = d.get('tokens', d.get('data', []))
for i, t in enumerate(tokens):
    sym = t.get('symbol', '?')
    mc = float(t.get('market_cap', 0) or 0)
    vol = float(t.get('volume_24h', t.get('volume', 0)) or 0)
    addr = t.get('contract_address', t.get('token_mint', '?'))
    print(f'{i+1:2d}. {sym:12s} mc=\${mc/1e6:.1f}M  vol24h=\${vol/1e6:.1f}M  {addr}')
"


path: scripts/cron_monitor.py
content: #!/usr/bin/env python3
"""
CRON SCRIPT: stop-loss-tp
Runs every 5 minutes to check portfolio for stop-loss or take-profit conditions.
"""

import sys
import os
import json
import subprocess

# Add parent directory to path so we can import 'examples.trading-workflow' or similar
# The trading-workflow.py is in ../examples/ relative to this script
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
EXAMPLES_DIR = os.path.join(os.path.dirname(SCRIPT_DIR), 'examples')
SWAP_SCRIPT_PATH = os.path.join(SCRIPT_DIR, 'swap.py')
WORKFLOW_PATH = os.path.join(EXAMPLES_DIR, "trading-workflow.py")

sys.path.append(EXAMPLES_DIR)

# Credentials must be pre-loaded in the environment (source ~/.openclaw/workspace/.env)
# Verify critical env vars
if not os.getenv("KRYPTOGO_API_KEY") or not os.getenv("SOLANA_WALLET_ADDRESS"):
    print("ERROR: Missing KRYPTOGO_API_KEY or SOLANA_WALLET_ADDRESS. Run 'source ~/.openclaw/workspace/.env' first.")
    sys.exit(1)

# Monkey-patch safe_execute_trade to use swap.py
def patched_safe_execute_trade(input_mint, output_mint, amount, slippage_bps=300):
    """
    Patched execution using external swap.py script to avoid signer mismatch.
    """
    print(f"--- PATCHED EXECUTION: Calling swap.py ---")
    
    SOL_MINT = [REDACTED_SECRET]
    cmd = ["python3", SWAP_SCRIPT_PATH]
    
    if input_mint == SOL_MINT:
        # BUY
        token_mint = output_mint
        amount_sol = amount / 1_000_000_000
        cmd.extend([token_mint, str(amount_sol)])
        print(f"Executing BUY: {amount_sol} SOL -> {token_mint}")
        
    elif output_mint == SOL_MINT:
        # SELL
        token_mint = input_mint
        # For sell, swap.py expects amount in SOL if buying, or token units if selling?
        # Let's check swap.py again.
        # It takes "amount" as a float.
        # If --sell is present: amount = int(args.amount * (10 ** decimals))
        # So args.amount MUST be human readable float.
        
        # But 'amount' passed here from trading-workflow.py is RAW INTEGER (lamports/units).
        # We need to convert it back to human readable float.
        
        try:
            import requests
            headers = {"Authorization": f"Bearer {os.getenv('KRYPTOGO_API_KEY')}"}
            resp = requests.get(
                f"https://wallet-data.kryptogo.app/token-overview",
                params={"address": token_mint},
                headers=headers
            )
            data = resp.json()
            decimals = data.get("decimals")
            if decimals is None:
                print(f"ABORT: Could not determine decimals for {token_mint}. Refusing to sell with unknown precision.")
                return None
            human_amount = amount / (10 ** decimals)

            cmd.extend([token_mint, str(human_amount), "--sell"])
            print(f"Executing SELL: {human_amount} tokens ({amount} raw) -> SOL")

        except Exception as e:
            print(f"ABORT: Failed to fetch decimals for sell: {e}.")
            return None

    else:
        print("Error: swap.py only supports SOL pairs.")
        return None

    cmd.extend(["--slippage", str(slippage_bps)])
    
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, check=True)
        print("swap.py output:", result.stdout)
        return {"status": "success", "explorer_url": "check_swap_py_logs", "tx_hash": "check_swap_py_logs"}
        
    except subprocess.CalledProcessError as e:
        print("swap.py failed:", e.stderr)
        return None

try:
    # Import the trading workflow module
    import importlib.util
    spec = importlib.util.spec_from_file_location("trading_workflow", WORKFLOW_PATH)
    tw = importlib.util.module_from_spec(spec)
    sys.modules["trading_workflow"] = tw
    
    # APPLY PATCH BEFORE LOAD
    tw.safe_execute_trade = patched_safe_execute_trade
    
    spec.loader.exec_module(tw)
    
    # RE-APPLY PATCH AFTER LOAD (just in case)
    tw.safe_execute_trade = patched_safe_execute_trade
    
    # 1. Check Portfolio & Monitor Positions
    print("Checking portfolio for stop-loss / take-profit conditions...")
    actions = tw.monitor_positions()
    
    if not actions:
        print("No actions needed. All positions within thresholds.")
        sys.exit(0)

    # 2. Execute Actions
    for action in actions:
        mint = action["mint"]
        symbol = action["symbol"]
        act_type = action["action"]
        reason = action["reason"]
        
        print(f"Action triggered: {act_type} {symbol} ({reason})")
        
        # We need pnl_pct and holding_hours for logging
        portfolio = tw.get_portfolio(os.getenv("SOLANA_WALLET_ADDRESS"))
        token_data = next((t for t in portfolio.get("tokens", []) if t["mint"] == mint), None)
        
        pnl_pct = 0.0
        pnl_sol = 0.0
        holding_hours = 0.0
        
        if token_data:
            # Recalculate PnL/holding metrics for the log
            unrealized_pnl = float(token_data.get("unrealized_pnl", "0"))
            avg_cost = float(token_data.get("holding_avg_cost", "0"))
            balance = float(token_data.get("balance", "0"))
            cost_basis = avg_cost * balance
            if cost_basis > 0:
                pnl_pct = (unrealized_pnl / cost_basis * 100)
            pnl_sol = unrealized_pnl # assuming raw value is USD or SOL? API usually returns USD for PnL
            holding_hours = int(float(token_data.get("avg_holding_seconds", "0"))) / 3600

        if act_type in ["SELL", "TAKE_PROFIT"]:
            print(f"Executing SELL for {symbol}...")
            # execute_exit calls safe_execute_trade, which is now patched!
            result = tw.execute_exit(mint, symbol, act_type, reason, pnl_pct, pnl_sol, holding_hours)
            if result:
                print(f"SUCCESS: Sold {symbol}. Tx: {result.get('tx_hash', 'N/A')}")
            else:
                print(f"FAILED: Could not execute sell for {symbol}.")
        
        elif act_type == "REDUCE":
            print(f"Recommendation: REDUCE {symbol} (Distribution detected). Auto-reduce not implemented yet.")
            
        elif act_type == "REVIEW":
            print(f"Recommendation: REVIEW {symbol} (Stale position > 24h).")

except Exception as e:
    print(f"CRITICAL ERROR in cron_monitor: {e}")
    sys.exit(1)


path: scripts/cron_scan.py
content: #!/usr/bin/env python3
"""
CRON SCRIPT: discovery-scan
Runs every 30 minutes to discover and analyze new tokens.
"""

import sys
import os
import importlib.util
import subprocess

# Define paths
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
# skill root is parent of scripts/
SKILL_ROOT = os.path.dirname(SCRIPT_DIR)
EXAMPLES_DIR = os.path.join(SKILL_ROOT, 'examples')
WORKFLOW_PATH = os.path.join(EXAMPLES_DIR, 'trading-workflow.py')
SWAP_SCRIPT_PATH = os.path.join(SCRIPT_DIR, 'swap.py')

# Add examples dir to sys.path to allow imports if needed
sys.path.append(EXAMPLES_DIR)

# Credentials must be pre-loaded in the environment (source ~/.openclaw/workspace/.env)
# Verify critical env vars
if not os.getenv("KRYPTOGO_API_KEY") or not os.getenv("SOLANA_WALLET_ADDRESS"):
    print("ERROR: Missing KRYPTOGO_API_KEY or SOLANA_WALLET_ADDRESS. Run 'source ~/.openclaw/workspace/.env' first.")
    sys.exit(1)

if not os.path.exists(WORKFLOW_PATH):
    print(f"ERROR: Workflow script not found at {WORKFLOW_PATH}")
    sys.exit(1)

# Monkey-patch safe_execute_trade to use swap.py
def patched_safe_execute_trade(input_mint, output_mint, amount, slippage_bps=300):
    """
    Patched execution using external swap.py script to avoid signer mismatch.
    """
    print(f"--- PATCHED EXECUTION: Calling swap.py ---")
    
    # Determine if BUY or SELL
    # Input SOL -> Output Token = BUY
    # Input Token -> Output SOL = SELL
    
    SOL_MINT = [REDACTED_SECRET]
    
    cmd = ["python3", SWAP_SCRIPT_PATH]
    
    if input_mint == SOL_MINT:
        # BUY: output_mint is the token to buy
        token_mint = output_mint
        # Amount passed to safe_execute_trade is usually in Lamports for SOL
        # swap.py expects SOL (float) for BUY
        amount_sol = amount / 1_000_000_000
        
        cmd.extend([token_mint, str(amount_sol)])
        print(f"Executing BUY via swap.py: {amount_sol} SOL -> {token_mint}")
        
    elif output_mint == SOL_MINT:
        # SELL: input_mint is the token to sell
        token_mint = input_mint
        
        # Amount passed to safe_execute_trade is in RAW UNITS (integer)
        # swap.py with --sell expects HUMAN READABLE units (float)
        # We must fetch decimals to convert.
        
        try:
            import requests
            headers = {"Authorization": f"Bearer {os.environ.get('KRYPTOGO_API_KEY')}"}
            # Use quicknode or kryptogo api to get decimals
            # We'll try KryptoGO token-overview first as it's used elsewhere
            print(f"Fetching decimals for {token_mint} to prepare SELL...")
            resp = requests.get(
                "https://wallet-data.kryptogo.app/token-overview",
                params={"address": token_mint},
                headers=headers,
                timeout=10
            )
            data = resp.json()
            decimals = data.get("decimals")
            if decimals is None:
                print(f"ABORT: Could not determine decimals for {token_mint}. Refusing to sell with unknown precision.")
                return None

            # Convert raw amount to human readable
            human_amount = amount / (10 ** decimals)

            cmd.extend([token_mint, str(human_amount), "--sell"])
            print(f"Executing SELL via swap.py: {human_amount} tokens ({amount} raw) -> SOL")

        except Exception as e:
            print(f"ABORT: Failed to fetch decimals for sell: {e}.")
            return None

    else:
        print("Error: swap.py only supports SOL pairs.")
        return None

    # Add slippage
    cmd.extend(["--slippage", str(slippage_bps)])
    
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, check=True)
        print("swap.py output:", result.stdout)
        
        # Parse output for explorer URL or fake a return object that trading-workflow expects
        # trading-workflow expects a dict with 'explorer_url' or similar
        # safe_execute_trade returns submit_transaction response (json)
        
        return {"status": "success", "explorer_url": "check_swap_py_logs", "tx_hash": "check_swap_py_logs"}
        
    except subprocess.CalledProcessError as e:
        print("swap.py failed:", e.stderr)
        return None

try:
    # Import the trading workflow module dynamically
    spec = importlib.util.spec_from_file_location("trading_workflow", WORKFLOW_PATH)
    tw = importlib.util.module_from_spec(spec)
    sys.modules["trading_workflow"] = tw
    
    # APPLY PATCH
    tw.safe_execute_trade = patched_safe_execute_trade
    
    spec.loader.exec_module(tw)
    
    # Run Discovery Pipeline
    print(f"Starting discovery scan using logic from {WORKFLOW_PATH}...")
    # NOTE: We re-patch again just in case exec_module overwrote it (unlikely but safe)
    tw.safe_execute_trade = patched_safe_execute_trade
    
    result = tw.discover_and_analyze()
    
    if result:
        print("Discovery scan completed with a trade execution.")
    else:
        print("Discovery scan completed. No trades executed.")

except Exception as e:
    print(f"CRITICAL ERROR in cron_scan: {e}")
    import traceback
    traceback.print_exc()
    sys.exit(1)


path: scripts/analysis.sh
content: #!/usr/bin/env bash
# KryptoGO Meme Trader — Full Analysis Dashboard
#
# Combines portfolio check, signal dashboard, trending tokens, and
# top candidate scoring into a single view. Useful for periodic scans
# or quick market assessment.
#
# Usage:
#   bash scripts/analysis.sh

set -euo pipefail

# Credentials must be pre-loaded in the environment (source ~/.openclaw/workspace/.env)
if [ -z "${KRYPTOGO_API_KEY:-}" ]; then
  echo "ERROR: Run 'source ~/.openclaw/workspace/.env' before running this script."
  exit 1
fi

BASE="https://wallet-data.kryptogo.app"
AUTH="Authorization: Bearer $KRYPTOGO_API_KEY"

echo "========================================="
echo "  KryptoGO Meme Trader - Full Analysis"
echo "========================================="
echo ""

# 1. Portfolio check
echo "=== PORTFOLIO ==="
curl -s "$BASE/agent/portfolio?wallet_address=$SOLANA_WALLET_ADDRESS" -H "$AUTH" | python3 -c "
import sys, json
d = json.load(sys.stdin)
print(f\"Wallet: {d.get('wallet','?')}\")
print(f\"SOL Balance: {d.get('sol_balance','0')} SOL\")
print(f\"Realized PnL: {d.get('total_realized_pnl','0')}\")
print(f\"Unrealized PnL: {d.get('total_unrealized_pnl','0')}\")
tokens = d.get('tokens', [])
if tokens:
    print(f'Holdings: {len(tokens)} tokens')
    for t in tokens[:5]:
        print(f\"  - {t.get('symbol','?')}: {t.get('balance','?')} (PnL: {t.get('unrealized_pnl','?')})\")
else:
    print('Holdings: None (empty wallet)')
" 2>&1
echo ""

# 2. Signal Dashboard (Pro/Alpha tier — will 403 on Free tier)
echo "=== SIGNAL DASHBOARD (Accumulation Signals) ==="
curl -s "$BASE/signal-dashboard" -H "$AUTH" | python3 -c "
import sys, json
d = json.load(sys.stdin)
signals = d.get('signals', [])
print(f'Total signals: {len(signals)}')
print('')
for s in signals[:15]:
    sym = s.get('symbol', '?')
    chain = s.get('chain', '?')
    pct = s.get('price_change_percent', 0)
    max_pct = s.get('max_price_change_percent', 0)
    pattern = s.get('pattern', '?')
    cr_at = s.get('cluster_ratio_at_signal')
    cr_now = s.get('current_cluster_ratio')
    addr = s.get('contract_address', '?')
    direction = '+' if pct >= 0 else ''
    cr_str = f'CR: {cr_at:.1f}%→{cr_now:.1f}%' if cr_at and cr_now else 'CR: N/A'
    print(f'  {sym:12s} [{chain}] {pattern:12s} {direction}{pct:.0f}% (max {direction}{max_pct:.0f}%)  {cr_str}  {addr[:16]}...')
" 2>&1
echo ""

# 3. Trending tokens
echo "=== TRENDING TOKENS (by volume) ==="
curl -s "$BASE/agent/trending-tokens?chain_id=501&page_size=10&sort_by=volume" -H "$AUTH" | python3 -c "
import sys, json
d = json.load(sys.stdin)
tokens = d.get('tokens', d.get('data', []))
for i, t in enumerate(tokens[:10]):
    sym = t.get('symbol', '?')
    mc = float(t.get('market_cap', 0) or 0)
    vol = float(t.get('volume_24h', t.get('volume', 0)) or 0)
    addr = t.get('contract_address', t.get('token_mint', '?'))
    print(f'{i+1:2d}. {sym:12s} mc=\${mc/1e6:.1f}M  vol24h=\${vol/1e6:.1f}M  {addr}')
" 2>&1
echo ""

# 4. Top candidates from signals (scored by cluster ratio growth)
echo "=== TOP CANDIDATES FOR ANALYSIS ==="
curl -s "$BASE/signal-dashboard" -H "$AUTH" | python3 -c "
import sys, json
d = json.load(sys.stdin)
signals = d.get('signals', [])

candidates = []
for s in signals:
    pct = s.get('price_change_percent', 0)
    max_pct = s.get('max_price_change_percent', 0)
    cr_at = s.get('cluster_ratio_at_signal')
    cr_now = s.get('current_cluster_ratio')

    cr_growth = (cr_now - cr_at) if cr_at and cr_now and cr_now > cr_at else 0

    candidates.append({
        'symbol': s.get('symbol', '?'),
        'address': s.get('contract_address', '?'),
        'chain': s.get('chain', '?'),
        'pct': pct,
        'max_pct': max_pct,
        'cr_at': cr_at,
        'cr_now': cr_now,
        'cr_growth': cr_growth,
        'pattern': s.get('pattern', '?'),
    })

candidates.sort(key=lambda x: x['cr_growth'], reverse=True)

print('Top 5 by cluster ratio growth (strongest accumulation):')
for i, c in enumerate(candidates[:5]):
    cr_str = f'CR: {c[\"cr_at\"]:.1f}%→{c[\"cr_now\"]:.1f}% (+{c[\"cr_growth\"]:.1f}pp)' if c['cr_at'] and c['cr_now'] else 'CR: N/A'
    print(f'  {i+1}. {c[\"symbol\"]:12s} [{c[\"chain\"]}] price: {c[\"pct\"]:+.0f}% (max: +{c[\"max_pct\"]:.0f}%)  {cr_str}  {c[\"address\"]}')
print()

# Early-stage tokens (price near signal, CR growing)
early = [c for c in candidates if -20 < c['pct'] < 50 and c['cr_growth'] > 0]
early.sort(key=lambda x: x['cr_growth'], reverse=True)
if early:
    print('Early stage (price near signal entry, CR still growing):')
    for i, c in enumerate(early[:5]):
        cr_str = f'CR: {c[\"cr_at\"]:.1f}%→{c[\"cr_now\"]:.1f}% (+{c[\"cr_growth\"]:.1f}pp)' if c['cr_at'] and c['cr_now'] else 'CR: N/A'
        print(f'  {i+1}. {c[\"symbol\"]:12s} [{c[\"chain\"]}] price: {c[\"pct\"]:+.0f}%  {cr_str}  {c[\"address\"]}')
" 2>&1
echo ""

echo "=== DONE ==="


path: scripts/trading-preferences.example.json
content: {
  "max_position_size": 0.1,
  "stop_loss_pct": 50,
  "take_profit_pct": 200,
  "min_market_cap": 100000,
  "scan_count": 10,
  "risk_tolerance": "aggressive",
  "chains": ["solana"],
  "max_open_positions": 5,
  "max_daily_trades": 10
}


path: scripts/test-api.sh
content: #!/usr/bin/env bash
# KryptoGO Meme Trader — API Connectivity Test
#
# Tests key API endpoints to verify authentication and connectivity.
#
# Usage:
#   bash scripts/test-api.sh

set -euo pipefail

# Credentials must be pre-loaded in the environment (source ~/.openclaw/workspace/.env)
if [ -z "${KRYPTOGO_API_KEY:-}" ]; then
  echo "ERROR: Run 'source ~/.openclaw/workspace/.env' before running this script."
  exit 1
fi

BASE="https://wallet-data.kryptogo.app"
AUTH="Authorization: Bearer $KRYPTOGO_API_KEY"
PASS=0
FAIL=0

test_endpoint() {
    local name="$1"
    local url="$2"
    local code
    code=$(curl -s -o /dev/null -w "%{http_code}" "$url" -H "$AUTH")
    if [ "$code" = "200" ]; then
        echo "  ✓ $name (HTTP $code)"
        PASS=$((PASS + 1))
    else
        echo "  ✗ $name (HTTP $code)"
        FAIL=$((FAIL + 1))
    fi
}

echo "=== KryptoGO API Connectivity Test ==="
echo ""

test_endpoint "Account & Tier"     "$BASE/agent/account"
test_endpoint "Trending Tokens"    "$BASE/agent/trending-tokens?chain_id=501&page_size=1&sort_by=volume"
test_endpoint "Portfolio"          "$BASE/agent/portfolio?wallet_address=$SOLANA_WALLET_ADDRESS"
test_endpoint "Signal Dashboard"   "$BASE/signal-dashboard"

echo ""
echo "Results: $PASS passed, $FAIL failed"


path: examples/trading-workflow.py
content: """
KryptoGO Meme Trader - Reference Trading Workflow

This script demonstrates the full trading workflow:
  discover signal -> analyze token -> assess risk -> execute trade -> monitor position

Requirements:
  - Python 3.10+
  - pip install requests solders

Environment variables (stored in .env, never logged or printed):
  - KRYPTOGO_API_KEY: Your KryptoGO Agent API key
  - SOLANA_PRIVATE_KEY: Base58-encoded Solana private key
  - SOLANA_WALLET_ADDRESS: Agent wallet public address

SECURITY WARNING:
  - NEVER log, print, or expose SOLANA_PRIVATE_KEY in any output.
  - NEVER commit .env to version control.
  - Signing happens locally; the private key is never sent to any server.
"""

import json
import os
import sys
import time
import base64

import requests
from solders.keypair import Keypair
from solders.transaction import VersionedTransaction

# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------

API_BASE = "https://wallet-data.kryptogo.app"
[REDACTED_SECRET]"KRYPTOGO_API_KEY")
PRIVATE_KEY = os.environ.get("SOLANA_PRIVATE_KEY")  # NEVER log this value
WALLET_ADDRESS = os.environ.get("SOLANA_WALLET_ADDRESS")

if not [REDACTED_SECRET]"ERROR: KRYPTOGO_API_KEY environment variable is not set.")
if not PRIVATE_KEY:
    sys.exit("ERROR: SOLANA_PRIVATE_KEY environment variable is not set.")
if not WALLET_ADDRESS:
    sys.exit("ERROR: SOLANA_WALLET_ADDRESS environment variable is not set.")

HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

SOL_MINT = [REDACTED_SECRET]
LAMPORTS_PER_SOL = 1_000_000_000

# ---------------------------------------------------------------------------
# User Preferences (load from memory/trading-preferences.json if available)
# ---------------------------------------------------------------------------

DEFAULT_PREFERENCES = {
    "max_position_size": 0.1,       # Max SOL per trade
    "stop_loss_pct": 30,            # Stop loss percentage
    "take_profit_pct": 100,         # Take profit percentage
    "min_market_cap": 500_000,      # Minimum market cap filter (USD)
    "scan_count": 10,               # Trending tokens per scan
    "risk_tolerance": "moderate",   # conservative / moderate / aggressive
    "chains": ["solana"],           # Chains to scan
}


def load_preferences(path="memory/trading-preferences.json"):
    """Load user trading preferences, falling back to defaults."""
    prefs = dict(DEFAULT_PREFERENCES)
    if os.path.exists(path):
        with open(path) as f:
            prefs.update(json.load(f))
    return prefs


# ---------------------------------------------------------------------------
# Trade Journal — Learning & Adaptation
# ---------------------------------------------------------------------------

JOURNAL_PATH = "memory/trading-journal.json"
LESSONS_PATH = "memory/trading-lessons.md"
REVIEWS_DIR = "memory/strategy-reviews"


def _load_journal():
    """Load trade journal from disk."""
    if os.path.exists(JOURNAL_PATH):
        with open(JOURNAL_PATH) as f:
            return json.load(f)
    return {"trades": []}


def _save_journal(journal):
    """Save trade journal to disk."""
    os.makedirs(os.path.dirname(JOURNAL_PATH), exist_ok=True)
    with open(JOURNAL_PATH, "w") as f:
        json.dump(journal, f, indent=2)


def log_trade_entry(token_mint, symbol, action, amount_sol, token_amount,
                    price, market_cap, entry_reasoning, chain_id="501"):
    """Log a new trade (BUY) to the journal. Call immediately after execution."""
    journal = _load_journal()
    ts = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
    trade_id = f"{ts}_{symbol}"

    entry = {
        "id": trade_id,
        "token_mint": token_mint,
        "symbol": symbol,
        "chain_id": chain_id,
        "action": action,
        "amount_sol": amount_sol,
        "token_amount": token_amount,
        "price_at_entry": price,
        "market_cap_at_entry": market_cap,
        "timestamp": ts,
        "entry_reasoning": entry_reasoning,
        "outcome": None,
    }
    journal["trades"].append(entry)
    _save_journal(journal)
    return trade_id


def log_trade_exit(trade_id, exit_price, exit_reason, pnl_sol, pnl_pct,
                   holding_hours, cluster_ratio_at_exit, lesson):
    """Update a journal entry with exit outcome. Call when a position is closed."""
    journal = _load_journal()
    for trade in journal["trades"]:
        if trade["id"] == trade_id:
            trade["outcome"] = {
                "exit_price": exit_price,
                "exit_timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
                "exit_reason": exit_reason,
                "pnl_sol": pnl_sol,
                "pnl_pct": pnl_pct,
                "holding_duration_hours": holding_hours,
                "cluster_ratio_at_exit": cluster_ratio_at_exit,
                "lesson": lesson,
            }
            break
    _save_journal(journal)

    # If loss >20%, append to lessons file
    if pnl_pct < -20:
        _log_lesson(trade_id, pnl_pct, lesson, exit_reason)


def _log_lesson(trade_id, pnl_pct, lesson, loss_type):
    """Append a loss post-mortem to trading-lessons.md."""
    os.makedirs(os.path.dirname(LESSONS_PATH), exist_ok=True)
    ts = time.strftime("%Y-%m-%d", time.gmtime())
    entry = (
        f"\n## {ts} — {trade_id} (PnL: {pnl_pct:+.1f}%)\n\n"
        f"- **Type:** {loss_type}\n"
        f"- **Lesson:** {lesson}\n"
    )
    with open(LESSONS_PATH, "a") as f:
        f.write(entry)


def get_journal_stats():
    """Calculate aggregate trading statistics for strategy review."""
    journal = _load_journal()
    closed = [t for t in journal["trades"] if t.get("outcome")]

    if not closed:
        return None

    wins = [t for t in closed if t["outcome"]["pnl_pct"] > 0]
    losses = [t for t in closed if t["outcome"]["pnl_pct"] <= 0]

    avg_win = sum(t["outcome"]["pnl_pct"] for t in wins) / len(wins) if wins else 0
    avg_loss = sum(t["outcome"]["pnl_pct"] for t in losses) / len(losses) if losses else 0
    avg_hold_win = sum(t["outcome"]["holding_duration_hours"] for t in wins) / len(wins) if wins else 0
    avg_hold_loss = sum(t["outcome"]["holding_duration_hours"] for t in losses) / len(losses) if losses else 0

    # Win rate by signal source
    source_stats = {}
    for t in closed:
        src = t.get("entry_reasoning", {}).get("signal_source", "unknown")
        if src not in source_stats:
            source_stats[src] = {"wins": 0, "total": 0}
        source_stats[src]["total"] += 1
        if t["outcome"]["pnl_pct"] > 0:
            source_stats[src]["wins"] += 1

    # Loss type distribution
    loss_types = {}
    for t in losses:
        lt = t["outcome"].get("exit_reason", "unknown")
        loss_types[lt] = loss_types.get(lt, 0) + 1

    return {
        "total_trades": len(closed),
        "win_rate": len(wins) / len(closed) * 100,
        "avg_win_pct": avg_win,
        "avg_loss_pct": avg_loss,
        "avg_hold_hours_win": avg_hold_win,
        "avg_hold_hours_loss": avg_hold_loss,
        "total_pnl_sol": sum(t["outcome"]["pnl_sol"] for t in closed),
        "source_win_rates": {
            src: s["wins"] / s["total"] * 100
            for src, s in source_stats.items()
        },
        "loss_type_counts": loss_types,
        "best_trade": max(closed, key=lambda t: t["outcome"]["pnl_pct"]),
        "worst_trade": min(closed, key=lambda t: t["outcome"]["pnl_pct"]),
    }


def save_strategy_review(stats, insights, proposals):
    """Save a periodic strategy review to memory/strategy-reviews/."""
    os.makedirs(REVIEWS_DIR, exist_ok=True)
    date = time.strftime("%Y-%m-%d", time.gmtime())
    path = os.path.join(REVIEWS_DIR, f"{date}.md")

    content = f"# Strategy Review — {date}\n\n"
    content += "## Performance Summary\n\n"
    content += f"- Total trades: {stats['total_trades']}\n"
    content += f"- Win rate: {stats['win_rate']:.1f}%\n"
    content += f"- Avg win: {stats['avg_win_pct']:+.1f}% | Avg loss: {stats['avg_loss_pct']:+.1f}%\n"
    content += f"- Total PnL: {stats['total_pnl_sol']:+.4f} SOL\n"
    content += f"- Avg hold (wins): {stats['avg_hold_hours_win']:.1f}h | (losses): {stats['avg_hold_hours_loss']:.1f}h\n\n"

    content += "## Win Rate by Source\n\n"
    for src, rate in stats["source_win_rates"].items():
        content += f"- {src}: {rate:.0f}%\n"

    content += "\n## Insights\n\n"
    for insight in insights:
        content += f"- {insight}\n"

    content += "\n## Proposed Changes\n\n"
    for proposal in proposals:
        content += f"- [ ] {proposal}\n"

    content += "\n---\n*Generated by kryptogo-meme-trader agent*\n"

    with open(path, "w") as f:
        f.write(content)
    return path


PREFERENCES = load_preferences()

# ---------------------------------------------------------------------------
# Analysis helpers
# ---------------------------------------------------------------------------


def get_token_overview(token_address):
    """Get token metadata: name, price, market cap, holders, risk level."""
    resp = requests.get(
        f"{API_BASE}/token-overview",
        params={"address": token_address},
        headers=HEADERS,
    )
    resp.raise_for_status()
    return resp.json()


def analyze_token(token_mint):
    """Get full cluster analysis: clusters, top holders, address metadata."""
    resp = requests.get(f"{API_BASE}/analyze/{token_mint}", headers=HEADERS)
    resp.raise_for_status()
    return resp.json()


def get_cluster_changes(token_mint, include_top_holders=True):
    """Get cluster holding ratio and changes across time periods."""
    resp = requests.get(
        f"{API_BASE}/analyze-cluster-change/{token_mint}",
        params={"include_top_holders": str(include_top_holders).lower()},
        headers=HEADERS,
    )
    resp.raise_for_status()
    return resp.json()


def get_wallet_labels(token_mint, wallets):
    """Get behavior labels for wallets: smart_money, whale, blue_chip_profit, high_frequency."""
    resp = requests.post(
        f"{API_BASE}/wallet-labels",
        json={"token_mint": token_mint, "wallets": wallets},
        headers=HEADERS,
    )
    resp.raise_for_status()
    return resp.json()


def get_token_wallet_labels(token_mint):
    """Get token-specific labels: developer, sniper, bundle, new_wallet."""
    resp = requests.post(
        f"{API_BASE}/token-wallet-labels",
        json={"token_mint": token_mint},
        headers=HEADERS,
    )
    resp.raise_for_status()
    return resp.json()


# ---------------------------------------------------------------------------
# Risk assessment
# ---------------------------------------------------------------------------


def assess_sell_pressure(token_mint, clusters, token_labels):
    """
    Evaluate sell pressure from low-cost position holders.

    Returns risk_level: 'low', 'medium', 'high'
    """
    risky_labels = {"developer", "sniper", "bundle", "new_wallet"}
    risky_addresses = set()

    label_data = token_labels.get("data", {})
    for address, labels in label_data.items():
        for label in labels:
            if label["label_type"] in risky_labels:
                risky_addresses.add(address)

    total_risky_balance = 0
    total_cluster_balance = 0

    for cluster in clusters.get("clusters", []):
        cluster_balance = float(cluster["total_balance"])
        total_cluster_balance += cluster_balance
        for wallet in cluster["wallets"]:
            if wallet["address"] in risky_addresses:
                total_risky_balance += float(wallet["token_balance"])

    if total_cluster_balance == 0:
        return "low"

    risky_ratio = total_risky_balance / total_cluster_balance

    if risky_ratio > 0.3:
        return "high"    # >30% of cluster holdings are low-cost tokens
    elif risky_ratio > 0.1:
        return "medium"  # 10-30% -- some risk
    else:
        return "low"     # <10% -- low-cost tokens mostly cleared


# ---------------------------------------------------------------------------
# Trading helpers
# ---------------------------------------------------------------------------


def sign_transaction(unsigned_tx_b64, private_key_b58):
    """
    Sign a base64-encoded unsigned V0 transaction.

    SECURITY: private_key_b58 is used locally only -- never sent over the network.
    """
    tx_bytes = base64.b64decode(unsigned_tx_b64)
    tx = VersionedTransaction.from_bytes(tx_bytes)
    keypair = Keypair.from_base58_string(private_key_b58)
    signed_tx = VersionedTransaction(tx.message, [keypair])
    return base64.b64encode(bytes(signed_tx)).decode()


def build_swap(input_mint, output_mint, amount, slippage_bps=300):
    """Build an unsigned swap transaction via the DEX aggregator.

    Always passes wallet_address so the returned transaction uses the agent's
    wallet as fee payer / signer (required for local signing).
    """
    resp = requests.post(
        f"{API_BASE}/agent/swap",
        headers=HEADERS,
        json={
            "input_mint": input_mint,
            "output_mint": output_mint,
            "amount": amount,
            "slippage_bps": slippage_bps,
            "wallet_address": WALLET_ADDRESS,
        },
    )
    resp.raise_for_status()
    data = resp.json()

    # Verify the transaction was built for our wallet
    if data.get("fee_payer") and data["fee_payer"] != WALLET_ADDRESS:
        raise ValueError(
            f"Transaction fee_payer mismatch: expected {WALLET_ADDRESS}, "
            f"got {data['fee_payer']}. Do not sign this transaction."
        )

    return data


def submit_transaction(signed_tx_b64):
    """Submit a signed transaction to the Solana network."""
    resp = requests.post(
        f"{API_BASE}/agent/submit",
        headers=HEADERS,
        json={"signed_transaction": signed_tx_b64},
    )
    resp.raise_for_status()
    return resp.json()


def safe_execute_trade(input_mint, output_mint, amount, slippage_bps=300):
    """Execute a trade with error handling and price impact validation."""
    try:
        swap = build_swap(input_mint, output_mint, amount, slippage_bps)
    except requests.HTTPError as e:
        if e.response.status_code == 402:
            data = e.response.json()
            print(f"Quota exceeded. Limit: {data.get('daily_limit')}, Tier: {data.get('tier')}")
            return None
        if e.response.status_code == 502:
            print("External service error. Retrying in 10s...")
            time.sleep(10)
            try:
                swap = build_swap(input_mint, output_mint, amount, slippage_bps)
            except requests.HTTPError:
                print("Retry failed.")
                return None
        else:
            raise

    price_impact = float(swap["quote"]["price_impact_pct"])
    if price_impact > 10.0:
        print(f"DANGER: Price impact {price_impact}%. Aborting.")
        return None
    if price_impact > 5.0:
        print(f"WARNING: Price impact {price_impact}%. Proceeding with caution.")

    # SECURITY: private key is used locally for signing, never sent to any server
    try:
        signed_tx = sign_transaction(swap["transaction"], PRIVATE_KEY)
        return submit_transaction(signed_tx)
    except requests.HTTPError as e:
        if e.response.status_code == 502:
            print("Failed to send transaction to Solana network.")
        else:
            raise
        return None


# ---------------------------------------------------------------------------
# Portfolio
# ---------------------------------------------------------------------------


def get_portfolio(wallet_address=None):
    """
    Fetch portfolio state.

    The API key is tied to your KryptoGO account (for billing and tier).
    The agent trades with its OWN wallet (generated during setup), separate
    from the wallet you used to log into kryptogo.xyz.
    Always pass the agent's wallet address.
    """
    params = {}
    if wallet_address:
        params["wallet_address"] = wallet_address
    resp = requests.get(f"{API_BASE}/agent/portfolio", headers=HEADERS, params=params)
    resp.raise_for_status()
    return resp.json()


# ---------------------------------------------------------------------------
# Discovery
# ---------------------------------------------------------------------------


def scan_trending_tokens(chain_id="501", sort_by="5", period="2", **filters):
    """
    Scan for trending tokens. Returns list of tokens sorted by the chosen metric.

    sort_by: 1=mcap, 2=holders, 3=liquidity, 4=txCount, 5=volume, 6=change
    period:  1=5min, 2=1h, 3=4h, 4=24h
    filters: marketCapMin, marketCapMax, holdersMin, volumeMin, tokenAgeMax, tokenAgeType, etc.
    """
    scan_count = PREFERENCES.get("scan_count", 10)
    params = {
        "chain_id": chain_id,
        "sort_by": sort_by,
        "period": period,
        "page_size": str(scan_count),
        **filters,
    }
    resp = requests.get(f"{API_BASE}/agent/trending-tokens", headers=HEADERS, params=params)
    resp.raise_for_status()
    return resp.json()["tokens"]


# ---------------------------------------------------------------------------
# Main pipeline
# ---------------------------------------------------------------------------


def check_rug_risk(token_mint):
    """
    Check for rug pull risks using RugCheck.xyz API.
    Returns: (is_safe: bool, reason: str)
    """
    try:
        url = f"https://api.rugcheck.xyz/v1/tokens/{token_mint}/report/summary"
        resp = requests.get(url, timeout=2)
        
        if resp.status_code != 200:
            print(f"Warning: RugCheck API unavailable ({resp.status_code}). Proceeding with caution.")
            return True, "API_UNAVAILABLE" 

        data = resp.json()
        
        # 1. Check Risk Score
        score = data.get("score", 0)
        # 5000 is a high threshold, many legit memes are 1000-3000 initially
        if score > 5000:
            return False, f"High Risk Score: {score}"

        # 2. Check Specific Danger Risks
        risks = data.get("risks", [])
        danger_signals = []
        for r in risks:
            if r.get("level") == "danger":
                name = r.get("name", "")
                # Critical rug indicators that are instant rejects
                if "Mint Authority" in name:
                    danger_signals.append("Mint Authority Enabled")
                elif "Freeze Authority" in name:
                    danger_signals.append("Freeze Authority Enabled")
                elif "LP Unlocked" in name:
                    danger_signals.append("LP Not Locked/Burned")
                elif "Low Liquidity" in name:
                    danger_signals.append("Low Liquidity")
                elif "High ownership" in name or "Single holder" in name:
                    # KryptoGO cluster analysis handles this better, but good as a secondary check
                    pass 

        if danger_signals:
            return False, f"Rug Risks: {', '.join(danger_signals)}"
        
        return True, "Safe"
        
    except Exception as e:
        print(f"Warning: RugCheck check failed: {e}")
        return True, "CHECK_ERROR"


def analyze_and_trade(token_mint, max_position_sol=None):
    """
    Full pipeline: check balance -> analyze token -> assess risk -> execute trade.

    G4 fix: Portfolio balance check at start to ensure sufficient SOL.
    G5 fix: Private key security warnings in comments.
    G6 fix: Uses scan_count from user preferences instead of hardcoded page_size.
    G7 fix: RugCheck integration for LP security.
    """
    if max_position_sol is None:
        max_position_sol = PREFERENCES.get("max_position_size", 0.1)

    # G4: Check portfolio balance before attempting any trade
    portfolio = get_portfolio(WALLET_ADDRESS)
    sol_balance = float(portfolio.get("sol_balance", "0"))
    min_required = max_position_sol + 0.01  # position + gas buffer
    if sol_balance < min_required:
        print(f"Insufficient SOL balance: {sol_balance:.4f} SOL (need {min_required:.4f}). Skipping.")
        return None

    # Step 1: Token overview
    overview = get_token_overview(token_mint)
    mcap = float(overview.get("market_cap") or 0)
    min_mcap = PREFERENCES.get("min_market_cap", 500_000)
    if mcap < min_mcap:
        print(f"Market cap ${mcap:,.0f} below minimum ${min_mcap:,.0f}. Skipping.")
        return None

    # [NEW] Step 1.5: Quick Rug Check (Fail Fast)
    is_safe, risk_reason = check_rug_risk(token_mint)
    if not is_safe:
        print(f"RUG CHECK FAILED: {risk_reason}. Skipping.")
        return None
    print(f"Rug Check Passed: {risk_reason}")

    # Step 2: Cluster analysis
    cluster_changes = get_cluster_changes(token_mint)
    cluster_ratio = cluster_changes["cluster_ratio"]
    change_1d = cluster_changes["changes"].get("1d", 0)
    change_7d = cluster_changes["changes"].get("7d", 0)

    print(f"Cluster ratio: {cluster_ratio:.1%}, 1d change: {change_1d:+.2%}, 7d change: {change_7d:+.2%}")

    if cluster_ratio < 0.20:
        print("Cluster ratio too low (<20%). No clear major holder.")
        return None

    # Step 2b: Scam detection -- check if any single cluster holds >50%
    clusters = analyze_token(token_mint)
    total_supply = float(overview.get("total_supply") or overview.get("circulating_supply") or 0)
    if total_supply > 0:
        for cluster in clusters.get("clusters", []):
            cluster_pct = float(cluster["total_balance"]) / total_supply
            if cluster_pct > 0.50:
                print(f"SCAM WARNING: Single cluster holds {cluster_pct:.0%} of supply. Skipping.")
                return None

    if change_1d < 0 and change_7d < 0:
        print("Clusters are distributing (negative 1d and 7d changes). Avoid.")
        return None

    # Step 3: Get token-specific labels
    token_labels = get_token_wallet_labels(token_mint)

    # Step 4: Risk assessment
    risk = assess_sell_pressure(token_mint, clusters, token_labels)
    if risk == "high":
        print("HIGH RISK: Developer/sniper/bundle holders still active. Skipping.")
        return None

    # Step 5: Get wallet labels for top cluster addresses
    top_cluster_wallets = []
    for cluster in clusters.get("clusters", [])[:5]:
        for w in cluster["wallets"]:
            top_cluster_wallets.append(w["address"])

    if top_cluster_wallets:
        labels = get_wallet_labels(token_mint, top_cluster_wallets[:50])
        smart_money_count = sum(
            1 for addr_labels in labels.get("data", {}).values()
            for lbl in addr_labels if lbl["label_type"] == "smart_money"
        )
        print(f"Smart money wallets in top clusters: {smart_money_count}")

    # Step 6: All checks passed -- execute trade
    symbol = overview.get("symbol", token_mint[:8])
    print(f"\nAnalysis passed. Buying {symbol} with {max_position_sol} SOL")

    amount_lamports = int(max_position_sol * LAMPORTS_PER_SOL)
    # SECURITY: sign_transaction uses PRIVATE_KEY locally; it is never sent to the server
    result = safe_execute_trade(SOL_MINT, token_mint, amount_lamports, slippage_bps=300)

    if result:
        print(f"Trade executed: {result['explorer_url']}")

        # Log trade to journal for learning
        trade_id = log_trade_entry(
            token_mint=token_mint,
            symbol=symbol,
            action="BUY",
            amount_sol=max_position_sol,
            token_amount=0,  # fill from result if available
            price=float(overview.get("price", 0)),
            market_cap=mcap,
            entry_reasoning={
                "cluster_ratio": cluster_ratio,
                "cluster_change_1d": change_1d,
                "cluster_change_7d": change_7d,
                "smart_money_count": smart_money_count if 'smart_money_count' in dir() else 0,
                "dev_exited": True,  # passed risk check
                "sniper_cleared": True,  # passed risk check
                "signal_source": "trending_tokens",  # or "signal_dashboard"
                "risk_level": risk,
            },
        )
        result["trade_id"] = trade_id
        print(f"Trade logged to journal: {trade_id}")

    return result


def check_account_tier():
    """Check agent account tier."""
    try:
        resp = requests.get(f"{API_BASE}/agent/account", headers=HEADERS, timeout=5)
        if resp.status_code == 200:
            return resp.json().get("tier", "free").lower()
    except Exception:
        pass
    return "free"


def scan_signal_dashboard(chain_id="501"):
    """Scan signal dashboard for Pro/Alpha users."""
    scan_count = PREFERENCES.get("scan_count", 10)
    params = {
        "chain_id": chain_id,
        "sort_by": "signal_count",
        "page_size": str(scan_count)
    }
    try:
        resp = requests.get(f"{API_BASE}/signal-dashboard", headers=HEADERS, params=params)
        resp.raise_for_status()
        # Adapt response to match trending tokens structure if needed
        data = resp.json()
        if isinstance(data, list):
            return data
        return data.get("tokens", []) or data.get("signals", [])
    except Exception as e:
        print(f"Warning: Signal Dashboard scan failed: {e}")
        return []


def discover_and_analyze():
    """Scan tokens (Signals for Pro+, Trending for Free) and trade."""
    max_position_sol = PREFERENCES.get("max_position_size", 0.1)
    min_mcap = PREFERENCES.get("min_market_cap", 500_000)

    tier = check_account_tier()
    print(f"Account Tier: {tier.upper()}")

    tokens = []
    if tier in ["pro", "alpha"]:
        print("Scanning Signal Dashboard (Pro/Alpha exclusive)...")
        tokens = scan_signal_dashboard()
        if not tokens:
            print("No signals found. Falling back to Trending Tokens.")
            tokens = scan_trending_tokens(
                marketCapMin=str(int(min_mcap / 5)),
                marketCapMax="5000000",
                holdersMin="50",
                volumeMin="10000",
                tokenAgeMax="7",
                tokenAgeType="3",
            )
    else:
        print("Scanning Trending Tokens...")
        tokens = scan_trending_tokens(
            marketCapMin=str(int(min_mcap / 5)),
            marketCapMax="5000000",
            holdersMin="50",
            volumeMin="10000",
            tokenAgeMax="7",
            tokenAgeType="3",
        )

    for token in tokens:
        # Normalized field access (signals might have different keys)
        mint = token.get("tokenContractAddress") or token.get("address") or token.get("mint") or token.get("contract_address") or token.get("token_address")
        chain_id = str(token.get("chain_id") or token.get("chainId") or "501")
        symbol = token.get("tokenSymbol") or token.get("symbol")
        mcap = float(token.get("marketCap") or token.get("market_cap") or 0)
        volume = float(token.get("volume") or token.get("volume_24h") or 0)
        change = float(token.get("change") or token.get("price_change_24h") or 0)

        print(f"Scanning {symbol}: mcap=${mcap:,.0f}, vol=${volume:,.0f}, change={change:+.1f}% (mint: {mint}, chain: {chain_id})")

        if not mint:
            print("Skipping token with missing mint address.")
            continue
        
        # Skip non-Solana tokens for trading (swap.py only supports Solana)
        if chain_id != "501":
            print(f"Skipping non-Solana token on chain {chain_id}")
            continue

        if change < 0:
            continue  # Skip tokens with negative momentum

        result = analyze_and_trade(mint, max_position_sol)
        if result:
            return result  # Stop after first successful trade

    print("No tokens passed all criteria.")
    return None


def monitor_positions():
    """Check all positions and flag candidates for selling."""
    portfolio = get_portfolio(WALLET_ADDRESS)
    actions = []
    stop_loss_pct = PREFERENCES.get("stop_loss_pct", 30)
    take_profit_pct = PREFERENCES.get("take_profit_pct", 100)

    for token in portfolio.get("tokens", []):
        mint = token["mint"]
        symbol = token["symbol"]
        unrealized_pnl = float(token.get("unrealized_pnl", "0"))
        avg_cost = float(token.get("holding_avg_cost", "0"))
        balance = float(token.get("balance", "0"))
        cost_basis = avg_cost * balance
        holding_hours = int(float(token.get("avg_holding_seconds", "0"))) / 3600

        # Check cluster changes for held tokens
        try:
            changes = get_cluster_changes(mint)
            change_4h = changes["changes"].get("4h", 0)
        except Exception:
            change_4h = 0

        # Stop loss
        if cost_basis > 0 and (unrealized_pnl / cost_basis * 100) < -stop_loss_pct:
            actions.append({"mint": mint, "symbol": symbol, "action": "SELL", "reason": "stop_loss"})

        # Distribution detected
        elif change_4h < -0.05:
            actions.append({"mint": mint, "symbol": symbol, "action": "REDUCE", "reason": "distribution_detected"})

        # Stale position
        elif holding_hours > 24 and unrealized_pnl < 0.01:
            actions.append({"mint": mint, "symbol": symbol, "action": "REVIEW", "reason": "stale_position"})

        # Take profit
        elif cost_basis > 0 and (unrealized_pnl / cost_basis * 100) > take_profit_pct:
            actions.append({"mint": mint, "symbol": symbol, "action": "TAKE_PROFIT", "reason": f"{take_profit_pct}%_gain"})

    return actions


def execute_exit(mint, symbol, action, reason, pnl_pct, pnl_sol, holding_hours):
    """Execute a sell and log the outcome to the journal."""
    # Find the matching open trade in journal
    journal = _load_journal()
    trade_id = None
    for trade in reversed(journal["trades"]):
        if trade["token_mint"] == mint and trade["outcome"] is None:
            trade_id = trade["id"]
            break

    # Execute the sell
    portfolio = get_portfolio(WALLET_ADDRESS)
    token_balance = 0
    for token in portfolio.get("tokens", []):
        if token["mint"] == mint:
            # Use raw_balance if available, otherwise fetch decimals to convert UI balance
            if "raw_balance" in [REDACTED_SECRET] = int(token["raw_balance"])
            else:
                # Fetch decimals to convert UI balance to raw units
                overview = get_token_overview(mint)
                decimals = overview.get("decimals")
                if decimals is None:
                    print(f"ABORT: Could not determine decimals for {symbol}. Refusing to sell with unknown precision.")
                    return None
                ui_balance = float(token.get("balance", 0))
                token_balance = int(ui_balance * (10 ** decimals))
            break

    if token_balance > 0:
        result = safe_execute_trade(mint, SOL_MINT, token_balance, slippage_bps=500)
        if result and trade_id:
            # Get current cluster state for the exit record
            try:
                changes = get_cluster_changes(mint)
                cluster_at_exit = changes["cluster_ratio"]
            except Exception:
                cluster_at_exit = 0

            log_trade_exit(
                trade_id=trade_id,
                exit_price=0,  # agent should fill from actual result
                exit_reason=reason,
                pnl_sol=pnl_sol,
                pnl_pct=pnl_pct,
                holding_hours=holding_hours,
                cluster_ratio_at_exit=cluster_at_exit,
                lesson="",  # agent fills this after reflection
            )
            print(f"Exit logged: {trade_id} — {reason} ({pnl_pct:+.1f}%)")
        return result
    return None


# ---------------------------------------------------------------------------
# Entry point (for manual testing)
# ---------------------------------------------------------------------------

if __name__ == "__main__":
    print("=== KryptoGO Meme Trader - Reference Workflow ===\n")
    print(f"Agent wallet: {WALLET_ADDRESS}")
    print(f"Preferences: {json.dumps(PREFERENCES, indent=2)}\n")

    # Check portfolio first
    portfolio = get_portfolio(WALLET_ADDRESS)
    print(f"SOL balance: {portfolio.get('sol_balance', '?')} SOL")
    print(f"Open positions: {len(portfolio.get('tokens', []))}\n")

    # Run discovery
    discover_and_analyze()


path: examples/deep-analysis-workflow.py
content: #!/usr/bin/env python3
import os
import sys
import requests
from datetime import datetime, timedelta

# --- Configuration ---
API_BASE = "https://wallet-data.kryptogo.app"
[REDACTED_SECRET]"KRYPTOGO_API_KEY")

def die(message):
    print(f"Error: {message}", file=sys.stderr)
    sys.exit(1)

def get_token_overview(token_mint: str, chain_id: int):
    """Fetches token overview to get total supply and other metadata."""
    headers = {"Authorization": f"Bearer {API_KEY}"}
    url = f"{API_BASE}/token-overview?address={token_mint}&chain_id={chain_id}"
    response = requests.get(url, headers=headers)
    if response.status_code != 200:
        die(f"Failed to get token overview: {response.status_code} {response.text}")
    data = response.json()
    if not data.get("total_supply"):
        die("Could not retrieve total_supply from token overview.")
    return float(data["total_supply"])

def get_key_addresses(token_mint: str, chain_id: int):
    """Fetches analysis data to extract cluster and smart money addresses."""
    headers = {"Authorization": f"Bearer {API_KEY}"}
    url = f"{API_BASE}/analyze/{token_mint}?chain_id={chain_id}"
    response = requests.get(url, headers=headers)
    if response.status_code != 200:
        die(f"Failed to get analysis data: {response.status_code} {response.text}")
    
    data = response.json()
    
    cluster_wallets = []
    if data.get("clusters"):
        for cluster in data["clusters"]:
            for wallet in cluster.get("wallets", []):
                cluster_wallets.append(wallet["address"])

    smart_money_wallets = []
    if data.get("address_metadata"):
        for addr, meta in data["address_metadata"].items():
            if meta.get("label", "").startswith("smart-"):
                smart_money_wallets.append(addr)

    return list(set(cluster_wallets)), list(set(smart_money_wallets))

def get_balance_history(token_mint: str, chain_id: int, wallets: list, days: int = 7):
    """Fetches aggregated balance history for a list of wallets."""
    if not wallets:
        return []
        
    headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
    url = f"{API_BASE}/balance-history"
    
    # Calculate start timestamp
    after_timestamp = int((datetime.now() - timedelta(days=days)).timestamp())
    
    payload = {
        "chain_id": chain_id,
        "wallets": wallets,
        "token": token_mint,
        "bar": "1D",
        "limit": days + 1,
        "after": after_timestamp,
    }
    
    response = requests.post(url, json=payload, headers=headers)
    if response.status_code != 200:
        # API might fail if the list of wallets is too long.
        # In a real implementation, this should be batched.
        print(f"Warning: Failed to get balance history (maybe too many wallets): {response.text}", file=sys.stderr)
        return []
        
    return response.json()

def main():
    if not API_KEY:
        die("KRYPTOGO_API_KEY environment variable is not set.")
    if len(sys.argv) < 2:
        die("Usage: python deep-analysis-workflow.py <token_mint> [chain_id]")

    token_mint = sys.argv[1]
    chain_id = int(sys.argv[2]) if len(sys.argv) > 2 else 501

    print(f"🔬 Starting deep analysis for [REDACTED_SECRET] on chain: {chain_id}\n")

    # 1. Get Total Supply
    print("Step 1: Fetching token overview to get total supply...")
    total_supply = get_token_overview(token_mint, chain_id)
    print(f"✅ Total Supply: {total_supply:,.0f}\n")

    # 2. Get Key Addresses
    print("Step 2: Extracting Cluster and Smart Money addresses...")
    cluster_wallets, smart_money_wallets = get_key_addresses(token_mint, chain_id)
    print(f"✅ Found {len(cluster_wallets)} cluster addresses.")
    print(f"✅ Found {len(smart_money_wallets)} Smart Money addresses.\n")

    # 3. Analyze Cluster Holding Trend
    print("Step 3: Analyzing Cluster holding trend...")
    cluster_history = get_balance_history(token_mint, chain_id, cluster_wallets)
    if cluster_history:
        print("📈 Cluster Holding as % of Total Supply (past 7 days):")
        for record in cluster_history:
            balance = float(record['balance'])
            percentage = (balance / total_supply) * 100
            date = datetime.fromtimestamp(record['timestamp']).strftime('%Y-%m-%d')
            print(f"  {date}: {percentage:.2f}%")
        print()
    else:
        print("⚠️ Could not retrieve cluster history.\n")

    # 4. Analyze Smart Money Holding Trend
    print("Step 4: Analyzing Smart Money holding trend...")
    sm_history = get_balance_history(token_mint, chain_id, smart_money_wallets)
    if sm_history:
        print("🧠 Smart Money Holding as % of Total Supply (past 7 days):")
        for record in sm_history:
            balance = float(record['balance'])
            percentage = (balance / total_supply) * 100
            date = datetime.fromtimestamp(record['timestamp']).strftime('%Y-%m-%d')
            print(f"  {date}: {percentage:.2f}%")
        print()
    else:
        print("⚠️ Could not retrieve Smart Money history.\n")

if __name__ == "__main__":
    main()


path: references/concepts.md
content: # Core Concepts

## 1. Wallet Clustering

A **cluster** is a group of wallets controlled by the same entity -- a market maker, trading team, or token operator. These wallets appear independent on-chain but are algorithmically linked through:

1. **Fund flow correlation** -- money flowing between addresses
2. **Same fund source** -- addresses receiving initial funding from the same origin
3. **Close withdrawal timing** -- addresses making withdrawals within similar timeframes
4. **Same-transaction binding** -- addresses tied to the same transaction (bundle buys)
5. **Multi-sig patterns** -- shared governance structures

**Why clustering matters:** Market manipulators spread holdings across dozens or hundreds of addresses. Tracking a single whale address misses the complete picture. Cluster analysis reveals the **total capital layout** of an entity.

**Key metric -- Cluster Holding Ratio:** The percentage of total token supply held by all identified clusters combined.
- **>=30-35%** = Token is "controlled" -- a major entity holds significant supply
- **>=50%** = Highly concentrated -- high manipulation risk but also high upside if accumulating
- **<10%** = Dispersed -- no clear major holder, likely retail-driven

**Scam detection rule:** If a **single cluster** controls more than **50% of total token supply**, this is a strong indicator of a scam or rug pull token. Avoid trading these unless you have extremely compelling reasons. The operator can dump the token at any time and drain liquidity.

## 2. Address Labels (3-Layer Model)

Every wallet is classified by on-chain behavior using a **3-layer model** ordered by cost basis (lowest to highest). Labels appear across all analysis responses and are critical for interpreting holder intent.

### Layer 1 -- Lowest-Cost Tokens (Sell Pressure Risk)

These addresses hold the cheapest tokens and pose the highest sell pressure risk. **Check these first -- if they haven't cleared, upside is capped.**

| Label | Threshold | What It Means |
|-------|-----------|---------------|
| **Developer** | Deployed the token + associated wallets | Core team addresses with lowest cost basis. If still holding large amounts = high dump risk. KryptoGO tracks multi-layer addresses linked to the deployer. |
| **Sniper** | Bought within 1 second of creation | Holds the absolute lowest-cost tokens. If NOT cleared = strong sell pressure overhead. |

### Layer 2 -- Manipulation Indicators (Operation Signals)

These labels indicate organized manipulation or short-term speculation. **High proportion = artificial activity, not genuine market interest.**

| Label | Threshold | What It Means |
|-------|-----------|---------------|
| **New Wallet** | Created within 24h before token deployment | Likely pre-prepared operation addresses. Batch-created for distributed manipulation. |
| **Bundle Transaction** | Multiple addresses buying in the same tx | Team operation pattern -- multiple small wallets eating internal orders at extremely low cost. |
| **High-Frequency** | Median hold < 12 hours | Short-term speculators. High proportion = unstable trend, likely just a hot spot. |

### Layer 3 -- Trend Direction (Smart Capital)

These are the most informative labels for trend prediction. **Their behavior indicates where experienced capital is flowing.**

| Label | Threshold | What It Means |
|-------|-----------|---------------|
| **Smart Money** | Realized profit > $100K | Consistently profitable traders. Their accumulation often precedes major price moves. |
| **Blue-Chip Profit** | Profit > $100K on tokens that peaked > $10M mcap | Long-term trend traders who catch main waves. KryptoGO re-parses Jupiter limit orders, DCA buys, and split orders to restore accurate cost basis. |
| **Whale** | Single-token position > $100K | Large capital holders. A whale cluster does not equal just "big holder" -- it could be an operating team or cross-market operator. |

## 3. Accumulation vs Distribution

The core analytical question: **Is the major holder buying or selling?**

**Accumulation:**
- Cluster holding % is **rising** while price is consolidating or pulling back
- Smart money / whale clusters are increasing positions
- Developer and sniper positions have been **cleared** (reduced sell pressure)
- New fund inflow approximately equals market cap times cluster holding % increase

**Distribution:**
- Price is **rising** but cluster holding % is **declining** -- major holder selling into strength
- Smart money holdings decreasing -- entering harvest phase
- Blue-chip profit wallets distributing en masse -- often signals end of main price wave
- Signal triggered but cluster % quickly drops back -- possible bull trap

**Key insight: Price and cluster holdings DIVERGING is the most important signal.** Rising price + falling cluster % = distribution. Falling price + rising cluster % = accumulation.

## 4. The "Other Holders" Survivorship Bias

The `/analyze-token` response includes `other_top_holders` -- top 200 non-cluster addresses. **Be careful**: this list only shows CURRENT holders. Addresses that sold and left are not shown, creating a survivorship bias where "other holders" appear bullish even when many have actually exited.

Cluster data is more reliable because it tracks the complete entity across all its addresses.


path: references/autonomous-trading.md
content: # Autonomous Trading Reference

This document contains detailed configuration and behavior for autonomous trading mode, cron-based monitoring, and the learning/adaptation system.

For the main skill overview, see [SKILL.md](../SKILL.md).

---

## Operating Modes

### Supervised Mode (Default)

All trades require explicit user confirmation before execution. The agent:

- Analyzes tokens and presents recommendations with reasoning
- Waits for user approval before executing any buy or sell
- Monitors stop-loss/take-profit conditions and **notifies** the user, but does NOT auto-execute
- Reports portfolio status and scan results for user review

### Autonomous Mode (Opt-in)

Enable by setting `require_trade_confirmation: false` in `memory/trading-preferences.json`.

**Automatic actions (no user confirmation needed):**
- Scanning trending tokens (default: top 10 per scan)
- Running full analysis pipeline on candidate tokens
- Checking portfolio status and PnL
- Executing trades that pass ALL criteria in the Bullish Checklist
- Stop-loss sells when unrealized loss exceeds threshold
- Take-profit sells when gain exceeds target

**Still requires user confirmation even in autonomous mode:**
- Risk level is "medium" (ambiguous signals)
- Position size exceeds user-defined max
- Selling at a loss outside stop-loss rules
- Any action outside the defined entry/exit strategies

**Reporting:**
- After each trade: summarize analysis result + trade details (concise text, not raw JSON)
- After each scan: brief summary of tokens scanned, why they passed/failed
- On errors: report the issue and suggest next steps

---

## Automated Monitoring (Cron)

The recommended setup uses **two OpenClaw cron jobs** working together:

| Job | Interval | Purpose |
|-----|----------|---------|
| `stop-loss-tp` | Every **5 min** | Check portfolio, notify on stop-loss / take-profit conditions |
| `discovery-scan` | Every **1 hour** | Discover new tokens, analyze, and recommend qualifying ones |

This separation ensures time-sensitive monitoring runs frequently, while the heavier discovery + analysis pipeline runs at a sustainable pace.

### Default Setup — Supervised (Recommended)

Run this to install both cron jobs in supervised mode (analysis-only, no auto-execution):

```bash
source ~/.openclaw/workspace/.env && bash scripts/cron-examples.sh setup-default
```

### Autonomous Setup

For autonomous trading (auto-buys and auto-sells), use:

```bash
source ~/.openclaw/workspace/.env && bash scripts/cron-examples.sh setup-autonomous
```

> **WARNING:** Autonomous mode enables automatic trade execution. Only use with a dedicated low-value wallet.

### Manual Cron Configuration

```bash
# Job 1: Stop-loss / Take-profit — every 5 minutes
openclaw cron add \
  --every 5m \
  --name "stop-loss-tp" \
  --prompt "Load the kryptogo-meme-trader skill. Source .env. Call /agent/portfolio with the agent wallet.
For each open position:
- If unrealized PnL ≤ -stop_loss_pct → notify user with sell recommendation (stop-loss).
- If unrealized PnL ≥ +take_profit_pct → notify user with sell recommendation (take-profit).
Read thresholds from memory/trading-preferences.json (defaults: stop_loss_pct=30, take_profit_pct=100).
Report triggered conditions to the user. Do NOT auto-execute sells. If no action needed, stay silent."

# Job 2: Discovery & Analysis — every 1 hour
openclaw cron add \
  --every 1h \
  --name "discovery-scan" \
  --prompt "Load the kryptogo-meme-trader skill. Source .env. Execute the full discovery workflow:
1. Read memory/trading-preferences.json for current parameters.
2. Read memory/trading-lessons.md (if exists) to avoid known bad patterns.
3. Check open positions count — skip buying if at max_open_positions.
4. Scan for candidates:
   - Pro/Alpha tier: call /signal-dashboard (sort_by=signal_count, page_size=10).
   - Free tier: call /agent/trending-tokens with min_market_cap filter.
5. Run top candidates through the 7-step analysis pipeline.
6. Send recommendations to the user. Do NOT auto-execute buys.
7. Update memory/trading-state.json with scan timestamp.
8. If any trades were closed since last scan: run post-trade reflection and update trading-lessons.md.
Report ALL findings to the user. If nothing happened, stay silent."
```

### Quick Commands

```bash
# List active cron jobs
openclaw cron list

# Pause trading (remove both jobs)
openclaw cron remove stop-loss-tp
openclaw cron remove discovery-scan

# Resume
source ~/.openclaw/workspace/.env && bash scripts/cron-examples.sh setup-default
```

### First-Time Auto-Setup (Agent Instruction)

The first time this skill is loaded, check if `stop-loss-tp` and `discovery-scan` cron jobs exist (via `openclaw cron list`). If not, proactively offer to set them up.

### Alternative: Heartbeat-Based Monitoring

If you prefer a simpler setup (one check every ~30 min instead of two cron jobs), add this to `HEARTBEAT.md`:

```markdown
## Trading Agent
- Check portfolio: call /agent/portfolio, report stop-loss/take-profit conditions to user
- Scan for opportunities: use signal dashboard (Pro/Alpha) or trending tokens API
- Send recommendations to user for approval
- Log closed trades to journal, run post-trade reflection
- ALWAYS report any triggered condition to the user
```

**Tradeoff:** Heartbeat batches everything into one turn (~30 min intervals). Stop-loss notifications could be delayed up to 30 minutes.

### Monitoring Workflow

1. **Portfolio check** (every 5 min via `stop-loss-tp`)
   - Notify on stop-loss if unrealized loss > `stop_loss_pct`
   - Notify on take-profit if unrealized gain > `take_profit_pct`
   - Flag stale positions (held > 7 days with no significant movement)
2. **Signal scan** (every 1h via `discovery-scan`)
   - **Pro/Alpha tier:** Use `/signal-dashboard` first — pre-filtered for smart money activity
   - **Free tier:** Fall back to `/agent/trending-tokens` with filters
   - Run top results through the 7-step analysis pipeline
   - Send recommendations to user
3. **State persistence** → Save to `memory/trading-state.json`
4. **Learning check** → If any trades were closed since last check:
   - Log outcome to `memory/trading-journal.json`
   - Run post-trade reflection
   - If loss >20%, trigger Loss Post-Mortem
   - If 20+ trades or 7+ days since last review, trigger Strategy Review

### Notification Rules

**Mandatory — agent MUST message the user when:**
- Any trade condition is triggered (stop-loss, take-profit, buy recommendation)
- A position is flagged for manual review (medium risk, stale, etc.)
- An error prevents normal operation (API down, insufficient SOL, quota exceeded)

**Stay silent when:**
- Portfolio checked, no action needed
- Tokens scanned, none qualified

### Kill Switch

To immediately stop all autonomous trading:
1. **Remove cron jobs:** `openclaw cron remove stop-loss-tp && openclaw cron remove discovery-scan`
2. **Remove Trading Agent section from HEARTBEAT.md** (if using heartbeat mode)
3. **Remove or rename `.env`** — prevents any API calls or signing

### Failure Recovery

If the agent crashes or session ends mid-trade:
- On startup, **ALWAYS** call `/agent/portfolio` first to check current holdings
- Compare with `memory/trading-state.json` to detect any untracked positions
- Report any discrepancies to the user immediately

---

## Learning & Adaptation

The agent improves over time by recording every trade, analyzing outcomes, and adjusting its strategy.

### Trade Journal

Every trade (buy or sell) MUST be logged to `memory/trading-journal.json` immediately after execution:

```json
{
  "trades": [
    {
      "id": "2026-02-25T14:30:00Z_BONK",
      "token_mint": [REDACTED_SECRET],
      "symbol": "BONK",
      "chain_id": "501",
      "action": "BUY",
      "amount_sol": 0.1,
      "token_amount": 150000000,
      "price_at_entry": 0.0000234,
      "market_cap_at_entry": 1500000,
      "timestamp": "2026-02-25T14:30:00Z",
      "entry_reasoning": {
        "cluster_ratio": 0.35,
        "cluster_change_1d": 0.05,
        "smart_money_count": 3,
        "dev_exited": true,
        "sniper_cleared": true,
        "signal_source": "signal_dashboard",
        "risk_level": "low"
      },
      "outcome": null
    }
  ]
}
```

When a position is closed, update the BUY entry's `outcome`:

```json
{
  "outcome": {
    "exit_price": 0.0000468,
    "exit_timestamp": "2026-02-26T09:15:00Z",
    "exit_reason": "take_profit",
    "pnl_sol": 0.1,
    "pnl_pct": 100.0,
    "holding_duration_hours": 18.75,
    "cluster_ratio_at_exit": 0.28,
    "lesson": "Cluster started distributing 2h before exit — could have sold earlier"
  }
}
```

### Mandatory Post-Trade Reflection

After every SELL (win or loss), the agent MUST:
1. Compare entry reasoning with actual outcome
2. Identify what analysis got right and what it missed
3. Write a one-sentence `lesson` in the outcome record
4. If loss >20%, do a Loss Post-Mortem

### Loss Post-Mortem

When a trade results in loss >20%:
1. Re-run analysis on the token at current state
2. Identify the miss: signal ignored? Timing error? External event?
3. Classify: `signal_miss`, `timing_error`, `external_event`, `parameter_drift`, `overconfidence`
4. Log to `memory/trading-lessons.md`

### Periodic Strategy Review

**Trigger:** Every 20 trades OR every 7 days.

1. Calculate aggregate stats (win rate, avg win/loss, best strategies, holding duration)
2. Identify patterns
3. Propose parameter adjustments to the user
4. Save review to `memory/strategy-reviews/YYYY-MM-DD.md`
5. Update preferences only after user approves

### Learning Memory Files

| File | Purpose | Updated |
|------|---------|---------|
| `memory/trading-journal.json` | Every trade with entry reasoning + outcome | After every trade |
| `memory/trading-lessons.md` | Patterns learned from losses | After losing trades |
| `memory/strategy-reviews/YYYY-MM-DD.md` | Periodic aggregate analysis | Every 20 trades or 7 days |
| `memory/trading-preferences.json` | Current strategy parameters | When user approves changes |
| `memory/trading-state.json` | Runtime state (open positions, last scan) | After every action |

### What the Agent Should NOT Do

- **Never auto-adjust parameters without user approval**
- **Never delete journal entries**
- **Never ignore a post-mortem**
- **Never blame "market conditions" without specifics**

---

## Implementation Notes & Lessons

### 1. Transaction Signer Must Match Your Wallet

Before signing, always verify that the fee payer matches your `SOLANA_WALLET_ADDRESS`. Always pass `wallet_address` in the `POST /agent/swap` request body.

### 2. `/agent/swap` Supports `wallet_address` Parameter

When provided, the unsigned transaction uses that address as fee payer and signer. Without it, the API defaults to the wallet associated with your API key.

### 3. Labels Are History, Not Current State

`/token-wallet-labels` returns behavioral labels based on historical actions. **Always verify current holdings via `/balance-history`** before making risk decisions.

### 4. Analysis Pipeline Order Is Mandatory

Never skip to trading without completing analysis:
1. Signal dashboard / trending tokens → candidates
2. Market cap / liquidity filter → eliminate small/illiquid tokens
3. Cluster multi-timeframe analysis → assess accumulation/distribution
4. Label + `/balance-history` verification → confirm sell pressure
5. Decision checklist → final go/no-go
6. Swap + sign + submit → execute trade


path: references/decision-framework.md
content: # Decision Framework

## Quick Assessment (3-Step Method)

A rapid screening method to evaluate any token in under 60 seconds:

1. **Concentration check:** Is cluster holding ratio > 30-40%? If no, skip (no clear major holder).
2. **Developer check:** Has the developer cluster exited? If still holding large position, high risk -- skip unless other signals are overwhelming.
3. **Trend check:** Is cluster holding % rising or falling over 1d/7d? Rising = accumulation, falling = distribution. Only proceed if accumulating.

If all 3 steps pass, proceed to the detailed checklist below.

## Bullish Checklist (All should be true for high-conviction entry)

- [ ] Market cap >= $500K
- [ ] Cluster holding ratio >= 30% and **rising** (positive changes across 1h, 4h, 1d)
- [ ] Smart money or whale clusters are increasing positions
- [ ] Developer cluster has **exited** or holds < 5%
- [ ] Sniper positions have been **cleared**
- [ ] Bundle transaction holders have **cleared**
- [ ] No high proportion of new wallets or high-frequency traders
- [ ] Price near or below cluster average buy price (support zone)

## Bearish Signals (Any one is a warning)

- Price rising but cluster holding % declining (distribution)
- Smart money holdings decreasing
- Developer still holds large low-cost position
- Sniper positions NOT cleared (sell pressure overhead)
- High-frequency traders dominate holder composition
- Signal triggered but cluster ratio quickly drops back (bull trap)

## Estimating Fund Inflow

A useful heuristic for quantifying accumulation:

```
Net fund inflow ~= Market cap x Cluster holding % increase
```

Example: If a token has $2M market cap and cluster holding increased by 5% over 24h:
- Estimated inflow = $2M x 0.05 = $100K net buying by clusters

This helps gauge the magnitude of accumulation -- are clusters putting in meaningful capital or just nibbling?

## Entry Strategies

| Strategy | When to Use | Entry Logic |
|----------|-------------|-------------|
| **Signal-driven** | Phase 2 signal fires + cluster % still rising | Enter on signal confirmation with 2-3% position |
| **Accumulation breakout** | Cluster % rising >5% in 24h + price consolidating | Enter before breakout with 3-5% position |
| **Support defense** | Price at cluster avg buy + holding ratio rising | Enter at support with 2-3% position, tight stop |
| **Dip buying** | Price drops 20%+ but cluster % holds or rises | Clusters defending = strong hands, enter 2-3% |

## Exit Strategies

| Signal | Action |
|--------|--------|
| Cluster % drops >5% in 4h while price stable/rising | Reduce 50% -- distribution starting |
| Smart money labels start appearing in sell-side | Close position -- smart money exiting |
| Developer addresses activate and start selling | Close immediately -- insider dumping |
| Price hits 2x entry with cluster % declining | Take profit on 50-75% of position |
| Price drops 30% from entry | Stop loss -- cut entire position |


path: references/api-reference.md
content: # API Reference

**Base URL:** `https://wallet-data.kryptogo.app`

**Authentication:** All endpoints require Bearer token authentication unless noted otherwise.

```
Authorization: Bearer sk_live_<48 hex chars>
```

**Common constants:**
- Wrapped SOL mint: [REDACTED_SECRET]
- 1 SOL = 1,000,000,000 lamports (1e9)
- All balance values are normalized by token decimals (6 decimal places as strings)

---

## Account & Rate Limits

### GET /agent/account

Check your current subscription tier and API usage. **This call does not count toward your daily quota.**

```
GET /agent/account
Authorization: Bearer <API_KEY>
```

**Response:**

```json
{
  "tier": "free",
  "daily_limit": 100,
  "daily_calls_used": 12,
  "daily_calls_remaining": 38
}
```

| Tier | Daily Limit |
|------|-------------|
| Free | 100 |
| Pro | 1,000 |
| Alpha | 5,000 |

### Rate Limits by Tier

| Tier  | Daily API Calls | Trading Fee | Signal Dashboard | KOL Finder |
|-------|-----------------|-------------|------------------|------------|
| Free  | 100 calls/day   | 1%          | No               | No         |
| Pro   | 1,000 calls/day | 0.5%        | Yes              | Yes        |
| Alpha | 5,000 calls/day | 0%          | Yes              | Yes        |

---

## Multi-Chain Support

Analysis endpoints support multiple chains via the `chain_id` parameter. **Trading (swap/submit) is currently Solana-only.**

| Chain | chain_id | Address Format | Trading Support |
|-------|----------|----------------|-----------------|
| Solana | `501` (default) | Base58 | Yes |
| BSC | `56` | Hex (0x-prefixed) | Analysis only |
| Base | `8453` | Hex (0x-prefixed) | Analysis only |
| Monad | `143` | Hex (0x-prefixed) | Analysis only |

---

## Analysis APIs

### GET /token-overview

Token metadata and market data.

```
GET /token-overview?address=<token_address>&chain_id=501
```

**Auth:** Bearer API key required.

**Response:**
```json
{
  "name": "FARTCOIN",
  "symbol": "FART",
  "logo_url": "https://...",
  "decimals": 9,
  "price": "0.001234",
  "market_cap": "1234567.89",
  "price_change_24h": "-5.23",
  "volume_24h": "567890.12",
  "holders_count": "12345",
  "liquidity": "234567.89",
  "total_supply": "1000000000",
  "circulating_supply": "950000000",
  "risk_level": 1,
  "creation_time": 1700000000
}
```

All fields are nullable. Numeric fields are strings.

---

### GET /analyze/:token_mint

Full cluster analysis for a token.

```
GET /analyze/<token_mint>?chain_id=501
```

**Auth:** Bearer API key required.

**Response:**
```json
{
  "clusters": [
    {
      "wallets": [
        {
          "address": "Abc123...",
          "token_balance": "500000.000000",
          "sol_balance": "10.000"
        }
      ],
      "total_balance": "1500000.000000",
      "sol_balance": "30.000"
    }
  ],
  "wintermute_balances": [
    { "address": "...", "token_balance": "...", "sol_balance": "..." }
  ],
  "other_top_holders": [
    { "address": "...", "token_balance": "...", "sol_balance": "..." }
  ],
  "address_metadata": {
    "Abc123...": { "label": "kol_name", "twitter_username": "kol_handle" }
  }
}
```

- `clusters`: Ordered by `total_balance` descending. Each cluster groups related wallets.
- `wintermute_balances`: Market maker (Wintermute) addresses, separated for clarity.
- `other_top_holders`: Top 200 non-cluster addresses (subject to survivorship bias).
- `address_metadata`: Known labels and social accounts for addresses.
- For EVM chains: uses `native_balance` instead of `sol_balance`, no `wintermute_balances`.

**Alternative:** `POST /analyze-token` with body `{"token_mint": "...", "chain_id": 501}` — same response, Solana only.

---

### GET /analyze-cluster-change/:token_mint

Cluster holding ratio and change trends across multiple time periods.

```
GET /analyze-cluster-change/<token_mint>?chain_id=501&include_top_holders=true
```

**Auth:** Bearer API key required.

**Response:**
```json
{
  "cluster_ratio": 0.35,
  "changes": {
    "15m": 0.001,
    "1h": 0.005,
    "4h": 0.012,
    "1d": 0.03,
    "7d": 0.08
  },
  "chart_data": {
    "1h": [
      { "timestamp": 1700000000, "balance": 0.33 },
      { "timestamp": 1700000120, "balance": 0.34 }
    ]
  },
  "top_holder_ratio": 0.25,
  "top_holder_changes": { "15m": 0.0, "1h": 0.001, "4h": -0.002, "1d": 0.01, "7d": 0.02 },
  "top_holder_chart_data": { "1h": [...], "4h": [...], ... }
}
```

- `cluster_ratio`: Current insider holding as fraction of total supply (0.35 = 35%)
- `changes`: Ratio change per period (positive = accumulating, negative = distributing)
- `chart_data`: Time series per period for trend visualization
- `top_holder_*`: Same metrics for non-cluster top holders (only when `include_top_holders=true`)

**Chart data granularity per period:**

| Period | Interval | Data Points |
|--------|----------|-------------|
| 15m | 1 min | ~15 |
| 1h | 2 min | ~30 |
| 4h | 5 min | ~48 |
| 1d | 30 min | ~48 |
| 7d | 4 hours | ~42 |

---

### POST /balance-history

Time-series balance data for specific wallets on a token.

```
POST /balance-history
{
  "token": "<token_address>",
  "wallets": ["<wallet_1>", "<wallet_2>"],
  "after": 1700000000,
  "bar": "1H",
  "limit": 100,
  "chain_id": 501
}
```

**Auth:** Bearer API key required. Bar/time restrictions by tier (Free: 1H/4H/1D, 7d lookback; Pro: +5m/15m, 90d; Alpha: +1m, unlimited).

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `token` | string | Yes | Token address |
| `wallets` | []string | Yes | Min 1 wallet |
| `after` | int64 | Yes | Unix timestamp start |
| `bar` | string | Yes | `1m`, `5m`, `15m`, `1H`, `4H`, `1D` |
| `limit` | int | Yes | 1-1000 |

**Response:** Array of `{"timestamp": int, "balance": "string"}` points.

---

### POST /wallet-labels

Behavior labels for multiple wallets (smart money, whale, blue-chip, high-frequency).

```
POST /wallet-labels
{
  "token_mint": "<token_address>",
  "wallets": ["<wallet_1>", "<wallet_2>"]
}
```

**Auth:** Bearer API key required.

**Response:**
```json
{
  "token_mint": "...",
  "data": {
    "<wallet_address>": [
      {
        "label_type": "smart_money",
        "token_mint": null,
        "token_symbol": null,
        "usd_value": 150000.50
      }
    ]
  }
}
```

Label types: `smart_money`, `high_frequency`, `whale`, `blue_chip_profit`. Only wallets with labels are included; unlabeled wallets are omitted.

---

### POST /token-wallet-labels

Token-specific labels: developer, sniper, bundle, new_wallet.

```
POST /token-wallet-labels
{ "token_mint": "<token_address>" }
```

**Auth:** Bearer API key required.

**Response:** Same structure as `/wallet-labels` but with label types: `developer`, `sniper`, `bundle`, `new_wallet`.

---

### GET /balance-increase/:token_mint

Range accumulation filter -- who bought the most in a time range.

```
GET /balance-increase/<token_mint>?from=<unix_ts>&to=<unix_ts>&chain_id=501
```

**Auth:** Bearer API key required.

**Response:**
```json
{
  "addresses": [
    {
      "address": "...",
      "balance_at_from": "100.000000",
      "balance_at_to": "500.000000",
      "current_balance": "480.000000",
      "balance_change_in_period": "400.000000",
      "balance_change_after_period": "-20.000000",
      "sol_balance": "1.500"
    }
  ],
  "address_metadata": { ... },
  "from_time": "2024-01-01T00:00:00Z",
  "to_time": "2024-01-02T00:00:00Z"
}
```

Max 500 results. Useful for verifying pre-pump accumulation: set range to 2-3 days before spike and check if top accumulators are known clusters.

---

### GET /top-holders-snapshot/:token_mint

Point-in-time top holder snapshot for before/after comparison.

```
GET /top-holders-snapshot/<token_mint>?timestamp=<unix_ts>&chain_id=501
```

**Auth:** Bearer API key required. Returns up to 1000 holders.

**Response:**
```json
{
  "top_holders": [
    { "address": "...", "balance": "123456.789000" }
  ],
  "address_metadata": { ... },
  "snapshot_time": "2024-01-01T00:00:00Z"
}
```

---

### GET /historical-top-holders/:token_mint

Addresses that held the most at ANY point in history.

```
GET /historical-top-holders/<token_mint>?chain_id=501
```

**Auth:** Bearer API key required.

**Response:**
```json
{
  "historical_top_holders": [
    {
      "address": "...",
      "current_balance": "100.000000",
      "historical_max_balance": "500.000000"
    }
  ],
  "address_metadata": { ... }
}
```

---

### GET /fresh-addresses/:token_mint

New wallet addresses holding a token (Solana only).

```
GET /fresh-addresses/<token_mint>
```

**Auth:** Bearer API key required.

**Response:**
```json
{
  "fresh_addresses": [
    {
      "address": "...",
      "current_balance": "100.000000",
      "historical_max_balance": "500.000000"
    }
  ],
  "address_metadata": { ... }
}
```

---

### GET /signal-dashboard

Network-wide accumulation signals (most recent ~100).

```
GET /signal-dashboard
```

**Auth:** Required. Tier: Pro or Alpha only.

**Response:**
```json
{
  "signals": [
    {
      "id": 12345,
      "chain": "sol",
      "chain_id": 501,
      "contract_address": "...",
      "symbol": "FART",
      "decimals": 9,
      "total_supply": "1000000000",
      "logo_url": "https://...",
      "pattern": "first_stage",
      "signal_time": 1700000000,
      "price_at_signal": "0.001234",
      "current_price": "0.002345",
      "max_price_after_signal": "0.003456",
      "price_change_percent": 90.12,
      "max_price_change_percent": 180.05,
      "cluster_ratio_at_signal": 0.15,
      "current_cluster_ratio": 0.18,
      "signal_history": [
        { "signal_time": 1699999000, "price_at_signal": "0.001000" }
      ]
    }
  ]
}
```

- `pattern`: `first_stage` (rapid/early, ~70/day) or `second_stage` (stable/confirmed, ~30/day)
- `cluster_ratio_at_signal` vs `current_cluster_ratio`: Compare to see if accumulation continued post-signal
- `signal_history`: Previous signals for this token -- backtest quality

---

### GET /signal-history/:address

Historical signals for a specific token.

```
GET /signal-history/<token_address>
```

**Auth:** Bearer API key required.

**Response:** Array of `{"signal_time": int, "price_at_signal": "string"}`.

---

### GET /analyze-dca-limit-orders/:token_mint

Detect DCA (dollar-cost averaging) and limit order usage by holders (Solana only).

```
GET /analyze-dca-limit-orders/<token_mint>
```

**Auth:** Bearer API key required.

**Response:**
```json
{
  "token_mint": "...",
  "user_clusters": [
    {
      "user_address": "...",
      "user_balance": "123456.000000",
      "dca_address_balances": [
        { "address": "...", "balance": "1000.000000" }
      ],
      "limit_order_address_balances": [
        { "address": "...", "balance": "500.000000" }
      ],
      "total_cluster_balance": "125000.000000"
    }
  ],
  "address_metadata": { ... }
}
```

DCA addresses with large positions = likely stealth accumulation by operators. Limit orders show tokens temporarily transferred out but still controlled by the same entity.

---

### GET /price-chart

OHLCV price candlestick data.

```
GET /price-chart?[REDACTED_SECRET]
```

| Param | Required | Values |
|-------|----------|--------|
| `token` | Yes | Token address |
| `after` | Yes | Unix timestamp |
| `bar` | Yes | `1m`, `5m`, `15m`, `1H`, `4H`, `1D` |
| `limit` | Yes | 1-1000 |

**Response:** Array of `[timestamp, open, high, low, close, volume, volumeUsd, confirm]` string arrays.

---

### POST /batch-token-prices

Batch price lookup for multiple tokens.

```
POST /batch-token-prices
{ "token_mints": ["<address_1>", "<address_2>"] }
```

**Response:** `{"prices": {"<address>": 0.001234}}` -- prices as float64 in USD.

---

### POST /cluster-wallet-connections

Get the actual fund flow connections between wallets within a cluster -- reveals WHY wallets were grouped.

```
POST /cluster-wallet-connections
{
  "token_mint": "<token_address>",
  "wallets": ["<wallet_1>", "<wallet_2>"]
}
```

**Auth:** Bearer API key required.

**Response:**
```json
{
  "connections": [
    {
      "source": "wallet_1",
      "target": "wallet_2",
      "connection_type": "fund_transfer",
      "discovered_at": "2024-01-01T00:00:00Z"
    }
  ]
}
```

Use case: Verify cluster detection logic. Identify key fund chain nodes -- which wallet is the "hub" that connects others.

---

### GET /wallet-assets

Get a wallet's complete token holdings (useful for inspecting other wallets, not just the agent's own).

```
GET /wallet-assets?address=<wallet_address>&chain_id=501
```

**Auth:** Bearer API key required.

**Response:** Returns token holdings with balances and USD values for the specified wallet.

---

## Discovery APIs

### GET /agent/trending-tokens

Returns trending tokens -- the same data source powering the KryptoGO xyz discovery page. Use this to scan the market for new opportunities.

```
GET /agent/trending-tokens?chain_id=501&sort_by=5&period=2&page_size=20
Authorization: Bearer <API_KEY>
```

**Query parameters:**

| Parameter | Default | Description |
|-----------|---------|-------------|
| `chain_id` | `501` | Chain filter: `501` (Solana), `56` (BSC), `8453` (Base), `143` (Monad), or comma-separated |
| `sort_by` | `5` | Sort field: `1`=market cap, `2`=holders, `3`=liquidity, `4`=tx count, `5`=volume, `6`=price change |
| `period` | `2` | Time window: `1`=5min, `2`=1h, `3`=4h, `4`=24h |
| `page` | `1` | Page number |
| `page_size` | `20` | Results per page (max 50) |
| `marketCapMin` | -- | Min market cap (USD) |
| `marketCapMax` | -- | Max market cap (USD) |
| `holdersMin` | -- | Min holder count |
| `holdersMax` | -- | Max holder count |
| `liquidityMin` | -- | Min liquidity (USD) |
| `liquidityMax` | -- | Max liquidity (USD) |
| `volumeMin` | -- | Min volume (USD) |
| `volumeMax` | -- | Max volume (USD) |
| `tokenAgeMin` | -- | Min token age |
| `tokenAgeMax` | -- | Max token age |
| `tokenAgeType` | -- | Age unit: `1`=min, `2`=hour, `3`=day, `4`=month, `5`=year |

**Response:**
```json
{
  "tokens": [
    {
      "tokenContractAddress": "2BZm...",
      "tokenSymbol": "FARTCOIN",
      "tokenLogoUrl": "https://...",
      "chainId": "501",
      "marketCap": "12345678",
      "holders": "5432",
      "liquidity": "234567",
      "volume": "567890",
      "price": "0.001234",
      "firstPriceTime": "1700000000",
      "change": "15.5"
    }
  ]
}
```

All numeric fields are strings. `change` is the percentage price change over the selected `period`. `firstPriceTime` is a Unix timestamp (seconds) representing token creation time.

**Free tier:** Limited to 10 tokens per request.

**Typical discovery workflow:**
1. Scan for new tokens with high 1h volume: `?sort_by=5&period=2&tokenAgeMax=7&tokenAgeType=3`
2. Filter by market cap range: `?marketCapMin=100000&marketCapMax=5000000`
3. For each interesting token, call `/analyze/:token_mint` for cluster analysis
4. Check `/token-overview?address=<mint>` for fundamentals

---

## Trading APIs

### 1. GET /agent/portfolio

Returns the wallet portfolio including SOL balance, token holdings with USD values, and per-token PnL data.

The API key is tied to your KryptoGO account (for billing and tier). The agent trades with its OWN wallet
(generated during setup), which is separate from the wallet you used to log into kryptogo.xyz.
Always pass the agent's wallet address in the `wallet_address` parameter.

```
GET /agent/portfolio?wallet_address=<optional_wallet_address>
Authorization: Bearer <API_KEY>
```

| Parameter | Required | Description |
|-----------|----------|-------------|
| `wallet_address` | No | The wallet to query. Defaults to the API key owner's wallet if omitted. |

**Response:**
```json
{
  "wallet": "3NgU...",
  "sol_balance": "1.5",
  "total_realized_pnl": "0.25",
  "total_unrealized_pnl": "-0.1",
  "total_pnl": "0.15",
  "tokens": [
    {
      "mint": "2BZm...",
      "symbol": "FARTCOIN",
      "logo_url": "https://...",
      "balance": "1000000",
      "usd_value": "50.25",
      "holding_avg_cost": "0.00005",
      "current_price": "0.00005025",
      "realized_pnl": "0.1",
      "unrealized_pnl": "0.05",
      "total_pnl": "0.15",
      "avg_holding_seconds": "3600",
      "holding_period_count": 2
    }
  ]
}
```

### 2. POST /agent/swap

Build an unsigned swap transaction via DEX aggregator.

```
POST /agent/swap
Authorization: Bearer <API_KEY>
Content-Type: application/json

{
  "input_mint": [REDACTED_SECRET],
  "output_mint": "2BZm...",
  "amount": 100000000,
  "slippage_bps": 300,
  "wallet_address": [REDACTED_SECRET]
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `input_mint` | string | Yes | Token to sell. `So111...112` for SOL. |
| `output_mint` | string | Yes | Token to buy. `So111...112` for SOL. |
| `amount` | integer | Yes | Amount in smallest unit (lamports for SOL). Must be > 0. |
| `slippage_bps` | integer | Yes | Slippage tolerance in basis points (1-5000). 300 = 3%. |
| `wallet_address` | string | No | Caller's wallet to use as tx fee payer / signer. If omitted, defaults to the wallet associated with the API key. **Recommended for local signing.** |

**Response:**
```json
{
  "transaction": "<base64-encoded unsigned V0 transaction>",
  "quote": {
    "input_mint": "So111...112",
    "output_mint": "2BZm...",
    "in_amount": "100000000",
    "out_amount": "1500000000",
    "price_impact_pct": "0.15",
    "platform_fee_lamports": "1000000",
    "platform_fee_rate": "0.01",
    "minimum_out_amount": "1455000000"
  },
  "fee_payer": [REDACTED_SECRET],
  "signers": [[REDACTED_SECRET]]
}
```

**Notes:**
- The transaction contains a recent blockhash which expires after ~60 seconds. Sign and submit promptly.
- Platform fee is embedded as a SOL transfer instruction. Only charged on SOL-involved swaps.
- When `wallet_address` is provided, `fee_payer` and `signers` in the response confirm which keys must sign. **Always verify `fee_payer` matches your wallet before signing.**
- Without `wallet_address`, the API uses the wallet tied to your API key, which may differ from your agent's trading wallet — causing signer mismatch errors during local signing.

### 3. POST /agent/submit

Submit a signed transaction to the Solana network.

```
POST /agent/submit
Authorization: Bearer <API_KEY>
Content-Type: application/json

{
  "signed_transaction": "<base64-encoded signed transaction>"
}
```

**Response:**
```json
{
  "tx_hash": "5XyZaBcDeFg...",
  "status": "submitted",
  "explorer_url": "https://solscan.io/tx/5XyZaBcDeFg..."
}
```

**Notes:**
- Server verifies the transaction contains a SystemProgram Transfer to the platform wallet.
- `"submitted"` means sent to network -- may still fail on-chain (slippage, etc.).

---

## Error Responses

All endpoints return errors in a consistent format:

```json
{ "error": "Human-readable error message" }
```

| Code | Meaning | Common Causes |
|------|---------|---------------|
| 400 | Bad Request | Invalid parameters, missing fields, invalid mint address, blacklisted token |
| 401 | Unauthorized | Missing or malformed `Authorization` header |
| 402 | Quota Exceeded | Daily API call limit reached (response includes `daily_limit` and `tier`) |
| 403 | Forbidden | Insufficient tier for endpoint (e.g., free tier accessing signals) |
| 502 | Bad Gateway | Solana RPC unavailable, DEX aggregator error |
| 504 | Timeout | Analysis took too long (>5 min for cluster analysis) |


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)