Skill: bbsyaya/tronlink-skills (version 1.0.1)

--- name: tron-wallet description: "This skill should be used when the user asks to 'check my TRX balance', 'show my TRON holdings', 'what tokens do I have on TRON', 'check my TRON wallet', 'TronLink balance', 'view my TRC-20 tokens', 'TRON transaction history', 'account info on TRON', or mentions checking wallet balance, viewing transaction history, or managing a TronLink wallet. Do NOT use for swap/trading — use tron-swap instead. Do NOT use for staking — use tron-staking instead." license: MIT metadata: author: tronlink-skills version: "1.0.0" homepage: "https://trongrid.io" --- # TRON Wallet Management 6 commands for wallet balance, TRC-20 holdings, transaction history, account info, address validation, and multi-sig support. ## Pre-flight Checks 1. **Confirm Node.js**: Run `node -e "console.log('ok')" # Node.js >= 18 required 2. **API Key (optional)**: For higher rate limits, set: ```bash export TRONGRID_API_KEY="your-api-key" ``` ## Skill Routing - For token metadata / search → use `tron-token` - For market prices / charts → use `tron-market` - For DEX swap → use `tron-swap` - For energy / bandwidth → use `tron-resource` - For staking / voting → use `tron-staking` ## Commands ### 1. Check TRX Balance ```bash node scripts/tron_api.mjs wallet-balance --address <TRON_ADDRESS> ``` Returns: TRX balance (human-readable), frozen TRX, account creation time. ### 2. Check TRC-20 Token Balance ```bash node scripts/tron_api.mjs token-balance --address <TRON_ADDRESS> --contract <TOKEN_CONTRACT> ``` Common TRC-20 contracts: | Token | Contract | |-------|----------| | USDT | [REDACTED_SECRET] | | USDC | [REDACTED_SECRET] | | WTRX | [REDACTED_SECRET] | | BTT | [REDACTED_SECRET] | | JST | [REDACTED_SECRET] | | SUN | [REDACTED_SECRET] | | WIN | [REDACTED_SECRET] | ### 3. Get All TRC-20 Holdings ```bash node scripts/tron_api.mjs wallet-tokens --address <TRON_ADDRESS> ``` Returns: list of all TRC-20 tokens with balances, symbols, and USD values. ### 4. Transaction History ```bash node scripts/tron_api.mjs tx-history --address <TRON_ADDRESS> --limit 20 ``` Returns: recent transactions with type, amount, timestamp, status. ### 5. Account Info ```bash node scripts/tron_api.mjs account-info --address <TRON_ADDRESS> ``` Returns: account creation date, permissions, resource overview, frozen balances, voting info. ### 6. Validate Address ```bash node scripts/tron_api.mjs validate-address --address <ADDRESS> ``` Returns: whether the address is valid TRON Base58Check format. ## Address Format Notes - TRON addresses start with `T` and are 34 characters long (Base58Check) - Hex addresses start with `41` and are 42 hex characters - Example: [REDACTED_SECRET] - The script accepts both formats and auto-converts ## Common Token Contracts (Mainnet) ``` USDT: TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t USDC: TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8 WTRX: TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR TUSD: TUpMhErZL2fhh4sVNULAbNKLokS4GjC1F4 BTT: TAFjULxiVgT4qWk6UZwjqwZXTSaGaqnVp4 JST: TCFLL5dx5ZJdKnWuesXxi1VPwjLVmWZZy9 SUN: TSSMHYeV2uE9qYH95DqyoCuNCzEL1NvU3S WIN: TLa2f6VPqDgRE67v1736s7bJ8Ray5wYjU7 NFT: TFczxzPhnThNSqr5by8tvxsdCFRRz6cPNq APENFT: TFczxzPhnThNSqr5by8tvxsdCFRRz6cPNq ``` ## Troubleshooting **"Account not found"**: The address has never been activated on TRON. A minimum of 1 TRX must be sent to activate it. **"Bandwidth insufficient"**: The account has used up its daily free bandwidth (600). Either wait for daily reset, freeze TRX for bandwidth, or the transaction will burn TRX as fee. **"Energy insufficient for TRC-20"**: Smart contract calls require Energy. Freeze TRX for Energy via `tron-staking`, or TRX will be burned (often 13-27 TRX for a USDT transfer).

path: _meta.json
content: {
  "owner": "tronlink-skills",
  "slug": "tronlink",
  "displayName": "TronLink Wallet Skills",
  "latest": {
    "version": "1.0.0",
    "publishedAt": 1741622400000,
    "commit": ""
  },
  "history": [],
  "keywords": ["tron", "tronlink", "trx", "trc20", "defi", "wallet", "staking", "energy", "bandwidth", "sunswap"],
  "description": "AI Agent skills for TronLink wallet — wallet management, token queries, DEX swap, energy/bandwidth resource management, and TRX staking on the TRON network."
}


path: docs/integration-guide.sh
content: #!/bin/bash
# ============================================================
# TronLink Skills — Claude Code Integration Guide
# ============================================================

# ────────────────────────────────────────────────────────────
# Method 1: Claude Code (Recommended)
# ────────────────────────────────────────────────────────────

# 1. Install Claude Code (requires Node.js 18+)
npm install -g @anthropic-ai/claude-code

# 2. Clone TronLink Skills into your project
git clone https://github.com/TronLink/tronlink-skills.git
cd tronlink-skills

# 3. Start Claude Code
claude

# 5. Claude Code will automatically read CLAUDE.md and skills/*/SKILL.md
#    Then you can give instructions in natural language, for example:

# 💬 "Check the TRX balance for this address: T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb"
# 💬 "Run a security audit on the USDT token"
# 💬 "What is the current Energy price on the TRON network?"
# 💬 "Get a swap quote for 100 TRX to USDT"
# 💬 "Show the TRON Super Representative list"
# 💬 "Analyze the resource usage for this address and give optimization suggestions"

# Claude Code will automatically:
#   1) Read the corresponding SKILL.md to understand the command format
#   2) Execute node scripts/tron_api.mjs <command>
#   3) Parse the JSON result and respond in natural language


# ────────────────────────────────────────────────────────────
# Method 2: Cursor / Windsurf IDE Agent
# ────────────────────────────────────────────────────────────

# 1. Clone into your project directory
git clone https://github.com/TronLink/tronlink-skills.git

# 2. Open the project in Cursor, the Agent will automatically read SKILL.md
# 3. Use natural language instructions in Cursor Chat


# ────────────────────────────────────────────────────────────
# Method 3: Use as MCP Server (Claude Desktop / Claude Code)
# ────────────────────────────────────────────────────────────

# Refer to the mcp_server.mjs file below to wrap Skills as an MCP Server
# Then configure in Claude Desktop's claude_desktop_config.json:
#
# {
#   "mcpServers": {
#     "tronlink": {
#       "command": "node",
#       "args": ["/path/to/tronlink-skills/scripts/mcp_server.mjs"]
#     }
#   }
# }


path: docs/resource-model.md
content: # TRON Resource Model — Domain Knowledge

## Overview

TRON's fee model is fundamentally different from Ethereum's gas system. Understanding this is critical for any agent operating on TRON.

## Bandwidth

**What it is**: A resource consumed by ALL transactions, proportional to the byte size of the transaction.

**Free allowance**: Every activated TRON account gets 600 free Bandwidth points per day, resetting at 00:00 UTC.

**Consumption**:
- Basic TRX transfer: ~267 Bandwidth
- TRC-20 transfer: ~345 Bandwidth
- Smart contract interaction: ~345-500 Bandwidth

**If insufficient**: TRX is burned at approximately 1,000 SUN (0.001 TRX) per Bandwidth point.

**Recovery**: Bandwidth recovers linearly over 24 hours after use.

## Energy

**What it is**: A resource consumed ONLY by smart contract calls (TRC-20 transfers, DEX swaps, DeFi protocols).

**No free allowance**: Unlike Bandwidth, there is ZERO free Energy.

**Consumption examples**:
| Operation | Energy |
|-----------|--------|
| USDT transfer (simple) | ~32,000 |
| USDT transfer (to new address) | ~65,000 |
| SunSwap V2 simple swap | ~65,000-100,000 |
| SunSwap V2 multi-hop | ~130,000-200,000 |
| SunSwap V3 swap | ~100,000-150,000 |
| Token approval | ~30,000 |
| Contract deployment | ~200,000-1,000,000+ |

**If insufficient**: TRX is burned. The burn rate depends on `getEnergyFee` chain parameter (typically 420 SUN per Energy unit).

**Cost calculation**: `TRX burned = energy_used × energy_price_sun / 1,000,000`

Example: USDT transfer (65,000 Energy × 420 SUN) = 27,300,000 SUN = 27.3 TRX

## How to Get Energy

### 1. Stake TRX (Stake 2.0)
- Freeze TRX → receive Energy proportional to stake
- Formula: `your_energy = (your_frozen_trx / total_network_frozen_trx) × total_energy_limit`
- Approximate: 1 TRX frozen ≈ 4.5 Energy per day (varies with network)
- Minimum lock: 14 days
- Also earns voting rewards if you vote for SRs

### 2. Rent Energy
- Third-party marketplaces sell energy at market rates
- Usually cheaper than burning TRX for occasional users
- No lock-up required

### 3. Burn TRX
- Simplest option — TRX is deducted automatically
- Most expensive option for frequent users
- No setup required

## Decision Matrix

| Usage Pattern | Best Strategy | Why |
|--------------|---------------|-----|
| Daily TRC-20 transfers | Freeze TRX for Energy | Amortized cost is lowest |
| Weekly transfers | Rent Energy | No 14-day lock, cheaper than burning |
| Monthly or less | Burn TRX | Simplest, acceptable cost |
| Heavy DeFi user | Freeze + rent excess | Combination for peak coverage |

## Energy Price History

The `getEnergyFee` parameter is set by TRON governance:
- 2021: 140 SUN/Energy
- 2022: 280 SUN/Energy
- 2023: 420 SUN/Energy
- 2024-2025: 420 SUN/Energy (current)

Higher energy prices make staking more valuable.

## Common Pitfalls

1. **"Why was 27 TRX deducted for a USDT transfer?"** — No staked energy; TRX was burned to cover energy cost.

2. **"Transaction failed, out of energy"** — The fee_limit was set too low. Default is 150 TRX; increase for complex operations.

3. **"I froze TRX but still have no energy"** — The amount frozen is too small relative to total network stake. Check `resource-info` for actual energy received.

4. **"Free bandwidth depleted"** — 600 daily bandwidth covers only ~2 basic transfers. Use staked bandwidth or accept small TRX burn.

5. **"Different USDT transfer costs"** — First transfer TO a new address costs ~65,000 energy; subsequent transfers to the same address cost ~32,000.


path: docs/staking-guide.md
content: # TRON Staking Guide — Domain Knowledge

## Stake 2.0 Overview

Stake 2.0 (introduced in proposal #81) replaced the legacy Stake 1.0 system. The key improvements:

1. **Separated freezing from voting** — You can freeze TRX for resources without voting, and vote without specifying a resource type.
2. **Flexible delegation** — Delegate Energy or Bandwidth to any address without transferring TRX.
3. **Partial unfreezing** — Unfreeze any portion of your staked TRX (not all-or-nothing).

## Lifecycle

```
              ┌─── Freeze TRX ───┐
              │                   │
              ▼                   ▼
         Get Energy          Get Bandwidth
              │                   │
              └───── Vote ────────┘
                      │
                      ▼
              Earn TRX Rewards
              (every 6 hours)
                      │
                      ▼
              Claim Rewards
                      │
                      ▼
              Unfreeze TRX
                      │
                      ▼
            14-day Wait Period
                      │
                      ▼
              Withdraw TRX
```

## Super Representatives (SRs)

TRON uses a Delegated Proof-of-Stake (DPoS) consensus mechanism:

- **27 Super Representatives** produce blocks in rotation
- **100+ SR Partners** serve as backup nodes
- SRs are elected every 6 hours via voting
- Block rewards: 16 TRX per block (produced every 3 seconds)
- Voting rewards: distributed proportionally to voters

### Reward Distribution

Total daily rewards ≈ 461,000 TRX (block + voting rewards)

Your share: `(your_votes / total_network_votes) × daily_reward_pool × (1 - sr_commission)`

### SR Selection Criteria

When choosing which SR to vote for:
1. **Commission rate** — Lower is better for voters (ranges from 0% to 100%)
2. **Block production rate** — Higher means more reliable
3. **Community contribution** — Some SRs fund ecosystem projects
4. **Uptime history** — Consistent block production

## Voting Strategy

### Maximize Rewards
- Vote for SRs with 0% commission (all rewards go to voters)
- Split votes across multiple low-commission SRs for diversification
- Re-vote if a preferred SR's commission changes

### Support the Ecosystem
- Some community SRs fund development, education, and tools
- Voting for them supports TRON ecosystem growth (may have higher commission)

### Compound Returns
- Claim rewards regularly
- Re-freeze claimed TRX for more Energy/TP
- Re-vote with increased TRON Power

## Economics

### Current Approximate APY: 3-5%

Factors affecting APY:
- Total TRX staked network-wide (more stake = lower individual APY)
- SR commission rates
- TRX price (affects USD-denominated returns)
- Block production efficiency

### Break-even Analysis

If you're freezing TRX primarily for Energy to avoid transaction burns:

```
Annual energy savings = daily_txs × energy_per_tx × energy_price_sun × 365
Annual staking reward = frozen_trx × apy

If energy_savings + staking_reward > opportunity_cost → Freeze
```

For a user doing 1 USDT transfer per day:
- Energy saved: 65,000 × 420 SUN × 365 = 9,963,000,000 SUN ≈ 9,963 TRX/year
- TRX to freeze for 65,000 daily energy: ~14,500 TRX
- Staking reward on 14,500 TRX at 4%: 580 TRX/year
- Total benefit: ~10,543 TRX/year — highly cost-effective

## Common Mistakes

1. **Forgetting to vote after freezing** — Freezing gives resources but NOT rewards. You must explicitly vote to earn.

2. **Unfreezing removes votes** — If you unfreeze, your votes are removed. Re-vote after re-freezing.

3. **Not claiming rewards** — Rewards don't auto-compound. Claim periodically and re-freeze.

4. **Freezing too little** — Due to the proportional resource model, freezing a small amount may not provide meaningful energy.

5. **Ignoring the 14-day lock** — Plan ahead; unfreezing takes 14 days. Don't freeze TRX you might need urgently.


path: docs/claude-integration-guide.md
content: # TronLink Skills — Complete Guide for Claude Integration

## Overview: Three Integration Methods

| Method | Use Case | Difficulty | Installation Required |
|--------|----------|------------|----------------------|
| **Claude Code + SKILL.md** | Daily development, on-chain queries | ⭐ Easy | Just clone the repo |
| **MCP Server** | Claude Desktop / Claude Code deep integration | ⭐⭐ Medium | MCP configuration needed |
| **Claude.ai file upload** | Temporary use, no installation | ⭐ Easy | Just upload the file |

---

## Method 1: Claude Code (Recommended)

Claude Code is Anthropic's command-line AI agent that can directly read SKILL.md files in your project and execute commands.

### Steps

```bash
# 1. Install Claude Code
npm install -g @anthropic-ai/claude-code

# 2. Clone the project
git clone https://github.com/TronLink/tronlink-skills.git
cd tronlink-skills

# 3. Start Claude Code
claude
```

### Usage

After starting, Claude Code **automatically reads** `CLAUDE.md` and `skills/*/SKILL.md`. Just ask questions in natural language:

```
💬 You: Check the TRX balance for T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb

🤖 Claude: Querying...
   [Executing: node scripts/tron_api.mjs wallet-balance --address T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb]

   The TRX balance for this address is 1,234.56 TRX, with 500 TRX frozen for Energy.

💬 You: Does this address have enough Energy for a USDT transfer?

🤖 Claude: Let me check the resource status...
   [Executing: node scripts/tron_api.mjs resource-info --address T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb]

   Current available Energy is 45,000, but a USDT transfer requires approximately 65,000 Energy.
   Recommend freezing more TRX, or this transaction will automatically burn approximately 8.4 TRX as a fee.

💬 You: Show the current TRON Super Representative list, which one has the lowest commission?

🤖 Claude: [Executing: node scripts/tron_api.mjs sr-list --limit 30]
   ...
```

### How It Works

When Claude Code starts, it will:
1. Read `CLAUDE.md` in the project root → understand project structure and available commands
2. Based on user questions, locate the corresponding `skills/tron-*/SKILL.md`
3. Extract command format, build and execute `node scripts/tron_api.mjs <command>`
4. Parse JSON output and respond to the user in natural language

---

## Method 2: MCP Server (Deep Integration)

MCP (Model Context Protocol) is Anthropic's tool protocol. After wrapping Skills as an MCP Server, Claude Desktop and Claude Code can call them like built-in tools.

### Adding MCP in Claude Code

```bash
# One-line command to add
claude mcp add tronlink -- node /your/path/to/tronlink-skills/scripts/mcp_server.mjs
```

After adding, Claude Code will automatically recognize 25 TRON tools (tron_wallet_balance, tron_token_price, etc.), no need to manually specify commands.

### Configuring in Claude Desktop

Edit the config file:

**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "tronlink": {
      "command": "node",
      "args": ["/your/absolute/path/to/tronlink-skills/scripts/mcp_server.mjs"],
      "env": {
        "TRONGRID_API_KEY": "your-api-key (optional)",
        "TRON_NETWORK": "mainnet"
      }
    }
  }
}
```

After restarting Claude Desktop, you can use TRON-related tools directly in conversations.

### 25 Tools Provided by MCP

| Tool Name | Function |
|-----------|----------|
| `tron_wallet_balance` | Check TRX balance |
| `tron_token_balance` | Check TRC-20 token balance |
| `tron_wallet_tokens` | List all token holdings |
| `tron_tx_history` | Transaction history |
| `tron_account_info` | Account details |
| `tron_validate_address` | Validate address |
| `tron_token_info` | Token metadata |
| `tron_token_search` | Search tokens |
| `tron_token_holders` | Top holders |
| `tron_trending_tokens` | Trending tokens |
| `tron_token_rankings` | Token rankings |
| `tron_token_security` | Security audit |
| `tron_token_price` | Real-time price |
| `tron_kline` | K-line data |
| `tron_whale_transfers` | Whale monitoring |
| `tron_market_overview` | Market overview |
| `tron_swap_quote` | DEX quote |
| `tron_tx_status` | Transaction status |
| `tron_resource_info` | Energy/Bandwidth query |
| `tron_energy_price` | Energy price |
| `tron_estimate_energy` | Energy estimation |
| `tron_optimize_cost` | Cost optimization report |
| `tron_sr_list` | Super Representative list |
| `tron_staking_info` | Staking details |
| `tron_staking_apy` | APY estimation |

---

## Method 3: Use Directly in Claude.ai Conversations

If you don't want to install anything, you can use it directly in claude.ai conversations (requires computer use capability):

1. Upload `tron_api.mjs` to the conversation
2. Tell Claude: "Please run this script and query XXX for me"
3. Claude will use the built-in computer tool to execute the command

For example, in the current conversation, you can say:

```
Use tron_api.mjs to run a security audit on the USDT contract TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
```

---

## Method 4: Cursor / Windsurf IDE

```bash
# Clone into project directory
git clone https://github.com/TronLink/tronlink-skills.git

# Open in Cursor, Agent automatically reads SKILL.md
# Or use MCP method:
# Cursor Settings → MCP → Add Server → point to mcp_server.mjs
```

---

## Environment Variables

```bash
# TronGrid API Key (optional, increases rate limits)
export TRONGRID_API_KEY="your-api-key"

# Switch network (default: mainnet)
export TRON_NETWORK="mainnet"   # or "shasta" / "nile"
```

---

## Quick Verification

After installation, run the following commands to verify everything works:

```bash
# Test address validation (offline)
node scripts/tron_api.mjs validate-address --address TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t

# Test Energy estimation (offline)
node scripts/tron_api.mjs estimate-bandwidth --tx-size 345

# Test APY calculation (offline)
node scripts/tron_api.mjs staking-apy --amount 10000

# Test network queries (requires network)
node scripts/tron_api.mjs market-overview
node scripts/tron_api.mjs token-price --contract TRX
```

---

## FAQ

### Q: Claude Code doesn't automatically recognize SKILL.md?
A: Make sure you start `claude` in the project root directory and the `CLAUDE.md` file exists.

### Q: MCP Server won't connect?
A: Check that the path to `mcp_server.mjs` is an absolute path, and Node.js >= 18.

### Q: API requests failing?
A: TronGrid public endpoints have rate limits. Set `TRONGRID_API_KEY` for higher limits. Apply at https://www.trongrid.io/dashboard.

### Q: Does this support transactions (transfer/swap)?
A: This skill set is read-only. It supports balance queries, token info, market data, resource estimation, and staking info. Transaction signing is not supported.


path: install.sh
content: #!/bin/sh
set -e

# ──────────────────────────────────────────────────────────────
# TronLink Skills — One-Command Installer
#
# Usage:
#   curl -sSL https://raw.githubusercontent.com/TronLink/tronlink-skills/main/install.sh | sh
#
# Local development (no GitHub needed):
#   sh install.sh --local /path/to/tronlink-skills
#
# Detects your AI environment automatically:
#   - Claude Code  → installs as plugin + registers MCP server
#   - Cursor       → installs as plugin with skills
#   - Codex CLI    → symlinks to skills directory
#   - OpenCode     → registers plugin + symlinks
#   - Other        → clones to ~/.tronlink-skills for manual use
# ──────────────────────────────────────────────────────────────

REPO="TronLink/tronlink-skills"
REPO_URL="https://github.com/${REPO}.git"
INSTALL_DIR="$HOME/.tronlink-skills"
VERSION="1.0.0"
LOCAL_SRC=""

# Parse arguments
while [ $# -gt 0 ]; do
  case "$1" in
    --local)
      LOCAL_SRC="$(cd "$2" && pwd)"
      shift 2
      ;;
    *)
      shift
      ;;
  esac
done

# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

info()  { printf "${CYAN}ℹ${NC}  %s\n" "$1"; }
ok()    { printf "${GREEN}✓${NC}  %s\n" "$1"; }
warn()  { printf "${YELLOW}⚠${NC}  %s\n" "$1"; }

# ──────────────────────────────────────────────────────────────
# Step 1: Clone or update the repo
# ──────────────────────────────────────────────────────────────

clone_or_update() {
  if [ -n "$LOCAL_SRC" ]; then
    # --local mode: copy from local directory instead of git clone
    info "Installing from local source: $LOCAL_SRC"
    if [ "$LOCAL_SRC" = "$INSTALL_DIR" ]; then
      ok "Source is already at $INSTALL_DIR, skipping copy"
    else
      rm -rf "$INSTALL_DIR"
      cp -R "$LOCAL_SRC" "$INSTALL_DIR"
      ok "Copied to $INSTALL_DIR"
    fi
    return
  fi

  if [ -d "$INSTALL_DIR/.git" ]; then
    info "Updating existing installation..."
    cd "$INSTALL_DIR" && git pull --quiet
    ok "Updated to latest version"
  else
    info "Cloning tronlink-skills..."
    git clone --depth 1 "$REPO_URL" "$INSTALL_DIR" 2>/dev/null
    ok "Cloned to $INSTALL_DIR"
  fi
}

# ──────────────────────────────────────────────────────────────
# Step 2: Detect AI environment
# ──────────────────────────────────────────────────────────────

detect_env() {
  # Claude Code
  if command -v claude >/dev/null 2>&1; then
    echo "claude-code"
    return
  fi

  # Cursor (check if running inside Cursor terminal)
  if [ -n "$CURSOR_TRACE_ID" ] || [ -d "$HOME/.cursor" ]; then
    echo "cursor"
    return
  fi

  # Codex CLI
  if command -v codex >/dev/null 2>&1; then
    echo "codex"
    return
  fi

  # OpenCode
  if command -v opencode >/dev/null 2>&1 || [ -d "$HOME/.config/opencode" ]; then
    echo "opencode"
    return
  fi

  # Windsurf
  if [ -d "$HOME/.windsurf" ]; then
    echo "windsurf"
    return
  fi

  echo "generic"
}

# ──────────────────────────────────────────────────────────────
# Step 3: Environment-specific setup
# ──────────────────────────────────────────────────────────────

setup_claude_code() {
  info "Detected: Claude Code"

  # Method 1: Register as global MCP server (available in all projects)
  info "Registering MCP server (global)..."
  if claude mcp add -s user tronlink -- node "$INSTALL_DIR/scripts/mcp_server.mjs" 2>/dev/null; then
    ok "MCP server registered globally (25 TRON tools available in all projects)"
  else
    warn "MCP auto-register failed. Manual setup:"
    echo ""
    echo "  claude mcp add -s user tronlink -- node $INSTALL_DIR/scripts/mcp_server.mjs"
    echo ""
  fi

  # Method 2: Also add CLAUDE.md to project if we're in a project
  if [ -f "package.json" ] || [ -d ".git" ]; then
    if [ ! -f "CLAUDE.md" ]; then
      cp "$INSTALL_DIR/CLAUDE.md" ./CLAUDE.md
      ok "Added CLAUDE.md to current project"
    else
      # Append if CLAUDE.md already exists
      if ! grep -qi "tronlink" CLAUDE.md 2>/dev/null; then
        echo "" >> CLAUDE.md
        cat "$INSTALL_DIR/CLAUDE.md" >> CLAUDE.md
        ok "Appended TronLink instructions to existing CLAUDE.md"
      fi
    fi
  fi

  ok "Claude Code setup complete!"
  echo ""
  echo "  Usage: Start Claude Code and ask about TRON:"
  echo "    💬 \"Check TRX balance for this address: TAddr...\""
  echo "    💬 \"Is the USDT contract safe?\""
  echo "    💬 \"Estimate how much Energy a USDT transfer needs\""
}

setup_cursor() {
  info "Detected: Cursor"

  # Symlink skills to current project
  if [ -f "package.json" ] || [ -d ".git" ]; then
    ln -sf "$INSTALL_DIR/skills" ./tronlink-skills 2>/dev/null || true
    cp -n "$INSTALL_DIR/AGENTS.md" ./ 2>/dev/null || true
    ok "Linked skills to current project"
  fi

  # Set up as .cursor-plugin if possible
  CURSOR_RULES="$HOME/.cursor/rules"
  mkdir -p "$CURSOR_RULES" 2>/dev/null || true
  if [ -d "$CURSOR_RULES" ]; then
    cp "$INSTALL_DIR/.cursor-plugin/plugin.json" "$CURSOR_RULES/tronlink-skills.json" 2>/dev/null || true
    ok "Registered in Cursor rules"
  fi

  # Also offer MCP
  echo ""
  info "For deeper integration, add MCP server in Cursor Settings → MCP:"
  echo ""
  echo "  Server name: tronlink"
  echo "  Command: node"
  echo "  Args: $INSTALL_DIR/scripts/mcp_server.mjs"
  echo ""

  ok "Cursor setup complete!"
}

setup_codex() {
  info "Detected: Codex CLI"

  mkdir -p "$HOME/.agents/skills"
  ln -sf "$INSTALL_DIR/skills" "$HOME/.agents/skills/tronlink-skills"
  ok "Symlinked to ~/.agents/skills/tronlink-skills"

  # Copy AGENTS.md to project if applicable
  if [ -f "package.json" ] || [ -d ".git" ]; then
    if [ ! -f "AGENTS.md" ]; then
      cp "$INSTALL_DIR/AGENTS.md" ./ 2>/dev/null || true
      ok "Added AGENTS.md to current project"
    fi
  fi

  ok "Codex setup complete! Restart Codex to discover skills."
}

setup_opencode() {
  info "Detected: OpenCode"

  OPENCODE_DIR="$HOME/.config/opencode"
  mkdir -p "$OPENCODE_DIR/plugins" "$OPENCODE_DIR/skills"

  # Symlink plugin
  ln -sf "$INSTALL_DIR/.opencode/plugins/tronlink-skills.js" \
         "$OPENCODE_DIR/plugins/tronlink-skills.js" 2>/dev/null || true

  # Symlink skills
  ln -sf "$INSTALL_DIR/skills" \
         "$OPENCODE_DIR/skills/tronlink-skills" 2>/dev/null || true

  ok "OpenCode setup complete! Restart OpenCode to load skills."
}

setup_windsurf() {
  info "Detected: Windsurf"

  # Similar to Cursor
  if [ -f "package.json" ] || [ -d ".git" ]; then
    ln -sf "$INSTALL_DIR/skills" ./tronlink-skills 2>/dev/null || true
    cp -n "$INSTALL_DIR/AGENTS.md" ./ 2>/dev/null || true
    ok "Linked skills to current project"
  fi

  echo ""
  info "Add MCP server in Windsurf settings for deeper integration:"
  echo "  node $INSTALL_DIR/scripts/mcp_server.mjs"

  ok "Windsurf setup complete!"
}

setup_generic() {
  info "No specific AI environment detected"
  echo ""
  echo "  Skills installed to: $INSTALL_DIR"
  echo ""
  echo "  Manual integration options:"
  echo ""
  echo "  ┌─────────────────────────────────────────────────────────────┐"
  echo "  │ Claude Code:                                                │"
  echo "  │   claude mcp add tronlink -- node \\                        │"
  echo "  │     $INSTALL_DIR/scripts/mcp_server.mjs                    │"
  echo "  │                                                             │"
  echo "  │ Claude Desktop (claude_desktop_config.json):                │"
  echo "  │   { \"mcpServers\": { \"tronlink\": {                          │"
  echo "  │       \"command\": \"node\",                                    │"
  echo "  │       \"args\": [\"$INSTALL_DIR/scripts/mcp_server.mjs\"]      │"
  echo "  │   }}}                                                       │"
  echo "  │                                                             │"
  echo "  │ Direct CLI:                                                 │"
  echo "  │   node $INSTALL_DIR/scripts/tron_api.mjs --help            │"
  echo "  └─────────────────────────────────────────────────────────────┘"
  echo ""
}

# ──────────────────────────────────────────────────────────────
# Step 4: Verify Node.js
# ──────────────────────────────────────────────────────────────

check_node() {
  if ! command -v node >/dev/null 2>&1; then
    warn "Node.js not found. Install Node.js 18+ first:"
    echo "  https://nodejs.org"
    exit 1
  fi

  NODE_VERSION=$(node -v | sed 's/v//' | cut -d. -f1)
  if [ "$NODE_VERSION" -lt 18 ] 2>/dev/null; then
    warn "Node.js $NODE_VERSION found, but 18+ required"
    echo "  Update: https://nodejs.org"
    exit 1
  fi

  ok "Node.js $(node -v) detected"
}

# ──────────────────────────────────────────────────────────────
# Main
# ──────────────────────────────────────────────────────────────

main() {
  echo ""
  echo "  ╔══════════════════════════════════════════╗"
  echo "  ║     TronLink Skills Installer v${VERSION}     ║"
  echo "  ║  Wallet · Market · Swap · Energy · Stake ║"
  echo "  ╚══════════════════════════════════════════╝"
  echo ""

  check_node
  clone_or_update

  ENV=$(detect_env)

  case "$ENV" in
    claude-code) setup_claude_code ;;
    cursor)      setup_cursor ;;
    codex)       setup_codex ;;
    opencode)    setup_opencode ;;
    windsurf)    setup_windsurf ;;
    *)           setup_generic ;;
  esac

  echo ""
  echo "  ──────────────────────────────────────────"
  echo "  Quick test:"
  echo "    node $INSTALL_DIR/scripts/tron_api.mjs validate-address \\"
  echo "      --address TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
  echo ""
  echo "  All commands:"
  echo "    node $INSTALL_DIR/scripts/tron_api.mjs --help"
  echo ""
  echo "  Docs: $INSTALL_DIR/docs/claude-integration-guide.md"
  echo "  ──────────────────────────────────────────"
  echo ""
}

main


path: package-lock.json
content: {
  "name": "tronlink-skills",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "tronlink-skills",
      "version": "1.0.0",
      "hasInstallScript": true,
      "license": "MIT",
      "dependencies": {
        "tronweb": "^6.2.2"
      },
      "bin": {
        "tron-api": "scripts/tron_api.mjs"
      },
      "engines": {
        "node": ">=18.0.0"
      },
      "optionalDependencies": {
        "tronweb": "^6.2.2"
      }
    },
    "node_modules/@adraffy/ens-normalize": {
      "version": "1.10.1",
      "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true
    },
    "node_modules/@babel/runtime": {
      "version": "7.26.10",
      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "regenerator-runtime": "^0.14.0"
      },
      "engines": {
        "node": ">=6.9.0"
      }
    },
    "node_modules/@noble/curves": {
      "version": "1.4.2",
      "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "@noble/hashes": "1.4.0"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@noble/hashes": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">= 16"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@scure/base": {
      "version": "1.1.9",
      "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@scure/bip32": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "@noble/curves": "~1.4.0",
        "@noble/hashes": "~1.4.0",
        "@scure/base": "~1.1.6"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@scure/bip39": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "@noble/hashes": "~1.4.0",
        "@scure/base": "~1.1.6"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@types/node": {
      "version": "22.7.5",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "undici-types": "~6.19.2"
      }
    },
    "node_modules/aes-js": {
      "version": "4.0.0-beta.5",
      "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true
    },
    "node_modules/asynckit": {
      "version": "0.4.0",
      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true
    },
    "node_modules/axios": {
      "version": "1.13.5",
      "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "follow-redirects": "^1.15.11",
        "form-data": "^4.0.5",
        "proxy-from-env": "^1.1.0"
      }
    },
    "node_modules/bignumber.js": {
      "version": "9.1.2",
      "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": "*"
      }
    },
    "node_modules/call-bind-apply-helpers": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "es-errors": "^1.3.0",
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/combined-stream": {
      "version": "1.0.8",
      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "delayed-stream": "~1.0.0"
      },
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/delayed-stream": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">=0.4.0"
      }
    },
    "node_modules/dunder-proto": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.1",
        "es-errors": "^1.3.0",
        "gopd": "^1.2.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-define-property": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-errors": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-object-atoms": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "es-errors": "^1.3.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-set-tostringtag": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "es-errors": "^1.3.0",
        "get-intrinsic": "^1.2.6",
        "has-tostringtag": "^1.0.2",
        "hasown": "^2.0.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/ethereum-cryptography": {
      "version": "2.2.1",
      "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "@noble/curves": "1.4.2",
        "@noble/hashes": "1.4.0",
        "@scure/bip32": "1.4.0",
        "@scure/bip39": "1.3.0"
      }
    },
    "node_modules/ethers": {
      "version": "6.13.5",
      "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.5.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "individual",
          "url": "https://github.com/sponsors/ethers-io/"
        },
        {
          "type": "individual",
          "url": "https://www.buymeacoffee.com/ricmoo"
        }
      ],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "@adraffy/ens-normalize": "1.10.1",
        "@noble/curves": "1.2.0",
        "@noble/hashes": "1.3.2",
        "@types/node": "22.7.5",
        "aes-js": "4.0.0-beta.5",
        "tslib": "2.7.0",
        "ws": "8.17.1"
      },
      "engines": {
        "node": ">=14.0.0"
      }
    },
    "node_modules/ethers/node_modules/@noble/curves": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "@noble/hashes": "1.3.2"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/ethers/node_modules/@noble/hashes": {
      "version": "1.3.2",
      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">= 16"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/eventemitter3": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true
    },
    "node_modules/follow-redirects": {
      "version": "1.15.11",
      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "individual",
          "url": "https://github.com/sponsors/RubenVerborgh"
        }
      ],
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">=4.0"
      },
      "peerDependenciesMeta": {
        "debug": {
          "optional": true
        }
      }
    },
    "node_modules/form-data": {
      "version": "4.0.5",
      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "asynckit": "^0.4.0",
        "combined-stream": "^1.0.8",
        "es-set-tostringtag": "^2.1.0",
        "hasown": "^2.0.2",
        "mime-types": "^2.1.12"
      },
      "engines": {
        "node": ">= 6"
      }
    },
    "node_modules/function-bind": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/get-intrinsic": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.2",
        "es-define-property": "^1.0.1",
        "es-errors": "^1.3.0",
        "es-object-atoms": "^1.1.1",
        "function-bind": "^1.1.2",
        "get-proto": "^1.0.1",
        "gopd": "^1.2.0",
        "has-symbols": "^1.1.0",
        "hasown": "^2.0.2",
        "math-intrinsics": "^1.1.0"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/get-proto": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "dunder-proto": "^1.0.1",
        "es-object-atoms": "^1.0.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/google-protobuf": {
      "version": "3.21.4",
      "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "(BSD-3-Clause AND Apache-2.0)",
      "optional": true
    },
    "node_modules/gopd": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/has-symbols": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/has-tostringtag": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "has-symbols": "^1.0.3"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/hasown": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/math-intrinsics": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/mime-db": {
      "version": "1.52.0",
      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/mime-types": {
      "version": "2.1.35",
      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "mime-db": "1.52.0"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/proxy-from-env": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true
    },
    "node_modules/regenerator-runtime": {
      "version": "0.14.1",
      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true
    },
    "node_modules/semver": {
      "version": "7.7.1",
      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "optional": true,
      "bin": {
        "semver": "bin/semver.js"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/tronweb": {
      "version": "6.2.2",
      "resolved": "https://registry.npmjs.org/tronweb/-/tronweb-6.2.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "@babel/runtime": "7.26.10",
        "axios": "1.13.5",
        "bignumber.js": "9.1.2",
        "ethereum-cryptography": "2.2.1",
        "ethers": "6.13.5",
        "eventemitter3": "5.0.1",
        "google-protobuf": "3.21.4",
        "semver": "7.7.1",
        "validator": "13.15.23"
      }
    },
    "node_modules/tslib": {
      "version": "2.7.0",
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "0BSD",
      "optional": true
    },
    "node_modules/undici-types": {
      "version": "6.19.8",
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true
    },
    "node_modules/validator": {
      "version": "13.15.23",
      "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.23.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">= 0.10"
      }
    },
    "node_modules/ws": {
      "version": "8.17.1",
      "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">=10.0.0"
      },
      "peerDependencies": {
        "bufferutil": "^4.0.1",
        "utf-8-validate": ">=5.0.2"
      },
      "peerDependenciesMeta": {
        "bufferutil": {
          "optional": true
        },
        "utf-8-validate": {
          "optional": true
        }
      }
    }
  }
}


path: package.json
content: {
  "name": "tronlink-skills",
  "version": "1.0.1",
  "description": "AI Agent skills for TronLink wallet — wallet management, token queries, DEX swap, energy/bandwidth resource management, and TRX staking on the TRON network.",
  "main": "scripts/tron_api.mjs",
  "type": "module",
  "bin": {
    "tron-api": "scripts/tron_api.mjs"
  },
  "files": [
    "skills/",
    "scripts/",
    "docs/",
    "AGENTS.md",
    "CLAUDE.md",
    ".claude-plugin/",
    ".cursor-plugin/",
    ".codex/",
    ".opencode/"
  ],
  "scripts": {
    "start": "node scripts/tron_api.mjs",
    "mcp": "node scripts/mcp_server.mjs",
    "test": "node scripts/tron_api.mjs validate-address --address TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
    "postinstall": "echo '\\n  ✅ tronlink-skills installed!\\n  Skills: tron-wallet, tron-token, tron-market, tron-swap, tron-resource, tron-staking\\n  Run: npx tron-api --help\\n'"
  },
  "dependencies": {},
  "keywords": [
    "tron", "tronlink", "trx", "trc20", "defi", "wallet",
    "staking", "energy", "bandwidth", "sunswap", "ai-agent",
    "skills", "claude-code", "cursor", "codex", "opencode", "web3"
  ],
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/TronLink/tronlink-skills"
  },
  "engines": {
    "node": ">=18.0.0"
  }
}


path: README.md
content: # TronLink Wallet Skills

AI Agent skills for TronLink wallet and the TRON ecosystem. Provides wallet management, token queries, market data, DEX swap, resource (Energy/Bandwidth) management, and TRX staking across the TRON network.

**Node.js >= 18 required** (uses native `fetch` and `crypto`).

## Why TRON Needs Its Own Skill Set

TRON has a unique resource model (Energy & Bandwidth) that differs fundamentally from EVM gas. Transactions consume Bandwidth; smart contract calls consume Energy. Users must stake TRX (Stake 2.0) or burn TRX to obtain these resources. This makes TRON wallet operations, cost estimation, and transaction planning significantly different from Ethereum-based chains — and no existing AI agent skill covers this properly.

## Installation — One Command

### Recommended (auto-detects your AI environment)

```bash
curl -sSL https://raw.githubusercontent.com/TronLink/tronlink-skills/main/install.sh | sh
```

This automatically detects Claude Code, Cursor, Codex, OpenCode, or Windsurf and configures everything.

### Uninstall

```bash
# If you still have the repo locally:
sh uninstall.sh

# Or run remotely:
curl -sSL https://raw.githubusercontent.com/TronLink/tronlink-skills/main/uninstall.sh | sh
```

Removes MCP registrations, symlinks, copied config files, and `~/.tronlink-skills`.

### Claude Code

```bash
# Option A: Vercel Skills CLI
npx skills add TronLink/tronlink-skills

# Option B: Claude Code plugin marketplace
/plugin marketplace add TronLink/tronlink-skills
/plugin install tronlink-skills

# Option C: MCP Server (manual)
claude mcp add tronlink -- node ~/.tronlink-skills/scripts/mcp_server.mjs
```

### Cursor / Windsurf

```bash
npx skills add TronLink/tronlink-skills
```

### Codex CLI

```
Fetch and follow instructions from https://raw.githubusercontent.com/TronLink/tronlink-skills/main/.codex/INSTALL.md
```

## Available Skills

| Skill | Description |
|-------|-------------|
| `tron-wallet` | Wallet setup, TRX/TRC-20 balances, transaction history, account info |
| `tron-token` | Token search, metadata, contract verification, holder analysis, trending tokens |
| `tron-market` | Real-time prices, K-line data, trade history, whale monitoring, smart money signals |
| `tron-swap` | DEX swap quote & route (SunSwap, etc.), transaction status |
| `tron-resource` | Energy & Bandwidth query, estimation, cost optimization |
| `tron-staking` | SR list, staking info, APY estimation |

## Quick Start

```bash
# Clone the repo
git clone https://github.com/TronLink/tronlink-skills.git
cd tronlink-skills

# Check TRX balance
node scripts/tron_api.mjs wallet-balance --address TXYZabc123...

# Get token info
node scripts/tron_api.mjs token-info --contract TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t

# Get swap quote
node scripts/tron_api.mjs swap-quote \
  --from-token TRX \
  --to-token TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t \
  --amount 100

# Check energy & bandwidth
node scripts/tron_api.mjs resource-info --address TXYZabc123...

```

## Zero Dependencies for Read Operations

The CLI uses **native Node.js `fetch`** (Node 18+) and **native `crypto`** for all read-only operations. No `npm install` needed for:

- Balance queries, token info, market data
- Resource estimation, energy price
- Transaction history, address validation
- SR list, staking APY calculation

## Prerequisites

- **Node.js 18+** (for native `fetch` support)
- TronGrid API Key (optional — apply at [TronGrid Dashboard](https://www.trongrid.io/dashboard))

```bash
# Optional: higher rate limits
export TRONGRID_API_KEY="your-api-key"

# Optional: switch network
export TRON_NETWORK="mainnet"  # or "shasta" / "nile"
```

## Supported Networks

| Network | Endpoint |
|---------|----------|
| Mainnet | https://api.trongrid.io |
| Shasta Testnet | https://api.shasta.trongrid.io |
| Nile Testnet | https://nile.trongrid.io |

## Skill Workflows

**Check Balance**: `tron-wallet` (check balance) → `tron-resource` (estimate energy cost)

**Research Token**: `tron-token` (search token) → `tron-market` (price/chart) → `tron-swap` (get quote)

**Staking Info**: `tron-wallet` (check TRX balance) → `tron-staking` (staking info, SR list, APY)

**Resource Optimization**: `tron-resource` (check energy/bandwidth) → `tron-resource` (estimate cost) → `tron-resource` (rent energy)

## Architecture

```
Natural Language Input
    ↓
AI Agent (Claude Code / Cursor / OpenClaw / Custom)
    ↓
tron_api.mjs (Node.js 18+, native fetch)
    ↓  ← TronGrid API Key (optional)
    └── TronGrid HTTP API (read operations — zero deps)
    ↓
Structured JSON → Agent interprets → Natural language response
```

## All Commands (30 total)

### Wallet (6 commands)
```
wallet-balance      token-balance       wallet-tokens       tx-history
account-info        validate-address
```

### Token (7 commands)
```
token-info          token-search        contract-info       token-holders
trending-tokens     token-rankings      token-security
```

### Market (8 commands)
```
token-price         kline               trade-history       dex-volume
whale-transfers     large-transfers     pool-info           market-overview
```

### Swap (3 commands)
```
swap-quote          swap-route          tx-status
```

### Resource (6 commands)
```
resource-info       estimate-energy     estimate-bandwidth  energy-price
energy-rental       optimize-cost
```

### Staking (3 commands)
```
sr-list             staking-info        staking-apy
```

## Compatible Platforms

| Platform | Status | Notes |
|----------|--------|-------|
| Claude Code | ✅ | Clone repo, add SKILL.md to project context |
| Cursor | ✅ | Clone into project, reference SKILL.md |
| OpenClaw | ✅ | Native skill support |
| Manus | ✅ | Auto-install and execute |
| Dify | ✅ | Use as Code node or API Tool |
| LangChain / CrewAI | ✅ | Wrap as a Tool |

## Security Notes

- This skill set is **read-only** — no private keys or signing operations
- All operations use public TronGrid API (rate-limited without API key)
- Built-in token symbols (TRX, USDT, USDC, etc.) auto-resolve to verified contracts

## License

MIT License Copyright (c) 2026 TronLink


path: scripts/mcp_server.mjs
content: #!/usr/bin/env node

/**
 * mcp_server.mjs — MCP (Model Context Protocol) Server for TronLink Skills
 *
 * Wraps tron_api.mjs commands as MCP tools, so Claude Desktop / Claude Code
 * can call them directly as function tools.
 *
 * Usage:
 *   node scripts/mcp_server.mjs
 *
 * Configure in Claude Desktop (claude_desktop_config.json):
 *   {
 *     "mcpServers": {
 *       "tronlink": {
 *         "command": "node",
 *         "args": ["/absolute/path/to/tronlink-skills/scripts/mcp_server.mjs"],
 *         "env": {
 *           "TRONGRID_API_KEY": "your-optional-api-key"
 *         }
 *       }
 *     }
 *   }
 *
 * Configure in Claude Code:
 *   claude mcp add tronlink -- node /absolute/path/to/tronlink-skills/scripts/mcp_server.mjs
 */

import { execFile } from "node:child_process";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import { createInterface } from "node:readline";

const __dirname = dirname(fileURLToPath(import.meta.url));
const TRON_API = join(__dirname, "tron_api.mjs");

// ---------------------------------------------------------------------------
// MCP Tool Definitions
// ---------------------------------------------------------------------------

const TOOLS = [
  // === Wallet ===
  {
    name: "tron_wallet_balance",
    description: "Check TRX balance and account overview for a TRON address",
    inputSchema: {
      type: "object",
      properties: { address: { type: "string", description: "TRON address (T...)" } },
      required: ["address"],
    },
    command: ["wallet-balance", "--address"],
    argKeys: ["address"],
  },
  {
    name: "tron_token_balance",
    description: "Check TRC-20 token balance. Supports symbol shortcuts (USDT, USDC) or contract address.",
    inputSchema: {
      type: "object",
      properties: {
        address: { type: "string", description: "TRON address" },
        contract: { type: "string", description: "Token contract or symbol (e.g. USDT, TR7NHq...)" },
      },
      required: ["address", "contract"],
    },
    command: ["token-balance", "--address", null, "--contract"],
    argKeys: ["address", "contract"],
  },
  {
    name: "tron_wallet_tokens",
    description: "List all TRC-20 token holdings for an address",
    inputSchema: {
      type: "object",
      properties: { address: { type: "string", description: "TRON address" } },
      required: ["address"],
    },
    command: ["wallet-tokens", "--address"],
    argKeys: ["address"],
  },
  {
    name: "tron_tx_history",
    description: "Get recent transaction history for a TRON address",
    inputSchema: {
      type: "object",
      properties: {
        address: { type: "string", description: "TRON address" },
        limit: { type: "number", description: "Number of transactions (default 20)" },
      },
      required: ["address"],
    },
    command: ["tx-history", "--address"],
    argKeys: ["address"],
    optionalArgs: { limit: "--limit" },
  },
  {
    name: "tron_account_info",
    description: "Get detailed account info including resources, frozen balances, and votes",
    inputSchema: {
      type: "object",
      properties: { address: { type: "string", description: "TRON address" } },
      required: ["address"],
    },
    command: ["account-info", "--address"],
    argKeys: ["address"],
  },
  {
    name: "tron_validate_address",
    description: "Validate whether a string is a valid TRON address",
    inputSchema: {
      type: "object",
      properties: { address: { type: "string", description: "Address to validate" } },
      required: ["address"],
    },
    command: ["validate-address", "--address"],
    argKeys: ["address"],
  },

  // === Token ===
  {
    name: "tron_token_info",
    description: "Get token metadata: name, symbol, supply, holders, price, social links",
    inputSchema: {
      type: "object",
      properties: { contract: { type: "string", description: "Token contract or symbol" } },
      required: ["contract"],
    },
    command: ["token-info", "--contract"],
    argKeys: ["contract"],
  },
  {
    name: "tron_token_search",
    description: "Search TRC-20 tokens by name or symbol keyword",
    inputSchema: {
      type: "object",
      properties: { keyword: { type: "string", description: "Search keyword" } },
      required: ["keyword"],
    },
    command: ["token-search", "--keyword"],
    argKeys: ["keyword"],
  },
  {
    name: "tron_token_holders",
    description: "Get top holders of a TRC-20 token with balance and percentage",
    inputSchema: {
      type: "object",
      properties: {
        contract: { type: "string", description: "Token contract address" },
        limit: { type: "number", description: "Number of holders (default 20)" },
      },
      required: ["contract"],
    },
    command: ["token-holders", "--contract"],
    argKeys: ["contract"],
    optionalArgs: { limit: "--limit" },
  },
  {
    name: "tron_trending_tokens",
    description: "Get trending tokens on TRON by 24h volume",
    inputSchema: { type: "object", properties: {} },
    command: ["trending-tokens"],
    argKeys: [],
  },
  {
    name: "tron_token_rankings",
    description: "Rank TRC-20 tokens by market_cap, volume, holders, gainers, or losers",
    inputSchema: {
      type: "object",
      properties: {
        sort_by: {
          type: "string",
          description: "Sort metric",
          enum: ["market_cap", "volume", "holders", "gainers", "losers"],
        },
      },
    },
    command: ["token-rankings"],
    argKeys: [],
    optionalArgs: { sort_by: "--sort-by" },
  },
  {
    name: "tron_token_security",
    description: "Security audit for a TRC-20 [REDACTED_SECRET], holder concentration, risk assessment",
    inputSchema: {
      type: "object",
      properties: { contract: { type: "string", description: "Token contract address" } },
      required: ["contract"],
    },
    command: ["token-security", "--contract"],
    argKeys: ["contract"],
  },

  // === Market ===
  {
    name: "tron_token_price",
    description: "Get current price for TRX or any TRC-20 token",
    inputSchema: {
      type: "object",
      properties: { contract: { type: "string", description: "Token contract, symbol, or 'TRX'" } },
      required: ["contract"],
    },
    command: ["token-price", "--contract"],
    argKeys: ["contract"],
  },
  {
    name: "tron_kline",
    description: "Get K-line (candlestick OHLCV) data for a token",
    inputSchema: {
      type: "object",
      properties: {
        contract: { type: "string", description: "Token contract or symbol" },
        interval: { type: "string", description: "Interval: 1m, 5m, 15m, 1h, 4h, 1d, 1w" },
        limit: { type: "number", description: "Number of candles" },
      },
      required: ["contract"],
    },
    command: ["kline", "--contract"],
    argKeys: ["contract"],
    optionalArgs: { interval: "--interval", limit: "--limit" },
  },
  {
    name: "tron_whale_transfers",
    description: "Monitor large token transfers (whale activity)",
    inputSchema: {
      type: "object",
      properties: {
        contract: { type: "string", description: "Token contract" },
        min_value: { type: "number", description: "Minimum value filter" },
      },
      required: ["contract"],
    },
    command: ["whale-transfers", "--contract"],
    argKeys: ["contract"],
    optionalArgs: { min_value: "--min-value" },
  },
  {
    name: "tron_market_overview",
    description: "Get TRON network overview: TRX price, total accounts, transactions, blocks",
    inputSchema: { type: "object", properties: {} },
    command: ["market-overview"],
    argKeys: [],
  },

  // === Swap ===
  {
    name: "tron_swap_quote",
    description: "Get DEX swap quote with best route across SunSwap V2/V3. Amount is human-readable.",
    inputSchema: {
      type: "object",
      properties: {
        from_token: { type: "string", description: "Source token (TRX, USDT, or contract)" },
        to_token: { type: "string", description: "Target token" },
        amount: { type: "string", description: "Human-readable amount (e.g. '100' for 100 TRX)" },
      },
      required: ["from_token", "to_token", "amount"],
    },
    command: ["swap-quote", "--from-token", null, "--to-token", null, "--amount"],
    argKeys: ["from_token", "to_token", "amount"],
  },
  {
    name: "tron_tx_status",
    description: "Check transaction status, energy/bandwidth used, and result",
    inputSchema: {
      type: "object",
      properties: { txid: { type: "string", description: "Transaction hash" } },
      required: ["txid"],
    },
    command: ["tx-status", "--txid"],
    argKeys: ["txid"],
  },

  // === Resource ===
  {
    name: "tron_resource_info",
    description: "Get Energy and Bandwidth status for an address. Shows remaining, used, and tips.",
    inputSchema: {
      type: "object",
      properties: { address: { type: "string", description: "TRON address" } },
      required: ["address"],
    },
    command: ["resource-info", "--address"],
    argKeys: ["address"],
  },
  {
    name: "tron_energy_price",
    description: "Get current Energy price and common operation costs (USDT transfer, swap, etc.)",
    inputSchema: { type: "object", properties: {} },
    command: ["energy-price"],
    argKeys: [],
  },
  {
    name: "tron_estimate_energy",
    description: "Estimate energy consumption and TRX cost for a smart contract call",
    inputSchema: {
      type: "object",
      properties: {
        contract: { type: "string", description: "Contract address" },
        func: { type: "string", description: "Function signature, e.g. transfer(address,uint256)" },
        caller: { type: "string", description: "Caller address" },
        params: { type: "string", description: "ABI-encoded parameters (optional)" },
      },
      required: ["contract", "func", "caller"],
    },
    command: ["estimate-energy", "--contract", null, "--function", null, "--caller"],
    argKeys: ["contract", "func", "caller"],
    optionalArgs: { params: "--params" },
  },
  {
    name: "tron_optimize_cost",
    description: "Personalized cost optimization report: freeze vs. rent vs. burn analysis",
    inputSchema: {
      type: "object",
      properties: { address: { type: "string", description: "TRON address" } },
      required: ["address"],
    },
    command: ["optimize-cost", "--address"],
    argKeys: ["address"],
  },

  // === Staking ===
  {
    name: "tron_sr_list",
    description: "List TRON Super Representatives with votes, ranking, and efficiency",
    inputSchema: {
      type: "object",
      properties: { limit: { type: "number", description: "Number of SRs (default 30)" } },
    },
    command: ["sr-list"],
    argKeys: [],
    optionalArgs: { limit: "--limit" },
  },
  {
    name: "tron_staking_info",
    description: "Get staking overview: frozen TRX, votes, pending unfreezes, unclaimed rewards",
    inputSchema: {
      type: "object",
      properties: { address: { type: "string", description: "TRON address" } },
      required: ["address"],
    },
    command: ["staking-info", "--address"],
    argKeys: ["address"],
  },
  {
    name: "tron_staking_apy",
    description: "Estimate staking APY and rewards for a given TRX amount",
    inputSchema: {
      type: "object",
      properties: { amount: { type: "string", description: "TRX amount to stake (default 10000)" } },
    },
    command: ["staking-apy"],
    argKeys: [],
    optionalArgs: { amount: "--amount" },
  },
];

// ---------------------------------------------------------------------------
// Execute tron_api.mjs command
// ---------------------------------------------------------------------------

function buildArgs(tool, input) {
  const args = [...tool.command];

  // Fill in required positional args
  let keyIdx = 0;
  for (let i = 0; i < args.length; i++) {
    if (args[i] === null && keyIdx < tool.argKeys.length) {
      args[i] = String(input[tool.argKeys[keyIdx]] || "");
      keyIdx++;
    } else if (args[i]?.startsWith("--") && keyIdx < tool.argKeys.length) {
      // Next position is the value
      const nextKey = tool.argKeys[keyIdx];
      if (i + 1 >= args.length || args[i + 1]?.startsWith("--")) {
        args.splice(i + 1, 0, String(input[nextKey] || ""));
        keyIdx++;
      }
    }
  }

  // For simple pattern: ["cmd", "--flag"] + argKeys mapping
  for (const key of tool.argKeys) {
    if (input[key] !== undefined && !args.includes(String(input[key]))) {
      args.push(String(input[key]));
    }
  }

  // Optional args
  if (tool.optionalArgs) {
    for (const [inputKey, flag] of Object.entries(tool.optionalArgs)) {
      if (input[inputKey] !== undefined && input[inputKey] !== null) {
        args.push(flag, String(input[inputKey]));
      }
    }
  }

  return args;
}

function runCommand(args) {
  return new Promise((resolve) => {
    execFile("node", [TRON_API, ...args], { timeout: 30000 }, (err, stdout, stderr) => {
      if (err) {
        resolve({ error: err.message, stderr: stderr?.trim() });
      } else {
        try {
          resolve(JSON.parse(stdout));
        } catch {
          resolve({ output: stdout.trim() });
        }
      }
    });
  });
}

// ---------------------------------------------------------------------------
// MCP JSON-RPC Protocol (stdio transport)
// ---------------------------------------------------------------------------

const rl = createInterface({ input: process.stdin, terminal: false });
let buffer = "";

function sendResponse(id, result) {
  const msg = JSON.stringify({ jsonrpc: "2.0", id, result }) + "\n";
  process.stdout.write(msg);
}

function sendError(id, code, message) {
  const msg = JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } }) + "\n";
  process.stdout.write(msg);
}

async function handleRequest(req) {
  const { id, method, params } = req;

  switch (method) {
    case "initialize":
      sendResponse(id, {
        protocolVersion: "2024-11-05",
        capabilities: { tools: {} },
        serverInfo: { name: "tronlink-skills", version: "1.0.0" },
      });
      break;

    case "notifications/initialized":
      // No response needed
      break;

    case "tools/list":
      sendResponse(id, {
        tools: TOOLS.map(t => ({
          name: t.name,
          description: t.description,
          inputSchema: t.inputSchema,
        })),
      });
      break;

    case "tools/call": {
      const toolName = params?.name;
      const toolInput = params?.arguments || {};
      const tool = TOOLS.find(t => t.name === toolName);

      if (!tool) {
        sendError(id, -32602, `Unknown tool: ${toolName}`);
        break;
      }

      const args = buildArgs(tool, toolInput);
      const result = await runCommand(args);

      sendResponse(id, {
        content: [
          {
            type: "text",
            text: typeof result === "string" ? result : JSON.stringify(result, null, 2),
          },
        ],
      });
      break;
    }

    default:
      sendError(id, -32601, `Method not found: ${method}`);
  }
}

rl.on("line", async (line) => {
  buffer += line;
  try {
    const req = JSON.parse(buffer);
    buffer = "";
    await handleRequest(req);
  } catch {
    // Might be partial JSON, wait for more
    if (buffer.length > 100000) buffer = ""; // Safety reset
  }
});

// Signal ready on stderr (convention)
process.stderr.write("TronLink Skills MCP Server started\n");


path: scripts/tron_api.mjs
content: #!/usr/bin/env node

/**
 * tron_api.mjs — Node.js CLI tool for TronLink Wallet Skills
 *
 * Provides wallet management, token queries, market data, DEX swap,
 * resource (Energy/Bandwidth) management, and TRX staking on the TRON network.
 *
 * Requirements: Node.js >= 18 (uses native fetch)
 *
 * Usage: node tron_api.mjs <command> [options]
 */

import { createHash } from "node:crypto";
import { parseArgs } from "node:util";

// ---------------------------------------------------------------------------
// Configuration
// ---------------------------------------------------------------------------

const TRONGRID_API_KEY = process.env.TRONGRID_API_KEY || "";
const TRONSCAN_API_KEY = process.env.TRONSCAN_API_KEY || "";
const TRON_NETWORKS = {
  mainnet: "https://api.trongrid.io",
  shasta: "https://api.shasta.trongrid.io",
  nile: "https://nile.trongrid.io",
};
const NETWORK = process.env.TRON_NETWORK || "mainnet";
const BASE_URL = TRON_NETWORKS[NETWORK] || TRON_NETWORKS.mainnet;
const TRONSCAN_API = "https://apilist.tronscanapi.com/api";
const SUN_PER_TRX = 1_000_000;

// Sun.io Smart Router API — official backend endpoints extracted from sun.io frontend bundle.
// Domain `endjgfsv.link` is Sun.io's CDN; no public api.sun.io/swap/router exists.
// See: https://docs.sun.io/developers/swap/smart-router
const SUNIO_ROUTER_API = {
  mainnet: "https://rot.endjgfsv.link",
  nile:    "https://tnrouter.endjgfsv.link",
};
const SWAP_ROUTER_BASE = SUNIO_ROUTER_API[NETWORK] || SUNIO_ROUTER_API.mainnet;

// CoinGecko free API — used as fallback for market data when TronScan endpoints are unavailable
const COINGECKO_API = "https://api.coingecko.com/api/v3";


const KNOWN_TOKENS = {
  TRX:  { contract: "TRX", symbol: "TRX",  decimals: 6,  name: "TRON" },
  USDT: { contract: [REDACTED_SECRET], symbol: "USDT", decimals: 6,  name: "Tether USD" },
  USDC: { contract: [REDACTED_SECRET], symbol: "USDC", decimals: 6,  name: "USD Coin" },
  WTRX: { contract: [REDACTED_SECRET], symbol: "WTRX", decimals: 6,  name: "Wrapped TRX" },
  BTT:  { contract: [REDACTED_SECRET], symbol: "BTT",  decimals: 18, name: "BitTorrent" },
  JST:  { contract: [REDACTED_SECRET], symbol: "JST",  decimals: 18, name: "JUST" },
  SUN:  { contract: [REDACTED_SECRET], symbol: "SUN",  decimals: 18, name: "SUN Token" },
  WIN:  { contract: [REDACTED_SECRET], symbol: "WIN",  decimals: 6,  name: "WINkLink" },
};

// ---------------------------------------------------------------------------
// HTTP Helpers
// ---------------------------------------------------------------------------

function headers(url = "") {
  const h = { "Content-Type": "application/json", Accept: "application/json" };
  // Only add TRON-PRO-API-KEY for TronGrid requests, not for TronScan
  if (TRONGRID_API_KEY && !url.includes("tronscanapi.com")) {
    h["TRON-PRO-API-KEY"] = TRONGRID_API_KEY;
  }
  return h;
}

async function httpGet(url, params = {}) {
  try {
    // Add TRONSCAN API Key for tronscan API requests (as URL parameter)
    if (url.includes("tronscanapi.com") && TRONSCAN_API_KEY) {
      params.[REDACTED_SECRET];
    }
    const qs = new URLSearchParams(params).toString();
    const fullUrl = qs ? `${url}?${qs}` : url;
    const reqHeaders = headers(url);  // Pass URL to headers() to avoid mixing API keys
    const resp = await fetch(fullUrl, { headers: reqHeaders, signal: AbortSignal.timeout(15000) });
    const data = await resp.json();
    return data;
  } catch (e) {
    return { error: e.message };
  }
}

async function httpPost(url, body = {}) {
  try {
    const resp = await fetch(url, {
      method: "POST",
      headers: headers(url),  // Pass URL to headers()
      body: JSON.stringify(body),
      signal: AbortSignal.timeout(15000),
    });
    return await resp.json();
  } catch (e) {
    return { error: e.message };
  }
}

function fmt(data) {
  return JSON.stringify(data, null, 2);
}

function sunToTrx(sun) { return sun / SUN_PER_TRX; }
function trxToSun(trx) { return Math.round(trx * SUN_PER_TRX); }

// ---------------------------------------------------------------------------
// Base58Check Address Utilities (zero-dependency)
// ---------------------------------------------------------------------------

const B58_ALPHABET = [REDACTED_SECRET];

function b58decode(str) {
  let n = 0n;
  for (const ch of str) {
    const idx = B58_ALPHABET.indexOf(ch);
    if (idx < 0) throw new Error(`Invalid Base58 char: ${ch}`);
    n = n * 58n + BigInt(idx);
  }
  // Convert bigint to Buffer
  let hex = n.toString(16);
  if (hex.length % 2 !== 0) hex = "0" + hex;
  const bytes = Buffer.from(hex, "hex");
  // Count leading '1's
  let pad = 0;
  for (const ch of str) { if (ch === "1") pad++; else break; }
  return Buffer.concat([Buffer.alloc(pad), bytes]);
}

function b58encode(buf) {
  let n = 0n;
  for (const byte of buf) n = n * 256n + BigInt(byte);
  const result = [];
  while (n > 0n) {
    const [q, r] = [n / 58n, n % 58n];
    result.push(B58_ALPHABET[Number(r)]);
    n = q;
  }
  // Leading zeros
  for (const byte of buf) { if (byte === 0) result.push("1"); else break; }
  return result.reverse().join("");
}

function sha256(data) {
  return createHash("sha256").update(data).digest();
}

function isValidTronAddress(address) {
  if (!address || address.length !== 34 || !address.startsWith("T")) return false;
  try {
    const decoded = b58decode(address);
    if (decoded.length !== 25) return false;
    const payload = decoded.subarray(0, -4);
    const checksum = decoded.subarray(-4);
    const hash = sha256(sha256(payload));
    return hash.subarray(0, 4).equals(checksum);
  } catch {
    return false;
  }
}

function hexToBase58(hexAddr) {
  if (hexAddr.startsWith("0x")) hexAddr = "41" + hexAddr.slice(2);
  const addrBuf = Buffer.from(hexAddr, "hex");
  const hash = sha256(sha256(addrBuf));
  return b58encode(Buffer.concat([addrBuf, hash.subarray(0, 4)]));
}

function normalizeAddress(addr) {
  if (addr.startsWith("41") && addr.length === 42) return hexToBase58(addr);
  if (addr.startsWith("0x") && addr.length === 42) return hexToBase58(addr);
  return addr;
}

function resolveToken(input) {
  const upper = input.toUpperCase();
  if (KNOWN_TOKENS[upper]) return KNOWN_TOKENS[upper];
  return { contract: input, symbol: input.slice(0, 8), decimals: 6, name: "Unknown" };
}

// ---------------------------------------------------------------------------
// Wallet Commands
// ---------------------------------------------------------------------------

async function cmdWalletBalance({ address }) {
  address = normalizeAddress(address);
  const data = await httpGet(`${BASE_URL}/v1/accounts/${address}`);
  if (data.error) return console.log(fmt(data));
  if (!data.data?.length) return console.log(fmt({ error: "Account not found. Needs at least 1 TRX to activate." }));

  const acct = data.data[0];
  const frozenV2 = acct.frozenV2 || [];
  const frozenEnergy = frozenV2.filter(f => f.type === "ENERGY").reduce((s, f) => s + (f.amount || 0), 0);
  const frozenBW = frozenV2.filter(f => f.type !== "ENERGY").reduce((s, f) => s + (f.amount || 0), 0);

  console.log(fmt({
    address,
    balance_trx: sunToTrx(acct.balance || 0),
    balance_sun: acct.balance || 0,
    frozen_for_energy_trx: sunToTrx(frozenEnergy),
    frozen_for_bandwidth_trx: sunToTrx(frozenBW),
    create_time: acct.create_time ? new Date(acct.create_time).toISOString() : null,
    network: NETWORK,
  }));
}

async function cmdTokenBalance({ address, contract }) {
  address = normalizeAddress(address);
  const tokenInfo = resolveToken(contract);
  contract = tokenInfo.contract;

  const data = await httpGet(`${BASE_URL}/v1/accounts/${address}`);
  if (data.error || !data.data?.length) return console.log(fmt({ error: "Account not found" }));

  const trc20 = data.data[0].trc20 || [];
  let balance = 0;
  for (const tokenMap of trc20) {
    if (tokenMap[contract]) { balance = parseInt(tokenMap[contract]); break; }
  }

  console.log(fmt({
    address,
    token_contract: contract,
    symbol: tokenInfo.symbol,
    balance_raw: balance,
    balance: balance / (10 ** tokenInfo.decimals),
    decimals: tokenInfo.decimals,
  }));
}

async function cmdWalletTokens({ address }) {
  address = normalizeAddress(address);
  const data = await httpGet(`${BASE_URL}/v1/accounts/${address}`);
  if (data.error || !data.data?.length) return console.log(fmt({ error: "Account not found" }));

  const acct = data.data[0];
  const holdings = [];

  for (const tokenMap of (acct.trc20 || [])) {
    for (const [addr, rawBal] of Object.entries(tokenMap)) {
      const bal = parseInt(rawBal);
      let symbol = addr.slice(0, 8) + "...", decimals = 6, name = "Unknown";
      for (const v of Object.values(KNOWN_TOKENS)) {
        if (v.contract === addr) { symbol = v.symbol; decimals = v.decimals; name = v.name; break; }
      }
      const humanBal = bal / (10 ** decimals);
      if (humanBal > 0) holdings.push({ symbol, name, contract: addr, balance: humanBal });
    }
  }

  console.log(fmt({
    address,
    trx_balance: sunToTrx(acct.balance || 0),
    trc20_tokens: holdings,
    token_count: holdings.length,
  }));
}

async function cmdTxHistory({ address, limit = 20 }) {
  address = normalizeAddress(address);
  const data = await httpGet(`${BASE_URL}/v1/accounts/${address}/transactions`, {
    limit, order_by: "block_timestamp,desc",
  });
  if (data.error) return console.log(fmt(data));

  const txs = (data.data || []).map(tx => {
    const raw = tx.raw_data || {};
    const c = (raw.contract || [{}])[0];
    const val = c.parameter?.value || {};
    return {
      txid: tx.txID || "",
      type: c.type || "Unknown",
      block_timestamp: tx.block_timestamp ? new Date(tx.block_timestamp).toISOString() : null,
      result: (tx.ret || [{}])[0].contractRet || "",
      amount_sun: val.amount || 0,
      amount_trx: val.amount ? sunToTrx(val.amount) : null,
      to: val.to_address ? normalizeAddress(val.to_address) : null,
    };
  });

  console.log(fmt({ address, transactions: txs, count: txs.length }));
}

async function cmdAccountInfo({ address }) {
  address = normalizeAddress(address);
  const data = await httpGet(`${BASE_URL}/v1/accounts/${address}`);
  if (data.error || !data.data?.length) return console.log(fmt({ error: "Account not found" }));

  const acct = data.data[0];
  const resource = await httpPost(`${BASE_URL}/wallet/getaccountresource`, { address, visible: true });

  console.log(fmt({
    address,
    balance_trx: sunToTrx(acct.balance || 0),
    create_time: acct.create_time ? new Date(acct.create_time).toISOString() : null,
    is_witness: acct.is_witness || false,
    frozen_v2: acct.frozenV2 || [],
    unfrozen_v2: acct.unfrozenV2 || [],
    votes: acct.votes || [],
    tron_power: acct.tron_power || {},
    resource_overview: {
      free_bandwidth_limit: resource.freeNetLimit || 600,
      free_bandwidth_used: resource.freeNetUsed || 0,
      staked_bandwidth_limit: resource.NetLimit || 0,
      staked_bandwidth_used: resource.NetUsed || 0,
      energy_limit: resource.EnergyLimit || 0,
      energy_used: resource.EnergyUsed || 0,
    },
    network: NETWORK,
  }));
}

async function cmdValidateAddress({ address }) {
  const data = await httpPost(`${BASE_URL}/wallet/validateaddress`, { address });
  const valid = data.error ? isValidTronAddress(address) : (data.result || false);
  console.log(fmt({
    address,
    valid,
    format: address.startsWith("T") ? "Base58Check" : "Hex",
    normalized: valid ? normalizeAddress(address) : null,
  }));
}

// ---------------------------------------------------------------------------
// Token Commands
// ---------------------------------------------------------------------------

async function cmdTokenInfo({ contract }) {
  const resolved = resolveToken(contract);
  contract = resolved.contract;

  const scanData = await httpGet(`${TRONSCAN_API}/token_trc20`, { contract });
  
  // Debug: check if API returned data
  if (scanData.error) {
    return console.log(fmt({
      error: scanData.error,
      contract,
      hint: "API request failed"
    }));
  }
  
  const [REDACTED_SECRET] || {};
  
  // If no token data, scanData might be empty
  if (!token.symbol) {
    return console.log(fmt({
      contract,
      error: "Token not found in TronScan API",
      api_response_keys: Object.keys(scanData),
      token_count: (scanData.trc20_tokens || []).length
    }));
  }

  const mi = token.market_info || {};
  console.log(fmt({
    contract,
    name: token.name || token.symbol || "Unknown",
    symbol: token.symbol || "Unknown",
    decimals: token.decimals || 0,
    total_supply: token.total_supply_with_decimals || "0",
    holders: token.holders_count || 0,
    transfers: token.transfer_num || 0,
    market_cap_usd: token.market_cap_usd || 0,
    price_usd: mi.priceInUsd || token.price || 0,
    icon_url: token.icon_url || "",
    home_page: token.home_page || "",
    issue_time: token.issue_time || "",
    issuer_addr: token.issue_address || "",
  }));
}

async function cmdTokenSearch({ keyword }) {
  // TronScan search API: use 'term' parameter to find tokens
  const results = await httpGet(`${TRONSCAN_API}/search`, { term: keyword });
  const items = Array.isArray(results) ? results : [];

  // Extract TRC-20 tokens and parse contract address from value string
  const trc20Items = items
    .filter(r => r.desc === "Token-TRC20")
    .map(r => {
      // value format: "TokenName(SYMBOL) ContractAddress"
      const m = r.value?.match(/^(.+?)\((.+?)\)\s+(\w+)$/);
      return m ? { name: m[1], symbol: m[2], contract: m[3] } : null;
    })
    .filter(Boolean)
    .slice(0, 20);

  // Enrich top results with on-chain data (batch up to 5 for speed)
  const enriched = await Promise.all(
    trc20Items.slice(0, 10).map(async (t) => {
      const data = await httpGet(`${TRONSCAN_API}/token_trc20`, { contract: t.contract });
      const [REDACTED_SECRET];
      const mi = token?.market_info || {};
      return {
        name: token?.name || t.name,
        symbol: token?.symbol || t.symbol,
        contract: t.contract,
        holders: token?.holders_count || 0,
        price_usd: mi.priceInUsd || token?.price || 0,
        market_cap: token?.market_cap_usd || 0,
      };
    })
  );

  console.log(fmt({ query: keyword, results: enriched, count: enriched.length }));
}

async function cmdContractInfo({ contract }) {
  const data = await httpGet(`${TRONSCAN_API}/contract`, { contract });
  const c = data.data?.[0] || {};
  console.log(fmt({
    contract,
    name: c.name || "Unknown",
    verified: c.verify_status || 0,
    creator: c.creator?.address || "",
    creation_time: c.date_created || "",
    energy_factor: c.consume_user_resource_percent || 0,
  }));
}

async function cmdTokenHolders({ contract, limit = 20 }) {
  const resolved = resolveToken(contract);
  contract = resolved.contract;

  const data = await httpGet(`${TRONSCAN_API}/token_trc20/holders`, {
    contract_address: contract, limit, start: 0,
  });
  const totalSupply = data.rangeTotal || 0;
  const holders = (data.trc20_tokens || []).map(h => {
    const bal = parseFloat(h.balance || 0);
    return {
      address: h.holder_address || "",
      address_tag: h.addressTag || "",
      balance: bal,
      percentage: totalSupply > 0 ? ((bal / totalSupply) * 100).toFixed(4) + "%" : "N/A",
    };
  });
  console.log(fmt({ contract, holders, total_holders: totalSupply, count: holders.length }));
}

async function cmdTrendingTokens() {
  const data = await httpGet(`${COINGECKO_API}/coins/markets`, {
    vs_currency: "usd", category: "tron-ecosystem",
    order: "volume_desc", per_page: 20, page: 1,
  });
  if (data.error || !Array.isArray(data)) {
    return console.log(fmt({ error: "Failed to fetch trending tokens", detail: data.error || data }));
  }
  const tokens = data.map(t => ({
    name: t.name, symbol: (t.symbol || "").toUpperCase(),
    coingecko_id: t.id,
    price_usd: t.current_price || 0,
    volume_24h: t.total_volume || 0,
    market_cap: t.market_cap || 0,
    change_24h: t.price_change_percentage_24h || 0,
  }));
  console.log(fmt({ trending_tokens: tokens, count: tokens.length, source: "CoinGecko (TRON ecosystem)" }));
}

async function cmdTokenRankings({ sortBy = "market_cap" }) {
  const cgOrder = {
    market_cap: "market_cap_desc", volume: "volume_desc",
    holders: "market_cap_desc", gainers: "market_cap_desc", losers: "market_cap_desc",
  };
  const data = await httpGet(`${COINGECKO_API}/coins/markets`, {
    vs_currency: "usd", category: "tron-ecosystem",
    order: cgOrder[sortBy] || "market_cap_desc", per_page: 20, page: 1,
  });
  if (data.error || !Array.isArray(data)) {
    return console.log(fmt({ error: "Failed to fetch token rankings", detail: data.error || data }));
  }
  let tokens = data.map(t => ({
    name: t.name, symbol: (t.symbol || "").toUpperCase(),
    coingecko_id: t.id,
    price_usd: t.current_price || 0,
    market_cap: t.market_cap || 0,
    volume_24h: t.total_volume || 0,
    change_24h: t.price_change_percentage_24h || 0,
  }));
  // Client-side sort for gainers/losers
  if (sortBy === "gainers") tokens.sort((a, b) => b.change_24h - a.change_24h);
  if (sortBy === "losers") tokens.sort((a, b) => a.change_24h - b.change_24h);
  tokens = tokens.map((t, i) => ({ rank: i + 1, ...t }));
  console.log(fmt({ sort_by: sortBy, tokens, source: "CoinGecko (TRON ecosystem)" }));
}

async function cmdTokenSecurity({ contract }) {
  const resolved = resolveToken(contract);
  contract = resolved.contract;

  const [contractData, holderData, tokenData] = await Promise.all([
    httpGet(`${TRONSCAN_API}/contract`, { contract }),
    httpGet(`${TRONSCAN_API}/token_trc20/holders`, { contract_address: contract, limit: 10, start: 0 }),
    httpGet(`${TRONSCAN_API}/token_trc20`, { contract }),
  ]);

  const c = contractData.data?.[0] || {};
  const t = tokenData.trc20_tokens?.[0] || {};
  const mi = t.market_info || {};
  const totalHolders = holderData.rangeTotal || t.holders_count || 0;
  const topHolders = holderData.trc20_tokens || [];

  // Calculate top-5 holder concentration using raw balances and total supply
  const totalSupplyRaw = parseFloat(t.total_supply_with_decimals || 0);
  const top5Balance = topHolders.slice(0, 5).reduce((s, h) => s + parseFloat(h.balance || 0), 0);
  const top5Pct = totalSupplyRaw > 0 ? (top5Balance / totalSupplyRaw) * 100 : 0;

  console.log(fmt({
    contract,
    name: t.name || "Unknown",
    symbol: t.symbol || "Unknown",
    security_checks: {
      is_verified: Boolean(c.verify_status),
      creator: c.creator?.address || "Unknown",
      creation_date: c.date_created ? new Date(c.date_created).toISOString() : "Unknown",
      top5_holder_concentration_pct: Math.round(top5Pct * 100) / 100,
      holder_count: totalHolders,
      total_transfers: t.transfer_num || 0,
    },
    risk_assessment: {
      concentration_risk: top5Pct > 80 ? "HIGH" : top5Pct > 50 ? "MEDIUM" : "LOW",
      verified_source: c.verify_status ? "PASS" : "FAIL — source not verified",
      holder_risk: totalHolders < 100 ? "HIGH" : "LOW",
      liquidity: mi.volume24hInTrx || t.volume24h || 0,
    },
    recommendation:
      top5Pct > 80 || !c.verify_status || totalHolders < 100
        ? "⚠️ CAUTION" : "✅ Appears relatively safe — always DYOR",
  }));
}

// ---------------------------------------------------------------------------
// Market Commands
// ---------------------------------------------------------------------------

async function cmdTokenPrice({ contract }) {
  if (contract.toUpperCase() === "TRX") {
    const data = await httpGet(`${COINGECKO_API}/simple/price`, {
      ids: "tron", vs_currencies: "usd",
      include_24hr_vol: true, include_24hr_change: true, include_market_cap: true,
    });
    const trx = data.tron || {};
    return console.log(fmt({
      token: "TRX",
      price_usd: trx.usd || 0,
      volume_24h_usd: trx.usd_24h_vol || 0,
      market_cap_usd: trx.usd_market_cap || 0,
      change_24h_pct: trx.usd_24h_change || 0,
      network: "TRON",
    }));
  }

  const resolved = resolveToken(contract);
  contract = resolved.contract;

  const data = await httpGet(`${TRONSCAN_API}/token_trc20`, { contract });
  const [REDACTED_SECRET] || {};
  const mi = token.market_info || {};

  console.log(fmt({
    contract,
    symbol: token.symbol || "Unknown",
    price_usd: mi.priceInUsd || token.price || 0,
    price_in_trx: mi.priceInTrx || 0,
    volume_24h_trx: mi.volume24hInTrx || 0,
    market_cap: token.market_cap_usd || 0,
    change_24h_pct: mi.gain || 0,
  }));
}

async function cmdKline({ contract, interval = "1h", limit = 100 }) {
  const resolved = resolveToken(contract);

  // Map interval to CoinGecko days parameter
  const intervalToDays = { "1m": 1, "5m": 1, "15m": 1, "1h": 1, "4h": 7, "1d": 30, "1w": 180 };
  const days = intervalToDays[interval] || 1;

  // Resolve CoinGecko coin ID
  let cgId;
  if (resolved.contract === "TRX" || resolved.symbol === "TRX") {
    cgId = "tron";
  } else {
    // Look up CoinGecko ID via contract address
    const lookup = await httpGet(`${COINGECKO_API}/coins/tron/contract/${resolved.contract}`);
    cgId = lookup.id;
  }

  if (!cgId) {
    return console.log(fmt({
      info: "Token not found on CoinGecko for K-line data.",
      suggestion: "Use TronScan or SunSwap for detailed chart data.",
      tronscan_url: `https://tronscan.org/#/token20/${resolved.contract}`,
    }));
  }

  const data = await httpGet(`${COINGECKO_API}/coins/${cgId}/ohlc`, {
    vs_currency: "usd", days,
  });

  if (!Array.isArray(data) || data.length === 0) {
    return console.log(fmt({
      info: "K-line data unavailable for this token.",
      tronscan_url: `https://tronscan.org/#/token20/${resolved.contract}`,
    }));
  }

  const candles = data.slice(-limit).map(c => ({
    timestamp: new Date(c[0]).toISOString(),
    open: c[1], high: c[2], low: c[3], close: c[4],
  }));
  console.log(fmt({ contract: resolved.contract, symbol: resolved.symbol, interval, candles, source: "CoinGecko" }));
}

async function cmdTradeHistory({ contract, limit = 50 }) {
  const resolved = resolveToken(contract);
  contract = resolved.contract;

  const data = await httpGet(`${TRONSCAN_API}/token_trc20/transfers`, {
    contract_address: contract, limit, start: 0, sort: "-timestamp",
  });
  const transfers = (data.token_transfers || []).map(t => {
    const decimals = parseInt(t.tokenInfo?.tokenDecimal || 6);
    const amount = parseFloat(t.quant || 0) / (10 ** decimals);
    return {
      txid: t.transaction_id || "",
      timestamp: t.block_ts ? new Date(t.block_ts).toISOString() : "",
      from: t.from_address || "",
      from_tag: t.from_address_tag?.from_address_tag || "",
      to: t.to_address || "",
      to_tag: t.to_address_tag?.to_address_tag || "",
      amount,
      symbol: t.tokenInfo?.tokenAbbr || resolved.symbol,
      confirmed: t.confirmed || false,
    };
  });
  console.log(fmt({ contract, transfers, count: transfers.length }));
}

async function cmdDexVolume({ contract, period = "24h" }) {
  const resolved = resolveToken(contract);
  contract = resolved.contract;

  const data = await httpGet(`${TRONSCAN_API}/token_trc20`, { contract });
  const [REDACTED_SECRET] || {};
  const mi = token.market_info || {};

  console.log(fmt({
    contract,
    symbol: token.symbol || "Unknown",
    period,
    volume_24h_trx: mi.volume24hInTrx || 0,
    volume_24h_usd: token.volume24h || 0,
    liquidity_usd: token.liquidity24h || 0,
    transfers_24h: token.transfer24h || 0,
    price_change: mi.gain || 0,
  }));
}

async function cmdWhaleTransfers({ contract, minValue = 100000 }) {
  const resolved = resolveToken(contract);
  contract = resolved.contract;

  const data = await httpGet(`${TRONSCAN_API}/token_trc20/transfers`, {
    contract_address: contract, limit: 50, start: 0, sort: "-quant",
  });

  const transfers = (data.token_transfers || [])
    .map(t => {
      const decimals = parseInt(t.tokenInfo?.tokenDecimal || 6);
      const amount = parseFloat(t.quant || 0) / (10 ** decimals);
      return {
        txid: t.transaction_id || "", from: t.from_address || "",
        to: t.to_address || "", amount,
        timestamp: t.block_ts || "",
        symbol: t.tokenInfo?.tokenAbbr || "",
      };
    })
    .filter(t => t.amount >= minValue / 100)
    .slice(0, 20);

  console.log(fmt({ contract, min_value_filter: minValue, large_transfers: transfers, count: transfers.length }));
}

async function cmdLargeTransfers({ minTrx = 100000, limit = 20 }) {
  const data = await httpGet(`${TRONSCAN_API}/transaction`, { sort: "-amount", limit, start: 0 });
  const transfers = (data.data || [])
    .map(tx => ({
      txid: tx.hash || "", from: tx.ownerAddress || "",
      to: tx.toAddress || "", amount_trx: sunToTrx(tx.amount || 0),
      timestamp: tx.timestamp || "", confirmed: tx.confirmed || false,
    }))
    .filter(t => t.amount_trx >= minTrx);
  console.log(fmt({ min_trx: minTrx, transfers }));
}

async function cmdPoolInfo({ contract }) {
  const resolved = resolveToken(contract);
  contract = resolved.contract;

  // Use token_trc20 market_info for liquidity data since defi/pools is no longer available
  const data = await httpGet(`${TRONSCAN_API}/token_trc20`, { contract });
  const [REDACTED_SECRET] || {};
  const mi = token.market_info || {};

  console.log(fmt({
    contract,
    symbol: token.symbol || "Unknown",
    liquidity_usd: token.liquidity24h || 0,
    volume_24h_usd: token.volume24h || 0,
    volume_24h_trx: mi.volume24hInTrx || 0,
    price_usd: mi.priceInUsd || 0,
    price_source: mi.priceFrom || "Unknown",
    pair_url: mi.pairUrl || "",
    dex_sources: "SunSwap V2, V3, Sun.io Curve",
    note: "Use swap-quote for specific pool routing details.",
  }));
}

async function cmdMarketOverview() {
  const [sysData, priceData] = await Promise.all([
    httpGet(`${TRONSCAN_API}/system/status`),
    httpGet(`${COINGECKO_API}/simple/price`, {
      ids: "tron", vs_currencies: "usd",
      include_24hr_vol: true, include_24hr_change: true, include_market_cap: true,
    }),
  ]);
  const trx = priceData.tron || {};
  console.log(fmt({
    tron_network: {
      trx_price_usd: trx.usd || 0,
      trx_24h_change: trx.usd_24h_change || 0,
      trx_market_cap: trx.usd_market_cap || 0,
      trx_24h_volume: trx.usd_24h_vol || 0,
      latest_block: sysData.database?.block || sysData.full?.block || 0,
      confirmed_block: sysData.database?.confirmedBlock || sysData.solidity?.block || 0,
      network_env: sysData.network?.env || "unknown",
    },
  }));
}

// ---------------------------------------------------------------------------
// Swap Commands
// ---------------------------------------------------------------------------

async function cmdSwapQuote({ fromToken, toToken, amount }) {
  const fromInfo = resolveToken(fromToken);
  const toInfo = resolveToken(toToken);
  const amountRaw = Math.round(parseFloat(amount) * (10 ** fromInfo.decimals));

  // Sun.io Smart Router requires WTRX address instead of native TRX
  const WTRX_ADDRESS = KNOWN_TOKENS.WTRX?.contract || [REDACTED_SECRET];
  const fromAddr = fromInfo.contract === "TRX" ? WTRX_ADDRESS : fromInfo.contract;

  const data = await httpGet(`${SWAP_ROUTER_BASE}/swap/router`, {
    fromToken: fromAddr, toToken: toInfo.contract,
    amountIn: String(amountRaw),
    typeList: "PSM,CURVE,WTRX,SUNSWAP_V2,SUNSWAP_V3",
  });

  if (data.error || !data.data || !Array.isArray(data.data) || data.data.length === 0) {
    return console.log(fmt({
      from: fromToken, to: toToken, amount_in: parseFloat(amount),
      status: "Quote API unavailable — try SunSwap directly",
      sunswap_url: "https://sunswap.com",
      estimated_energy: "65,000-200,000 (depends on route complexity)",
      estimated_trx_burn: "13-40 TRX (if no staked energy)",
    }));
  }

  // API returns an array of routes sorted by best output; take the first (best) route
  const best = data.data[0];

  console.log(fmt({
    from: fromToken, to: toToken,
    amount_in: parseFloat(amount),
    amount_out: parseFloat(best.amountOut || 0),
    price_impact: best.impact || "N/A",
    fee_pct: best.fee || "N/A",
    path: best.symbols || [],
    pool_versions: best.poolVersions || [],
    routes_available: data.data.length,
    estimated_energy: "65,000-200,000",
    dex_sources: "SunSwap V2, V3, Sun.io Curve, PSM",
  }));
}


async function cmdTxStatus({ txid }) {
  // Use full-node API to get transaction and its on-chain receipt
  const [txData, infoData] = await Promise.all([
    httpPost(`${BASE_URL}/wallet/gettransactionbyid`, { value: txid }),
    httpPost(`${BASE_URL}/wallet/gettransactioninfobyid`, { value: txid }),
  ]);

  if (txData.error || !txData.txID) return console.log(fmt({ error: `Transaction ${txid} not found` }));

  const receipt = infoData.receipt || {};

  console.log(fmt({
    txid,
    status: (txData.ret || [{}])[0].contractRet || "UNKNOWN",
    block_number: infoData.blockNumber || 0,
    timestamp: infoData.blockTimeStamp ? new Date(infoData.blockTimeStamp).toISOString() : null,
    energy_used: receipt.energy_usage_total || 0,
    bandwidth_used: receipt.net_usage || infoData.receipt?.net_usage || 0,
    energy_fee_sun: receipt.energy_fee || 0,
    net_fee_sun: receipt.net_fee || infoData.fee || 0,
    tronscan_url: `https://tronscan.org/#/transaction/${txid}`,
  }));
}

// ---------------------------------------------------------------------------
// Resource Commands
// ---------------------------------------------------------------------------

async function cmdResourceInfo({ address }) {
  address = normalizeAddress(address);
  const data = await httpPost(`${BASE_URL}/wallet/getaccountresource`, { address, visible: true });
  if (data.error) return console.log(fmt(data));

  const freeBwLimit = data.freeNetLimit || 600;
  const freeBwUsed = data.freeNetUsed || 0;
  const stakedBwLimit = data.NetLimit || 0;
  const stakedBwUsed = data.NetUsed || 0;
  const energyLimit = data.EnergyLimit || 0;
  const energyUsed = data.EnergyUsed || 0;

  console.log(fmt({
    address,
    bandwidth: {
      free_remaining: freeBwLimit - freeBwUsed,
      free_total: freeBwLimit,
      free_used: freeBwUsed,
      staked_remaining: stakedBwLimit - stakedBwUsed,
      staked_total: stakedBwLimit,
      staked_used: stakedBwUsed,
    },
    energy: {
      remaining: energyLimit - energyUsed,
      total: energyLimit,
      used: energyUsed,
    },
    tips: {
      free_bandwidth_covers: `~${Math.floor((freeBwLimit - freeBwUsed) / 267)} basic TRX transfers`,
      energy_covers: energyLimit > 0
        ? `~${Math.floor((energyLimit - energyUsed) / 65000)} USDT transfers`
        : "0 — no staked energy, TRX will be burned for smart contract calls",
    },
  }));
}

async function cmdEstimateEnergy({ contract, func, params = "", caller }) {
  const resolved = resolveToken(contract);
  contract = resolved.contract;

  const data = await httpPost(`${BASE_URL}/wallet/triggerconstantcontract`, {
    owner_address: caller,
    contract_address: contract,
    function_selector: func,
    parameter: params,
    visible: true,
  });

  const energy = data.energy_used || 0;
  const energyPriceSun = 420;
  const trxCost = (energy * energyPriceSun) / SUN_PER_TRX;

  console.log(fmt({
    contract, function: func,
    estimated_energy: energy,
    estimated_trx_burn: Math.round(trxCost * 100) / 100,
    note: "TRX burn only applies if you have no staked energy. Freeze TRX to avoid burning.",
    result: data.constant_result || [],
  }));
}

async function cmdEstimateBandwidth({ txSize = 267 }) {
  const freeBw = 600;
  console.log(fmt({
    estimated_bandwidth: txSize,
    free_daily_allowance: freeBw,
    covered_by_free: txSize <= freeBw,
    trx_burn_if_insufficient: Math.round(txSize * 1000 / SUN_PER_TRX * 10000) / 10000,
  }));
}

async function cmdEnergyPrice() {
  const params = await httpGet(`${BASE_URL}/wallet/getchainparameters`);
  let energyFee = 0;
  for (const p of (params.chainParameter || [])) {
    if (p.key === "getEnergyFee") { energyFee = p.value || 0; break; }
  }
  console.log(fmt({
    energy_price_sun: energyFee,
    energy_price_trx_per_10k: Math.round(energyFee * 10000 / SUN_PER_TRX * 10000) / 10000,
    usdt_transfer_cost_trx: Math.round(energyFee * 65000 / SUN_PER_TRX * 100) / 100,
    sunswap_v2_cost_trx: Math.round(energyFee * 130000 / SUN_PER_TRX * 100) / 100,
    note: "Costs shown assume zero staked energy. Staking eliminates these burns.",
  }));
}


async function cmdEnergyRental({ amount = 65000 }) {
  console.log(fmt({
    energy_needed: parseInt(amount),
    rental_platforms: [
      { name: "TronNRG", url: "https://tronnrg.com", description: "Community energy marketplace" },
      { name: "JustLend", url: "https://justlend.org", description: "Official TRON lending + energy rental" },
      { name: "Feee.io", url: "https://feee.io", description: "Energy rental service" },
    ],
    tip: `For ${amount} energy, compare rental price vs. freezing TRX vs. burning TRX.`,
  }));
}

async function cmdOptimizeCost({ address }) {
  address = normalizeAddress(address);

  const [resourceData, accountData, paramsData] = await Promise.all([
    httpPost(`${BASE_URL}/wallet/getaccountresource`, { address, visible: true }),
    httpGet(`${BASE_URL}/v1/accounts/${address}`),
    httpGet(`${BASE_URL}/wallet/getchainparameters`),
  ]);

  const energyLimit = resourceData.EnergyLimit || 0;
  const balance = accountData.data?.[0]?.balance || 0;
  const trxBalance = sunToTrx(balance);

  let energyFee = 420;
  for (const p of (paramsData.chainParameter || [])) {
    if (p.key === "getEnergyFee") { energyFee = p.value || 0; break; }
  }

  const usdtBurn = energyFee * 65000 / SUN_PER_TRX;
  const dailyEnergyPerTrx = 4.5;

  const result = {
    address,
    current_state: {
      trx_balance: trxBalance,
      energy_available: energyLimit,
      can_do_usdt_transfer_free: energyLimit >= 65000,
    },
    recommendations: [],
  };

  if (energyLimit < 65000) {
    const freezeNeeded = Math.ceil(65000 / dailyEnergyPerTrx) + 1;
    result.recommendations.push(
      {
        strategy: "Freeze TRX for Energy",
        description: `Freeze ~${freezeNeeded} TRX to get 65,000 energy/day for free USDT transfers`,
        trx_needed: freezeNeeded,
        lock_period: "14 days minimum",
        savings_per_tx: `~${usdtBurn.toFixed(1)} TRX`,
      },
      {
        strategy: "Rent Energy",
        description: "Rent energy from marketplace for occasional use",
        best_for: "1-5 transactions per month",
        platforms: ["TronNRG", "JustLend", "Feee.io"],
      },
      {
        strategy: "Accept TRX Burn",
        description: `Each USDT transfer burns ~${usdtBurn.toFixed(1)} TRX`,
        best_for: "Rare one-off transactions",
        no_lock_required: true,
      }
    );
  } else {
    result.recommendations.push({
      strategy: "Current setup is optimal",
      description: "You have enough staked energy for basic operations.",
    });
  }
  console.log(fmt(result));
}

// ---------------------------------------------------------------------------
// Staking Commands
// ---------------------------------------------------------------------------


async function cmdSrList({ limit = 30 }) {
  const data = await httpGet(`${TRONSCAN_API}/vote/witness`, { limit, start: 0 });
  const srs = (data.data || []).map(w => ({
    rank: w.realTimeRanking || 0, name: w.name || "Unknown",
    address: w.address || "",
    total_votes: w.realTimeVotes || 0,
    vote_percentage: w.votesPercentage || 0,
    blocks_produced: w.producedTotal || 0,
    efficiency: w.producedEfficiency || 0,
    annualized_rate: w.annualizedRate || 0,
    url: w.url || "",
  }));
  console.log(fmt({ super_representatives: srs, count: srs.length }));
}

async function cmdStakingInfo({ address }) {
  address = normalizeAddress(address);
  const accountData = await httpGet(`${BASE_URL}/v1/accounts/${address}`);
  if (!accountData.data?.length) return console.log(fmt({ error: "Account not found" }));

  const acct = accountData.data[0];
  const rewardData = await httpPost(`${BASE_URL}/wallet/getReward`, { address, visible: true });

  console.log(fmt({
    address,
    frozen: {
      for_energy: (acct.frozenV2 || []).filter(f => f.type === "ENERGY"),
      for_bandwidth: (acct.frozenV2 || []).filter(f => f.type !== "ENERGY"),
    },
    pending_unfreezes: (acct.unfrozenV2 || []).map(u => ({
      amount_sun: u.unfreeze_amount || 0,
      amount_trx: sunToTrx(u.unfreeze_amount || 0),
      expire_time: u.unfreeze_expire_time ? new Date(u.unfreeze_expire_time).toISOString() : null,
    })),
    votes: (acct.votes || []).map(v => ({
      sr_address: v.vote_address || "",
      vote_count: v.vote_count || 0,
    })),
    tron_power: acct.tron_power?.frozen_balance || 0,
    unclaimed_reward_sun: rewardData.reward || 0,
    unclaimed_reward_trx: sunToTrx(rewardData.reward || 0),
  }));
}

async function cmdStakingApy({ amount = "10000" }) {
  const amountNum = parseFloat(amount);
  const data = await httpGet(`${TRONSCAN_API}/vote/witness`, { limit: 5 });
  const topSr = data.data?.[0] || {};
  const apy = parseFloat(topSr.annualizedRate) || 4.0;
  const daily = amountNum * (apy / 100) / 365;

  console.log(fmt({
    staking_amount_trx: amountNum,
    estimated_apy_pct: apy,
    estimated_rewards: {
      daily_trx: Math.round(daily * 10000) / 10000,
      monthly_trx: Math.round(daily * 30 * 100) / 100,
      yearly_trx: Math.round(amountNum * (apy / 100) * 100) / 100,
    },
    top_sr_reference: { name: topSr.name || "Unknown", total_votes: topSr.realTimeVotes || 0, apy_pct: apy },
    note: "APY varies based on SR performance, total network stake, and commission rates.",
  }));
}


// ---------------------------------------------------------------------------
// CLI Parser
// ---------------------------------------------------------------------------

const COMMANDS = {
  // Wallet
  "wallet-balance":    { opts: { address: { type: "string" } }, required: ["address"], handler: cmdWalletBalance },
  "token-balance":     { opts: { address: { type: "string" }, contract: { type: "string" } }, required: ["address", "contract"], handler: cmdTokenBalance },
  "wallet-tokens":     { opts: { address: { type: "string" } }, required: ["address"], handler: cmdWalletTokens },
  "tx-history":        { opts: { address: { type: "string" }, limit: { type: "string", default: "20" } }, required: ["address"], handler: (a) => cmdTxHistory({ ...a, limit: parseInt(a.limit) }) },
  "account-info":      { opts: { address: { type: "string" } }, required: ["address"], handler: cmdAccountInfo },
  "validate-address":  { opts: { address: { type: "string" } }, required: ["address"], handler: cmdValidateAddress },

  // Token
  "token-info":        { opts: { contract: { type: "string" } }, required: ["contract"], handler: cmdTokenInfo },
  "token-search":      { opts: { keyword: { type: "string" } }, required: ["keyword"], handler: cmdTokenSearch },
  "contract-info":     { opts: { contract: { type: "string" } }, required: ["contract"], handler: cmdContractInfo },
  "token-holders":     { opts: { contract: { type: "string" }, limit: { type: "string", default: "20" } }, required: ["contract"], handler: (a) => cmdTokenHolders({ ...a, limit: parseInt(a.limit) }) },
  "trending-tokens":   { opts: {}, required: [], handler: cmdTrendingTokens },
  "token-rankings":    { opts: { "sort-by": { type: "string", default: "market_cap" } }, required: [], handler: (a) => cmdTokenRankings({ sortBy: a["sort-by"] }) },
  "token-security":    { opts: { contract: { type: "string" } }, required: ["contract"], handler: cmdTokenSecurity },

  // Market
  "token-price":       { opts: { contract: { type: "string" } }, required: ["contract"], handler: cmdTokenPrice },
  "kline":             { opts: { contract: { type: "string" }, interval: { type: "string", default: "1h" }, limit: { type: "string", default: "100" } }, required: ["contract"], handler: (a) => cmdKline({ ...a, limit: parseInt(a.limit) }) },
  "trade-history":     { opts: { contract: { type: "string" }, limit: { type: "string", default: "50" } }, required: ["contract"], handler: (a) => cmdTradeHistory({ ...a, limit: parseInt(a.limit) }) },
  "dex-volume":        { opts: { contract: { type: "string" }, period: { type: "string", default: "24h" } }, required: ["contract"], handler: cmdDexVolume },
  "whale-transfers":   { opts: { contract: { type: "string" }, "min-value": { type: "string", default: "100000" } }, required: ["contract"], handler: (a) => cmdWhaleTransfers({ ...a, minValue: parseFloat(a["min-value"]) }) },
  "large-transfers":   { opts: { "min-trx": { type: "string", default: "100000" }, limit: { type: "string", default: "20" } }, required: [], handler: (a) => cmdLargeTransfers({ minTrx: parseFloat(a["min-trx"]), limit: parseInt(a.limit) }) },
  "pool-info":         { opts: { contract: { type: "string" } }, required: ["contract"], handler: cmdPoolInfo },
  "market-overview":   { opts: {}, required: [], handler: cmdMarketOverview },

  // Swap
  "swap-quote":        { opts: { "from-token": { type: "string" }, "to-token": { type: "string" }, amount: { type: "string" } }, required: ["from-token", "to-token", "amount"], handler: (a) => cmdSwapQuote({ fromToken: a["from-token"], toToken: a["to-token"], amount: a.amount }) },
  "swap-route":        { opts: { "from-token": { type: "string" }, "to-token": { type: "string" }, amount: { type: "string" } }, required: ["from-token", "to-token", "amount"], handler: (a) => cmdSwapQuote({ fromToken: a["from-token"], toToken: a["to-token"], amount: a.amount }) },
  "tx-status":         { opts: { txid: { type: "string" } }, required: ["txid"], handler: cmdTxStatus },

  // Resource
  "resource-info":     { opts: { address: { type: "string" } }, required: ["address"], handler: cmdResourceInfo },
  "estimate-energy":   { opts: { contract: { type: "string" }, function: { type: "string" }, params: { type: "string", default: "" }, caller: { type: "string" } }, required: ["contract", "function", "caller"], handler: (a) => cmdEstimateEnergy({ contract: a.contract, func: a.function, params: a.params, caller: a.caller }) },
  "estimate-bandwidth":{ opts: { "tx-size": { type: "string", default: "267" } }, required: [], handler: (a) => cmdEstimateBandwidth({ txSize: parseInt(a["tx-size"]) }) },
  "energy-price":      { opts: {}, required: [], handler: cmdEnergyPrice },
  "energy-rental":     { opts: { amount: { type: "string", default: "65000" } }, required: [], handler: cmdEnergyRental },
  "optimize-cost":     { opts: { address: { type: "string" } }, required: ["address"], handler: cmdOptimizeCost },

  // Staking
  "sr-list":           { opts: { limit: { type: "string", default: "30" } }, required: [], handler: (a) => cmdSrList({ limit: parseInt(a.limit) }) },
  "staking-info":      { opts: { address: { type: "string" } }, required: ["address"], handler: cmdStakingInfo },
  "staking-apy":       { opts: { amount: { type: "string", default: "10000" } }, required: [], handler: cmdStakingApy },
};

function printHelp() {
  console.log(`
tron_api.mjs — TronLink Wallet Skills CLI (Node.js)

Usage: node tron_api.mjs <command> [--option value ...]

Commands:
  Wallet:
    wallet-balance      --address <ADDR>
    token-balance       --address <ADDR> --contract <TOKEN>
    wallet-tokens       --address <ADDR>
    tx-history          --address <ADDR> [--limit N]
    account-info        --address <ADDR>
    validate-address    --address <ADDR>

  [REDACTED_SECRET]          --contract <TOKEN>
    token-search        --keyword <KEYWORD>
    contract-info       --contract <TOKEN>
    token-holders       --contract <TOKEN> [--limit N]
    trending-tokens
    token-rankings      [--sort-by market_cap|volume|holders|gainers|losers]
    token-security      --contract <TOKEN>

  Market:
    token-price         --contract <TOKEN|TRX>
    kline               --contract <TOKEN> [--interval 1h] [--limit 100]
    trade-history       --contract <TOKEN> [--limit 50]
    dex-volume          --contract <TOKEN> [--period 24h]
    whale-transfers     --contract <TOKEN> [--min-value 100000]
    large-transfers     [--min-trx 100000] [--limit 20]
    pool-info           --contract <TOKEN>
    market-overview

  Swap:
    swap-quote          --from-token <TOKEN> --to-token <TOKEN> --amount <N>
    swap-route          --from-token <TOKEN> --to-token <TOKEN> --amount <N>
    tx-status           --txid <HASH>

  Resource:
    resource-info       --address <ADDR>
    estimate-energy     --contract <TOKEN> --function <SIG> --caller <ADDR> [--params <P>]
    estimate-bandwidth  [--tx-size 267]
    energy-price
    energy-rental       [--amount 65000]
    optimize-cost       --address <ADDR>

  Staking:
    sr-list             [--limit 30]
    staking-info        --address <ADDR>
    staking-apy         [--amount 10000]

Environment:
    TRONGRID_API_KEY       TronGrid API key (optional, for higher rate limits)
    TRON_NETWORK           mainnet (default) | shasta | nile
  `);
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------

async function main() {
  const args = process.argv.slice(2);
  const command = args[0];

  if (!command || command === "--help" || command === "-h") {
    printHelp();
    process.exit(0);
  }

  const cmdDef = COMMANDS[command];
  if (!cmdDef) {
    console.error(`Unknown command: ${command}\nRun with --help for usage.`);
    process.exit(1);
  }

  // Parse command-specific options
  const cmdArgs = args.slice(1);
  let parsed;
  try {
    parsed = parseArgs({
      args: cmdArgs,
      options: cmdDef.opts,
      strict: false,
    });
  } catch (e) {
    console.error(`Error parsing options: ${e.message}`);
    process.exit(1);
  }

  // Check required options
  for (const req of cmdDef.required) {
    if (!parsed.values[req]) {
      console.error(`Missing required option: --${req}`);
      process.exit(1);
    }
  }

  await cmdDef.handler(parsed.values);
}

main().catch(e => {
  console.error(`Fatal error: ${e.message}`);
  process.exit(1);
});


path: uninstall.sh
content: #!/bin/sh
set -e

# ──────────────────────────────────────────────────────────────
# TronLink Skills — Uninstaller
#
# Reverses everything done by install.sh:
#   - Removes MCP server registration (Claude Code)
#   - Removes symlinks and copied config files
#   - Deletes ~/.tronlink-skills
#
# Usage:
#   sh uninstall.sh
#   # or, if already removed locally:
#   curl -sSL https://raw.githubusercontent.com/TronLink/tronlink-skills/main/uninstall.sh | sh
# ──────────────────────────────────────────────────────────────

INSTALL_DIR="$HOME/.tronlink-skills"

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

info()  { printf "${CYAN}ℹ${NC}  %s\n" "$1"; }
ok()    { printf "${GREEN}✓${NC}  %s\n" "$1"; }
warn()  { printf "${YELLOW}⚠${NC}  %s\n" "$1"; }
removed() { printf "${RED}✗${NC}  Removed: %s\n" "$1"; }

# ──────────────────────────────────────────────────────────────
# Claude Code
# ──────────────────────────────────────────────────────────────

uninstall_claude_code() {
  if command -v claude >/dev/null 2>&1; then
    info "Removing Claude Code MCP server..."
    # Remove from all scopes (user/global and project-level)
    claude mcp remove -s user tronlink 2>/dev/null && removed "MCP server 'tronlink' (global)"
    claude mcp remove tronlink 2>/dev/null && removed "MCP server 'tronlink' (project)"
    ok "MCP cleanup done"
  fi
}

# ──────────────────────────────────────────────────────────────
# Cursor
# ──────────────────────────────────────────────────────────────

uninstall_cursor() {
  CURSOR_RULES="$HOME/.cursor/rules/tronlink-skills.json"
  if [ -f "$CURSOR_RULES" ]; then
    rm -f "$CURSOR_RULES"
    removed "$CURSOR_RULES"
  fi
}

# ──────────────────────────────────────────────────────────────
# Codex CLI
# ──────────────────────────────────────────────────────────────

uninstall_codex() {
  CODEX_LINK="$HOME/.agents/skills/tronlink-skills"
  if [ -L "$CODEX_LINK" ] || [ -e "$CODEX_LINK" ]; then
    rm -f "$CODEX_LINK"
    removed "$CODEX_LINK"
  fi
}

# ──────────────────────────────────────────────────────────────
# OpenCode
# ──────────────────────────────────────────────────────────────

uninstall_opencode() {
  OC_PLUGIN="$HOME/.config/opencode/plugins/tronlink-skills.js"
  OC_SKILLS="$HOME/.config/opencode/skills/tronlink-skills"

  if [ -L "$OC_PLUGIN" ] || [ -e "$OC_PLUGIN" ]; then
    rm -f "$OC_PLUGIN"
    removed "$OC_PLUGIN"
  fi
  if [ -L "$OC_SKILLS" ] || [ -e "$OC_SKILLS" ]; then
    rm -f "$OC_SKILLS"
    removed "$OC_SKILLS"
  fi
}

# ──────────────────────────────────────────────────────────────
# Project-level files (Cursor / Windsurf / Codex may have added)
# ──────────────────────────────────────────────────────────────

uninstall_project_files() {
  # Symlink to skills
  if [ -L "./tronlink-skills" ]; then
    rm -f "./tronlink-skills"
    removed "./tronlink-skills (symlink)"
  fi

  # AGENTS.md — only remove if it was copied from our install
  if [ -f "./AGENTS.md" ] && grep -q "tron_api.mjs" "./AGENTS.md" 2>/dev/null; then
    rm -f "./AGENTS.md"
    removed "./AGENTS.md"
  fi

  # CLAUDE.md — only remove tronlink content
  if [ -f "./CLAUDE.md" ]; then
    if grep -q "TronLink Wallet Skills" "./CLAUDE.md" 2>/dev/null; then
      # If the entire file is our content, remove it
      FIRST_LINE=$(head -1 "./CLAUDE.md")
      if echo "$FIRST_LINE" | grep -q "TronLink"; then
        rm -f "./CLAUDE.md"
        removed "./CLAUDE.md (was entirely TronLink content)"
      else
        warn "./CLAUDE.md contains TronLink content mixed with other content."
        warn "Please manually remove the TronLink section from CLAUDE.md"
      fi
    fi
  fi
}

# ──────────────────────────────────────────────────────────────
# Core: remove ~/.tronlink-skills
# ──────────────────────────────────────────────────────────────

uninstall_core() {
  if [ -d "$INSTALL_DIR" ]; then
    rm -rf "$INSTALL_DIR"
    removed "$INSTALL_DIR"
  else
    info "$INSTALL_DIR does not exist, skipping"
  fi
}

# ──────────────────────────────────────────────────────────────
# Main
# ──────────────────────────────────────────────────────────────

main() {
  echo ""
  echo "  ╔══════════════════════════════════════════╗"
  echo "  ║     TronLink Skills Uninstaller          ║"
  echo "  ╚══════════════════════════════════════════╝"
  echo ""

  info "Cleaning up all environments..."
  echo ""

  uninstall_claude_code
  uninstall_cursor
  uninstall_codex
  uninstall_opencode
  uninstall_project_files
  uninstall_core

  echo ""
  ok "TronLink Skills has been completely uninstalled."
  echo ""
  echo "  Note: Environment variables (TRONGRID_API_KEY, etc.)"
  echo "  are not removed. Remove them from your shell profile if needed:"
  echo "    ~/.bashrc  ~/.zshrc  ~/.profile"
  echo ""
}

main


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