Skill: srikanthbellary/solana-sniper-bot (version 1.0.0)

--- name: solana-sniper-bot description: Autonomous Solana token sniper and trading bot. Monitors new token launches on Raydium/Jupiter, evaluates rugpull risk with LLM analysis, auto-buys promising launches, and manages exit strategies. Use when user wants to snipe Solana token launches, trade memecoins, monitor new Solana pairs, or build a Solana trading bot. Supports cron-based monitoring, take-profit/stop-loss, and portfolio tracking. metadata: {"openclaw": {"requires": {"env": ["SOLANA_PRIVATE_KEY", "LLM_API_KEY"]}, "primaryEnv": "LLM_API_KEY", "homepage": "https://github.com/srikanthbellary"}} --- # Solana Sniper Bot Autonomous token sniper for Solana. Monitors Raydium and Jupiter for new liquidity pools, evaluates tokens using LLM-powered rugpull detection, and executes buy/sell orders via Jupiter aggregator. ## Prerequisites - **Solana wallet** with SOL for gas + trading capital (USDC or SOL) - **Anthropic API key** (uses Haiku for token evaluation ~$0.001/eval) - **Helius or QuickNode RPC** (free tier works, paid recommended for speed) ## Setup ### 1. Install Dependencies ```bash python3 {baseDir}/scripts/setup.sh ``` Or manually: ```bash pip install solana solders httpx aiohttp python-dotenv ``` ### 2. Configuration Create `.env`: ``` SOLANA_PRIVATE_KEY=<base58-private-key> LLM_API_KEY=<anthropic-api-key> RPC_URL=https://api.mainnet-beta.solana.com HELIUS_API_KEY=<optional-for-faster-monitoring> BUY_AMOUNT_SOL=0.1 TAKE_PROFIT=2.0 STOP_LOSS=0.5 ``` ### 3. Deploy ```bash cp {baseDir}/scripts/sniper.py /opt/sniper/ python3 /opt/sniper/sniper.py ``` ## How It Works 1. **Pool Monitor** — Watches Raydium AMM for new liquidity pool creation events 2. **Token Analysis** — For each new pool, queries token metadata: - Mint authority (revoked = good) - Freeze authority (revoked = good) - LP burned/locked percentage - Top holder concentration - Contract verification status 3. **LLM Risk Assessment** — Sends token data to Claude Haiku for rugpull probability estimate 4. **Auto-Buy** — If risk score < threshold, buys via Jupiter aggregator for best price 5. **Position Management** — Monitors positions with take-profit and stop-loss triggers 6. **Auto-Sell** — Exits via Jupiter when TP/SL hit ## Risk Scoring Each token gets scored 0-100 (lower = safer): | Factor | Weight | Red Flag | |--------|--------|----------| | Mint authority | 25% | Not revoked | | Freeze authority | 20% | Not revoked | | LP lock | 20% | < 80% locked | | Top 10 holders | 15% | > 50% supply | | Contract age | 10% | < 1 hour | | LLM sentiment | 10% | Negative assessment | Default buy threshold: risk score < 40 ## Trading Parameters Configurable in `.env`: - `BUY_AMOUNT_SOL` — Amount per snipe (default: 0.1 SOL) - `TAKE_PROFIT` — Exit multiplier (default: 2.0 = 100% gain) - `STOP_LOSS` — Exit multiplier (default: 0.5 = 50% loss) - `MAX_POSITIONS` — Max concurrent positions (default: 5) - `MIN_LIQUIDITY` — Minimum pool liquidity in USD (default: $5000) - `SLIPPAGE_BPS` — Slippage tolerance in bps (default: 500 = 5%) ## ⚠️ Security Considerations - **Use a DEDICATED wallet** with only what you're willing to lose - **Memecoin trading is extremely high risk** — most new tokens go to zero - **Never use your main wallet's private key** - Start with tiny amounts (0.01-0.1 SOL per trade) - Monitor actively — this is not a set-and-forget system - **RPC rate limits** — Free Solana RPC will miss fast launches. Use Helius/QuickNode for serious sniping. ## References - See `references/jupiter-api.md` for Jupiter aggregator API docs - See `references/raydium-pools.md` for pool monitoring details

path: references/jupiter-api.md
content: # Jupiter Aggregator API

## Endpoints
| Endpoint | URL |
|----------|-----|
| Quote | `GET https://quote-api.jup.ag/v6/quote` |
| Swap | `POST https://quote-api.jup.ag/v6/swap` |
| Price | `GET https://price.jup.ag/v6/price` |

## Quote Params
- `inputMint` — Source token mint address
- `outputMint` — Destination token mint address  
- `amount` — Amount in smallest unit (lamports for SOL)
- `slippageBps` — Slippage in basis points (500 = 5%)

## Swap Body
```json
{"quoteResponse": <quote>, "userPublicKey": "<wallet>", "wrapAndUnwrapSol": true}
```
Returns `swapTransaction` (base64 serialized transaction to sign and send).

## Key Mints
- SOL: [REDACTED_SECRET]
- USDC: [REDACTED_SECRET]
- USDT: [REDACTED_SECRET]


path: references/raydium-pools.md
content: # Raydium Pool Monitoring

## API
- Pools: `GET https://api.raydium.io/v2/ammV3/ammPools`
- Returns pool list with `mintA`, `mintB`, `tvl`, `volume24h`

## New Pool Detection
- Poll every 5-10 seconds
- Compare against seen pool set
- Filter: skip if both mints are SOL/USDC/USDT (stablecoin pairs)
- Filter: skip if TVL < minimum liquidity threshold

## Rugpull Red Flags
1. Mint authority NOT revoked — creator can mint infinite tokens
2. Freeze authority NOT revoked — creator can freeze your tokens
3. Top 10 holders own >50% supply — dump risk
4. LP not locked/burned — creator can pull liquidity
5. Contract deployed < 1 hour ago with huge initial buy
6. No social media / website / audit


path: scripts/setup.sh
content: #!/bin/bash
set -e
echo "=== Solana Sniper Bot Setup ==="
pip install solana==0.35.0 solders==0.21.0 httpx==0.27.0 aiohttp==3.9.5 python-dotenv==1.0.1 base58==2.1.1
echo "Done. Create .env with SOLANA_PRIVATE_KEY and LLM_API_KEY"


path: scripts/sniper.py
content: #!/usr/bin/env python3
"""Solana Token Sniper — monitors Raydium pools, evaluates with LLM, auto-trades via Jupiter."""
import os, sys, json, time, logging, asyncio
from datetime import datetime, timezone
from pathlib import Path
from dotenv import load_dotenv
import httpx

load_dotenv()
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
log = logging.getLogger("sniper")

PRIVATE_KEY = os.environ["SOLANA_PRIVATE_KEY"]
LLM_API_KEY = os.environ["LLM_API_KEY"]
RPC_URL = os.getenv("RPC_URL", "https://api.mainnet-beta.solana.com")
HELIUS_KEY = os.getenv("HELIUS_API_KEY", "")
BUY_AMOUNT = float(os.getenv("BUY_AMOUNT_SOL", "0.1"))
TAKE_PROFIT = float(os.getenv("TAKE_PROFIT", "2.0"))
STOP_LOSS = float(os.getenv("STOP_LOSS", "0.5"))
MAX_POSITIONS = int(os.getenv("MAX_POSITIONS", "5"))
MIN_LIQUIDITY = float(os.getenv("MIN_LIQUIDITY", "5000"))
SLIPPAGE_BPS = int(os.getenv("SLIPPAGE_BPS", "500"))
RISK_THRESHOLD = int(os.getenv("RISK_THRESHOLD", "40"))

JUPITER_QUOTE = "https://quote-api.jup.ag/v6/quote"
JUPITER_SWAP = "https://quote-api.jup.ag/v6/swap"
SOL_MINT = [REDACTED_SECRET]
USDC_MINT = [REDACTED_SECRET]

TRADES_LOG = Path("trades.jsonl")
positions = {}

def get_rpc_url():
    if HELIUS_KEY:
        return f"https://mainnet.helius-rpc.com/?[REDACTED_SECRET]"
    return RPC_URL

async def get_token_info(mint: str) -> dict:
    """Fetch token metadata for risk assessment."""
    async with httpx.AsyncClient(timeout=15) as client:
        # Get mint account info
        rpc = get_rpc_url()
        resp = await client.post(rpc, json={
            "jsonrpc": "2.0", "id": 1, "method": "getAccountInfo",
            "params": [mint, {"encoding": "jsonParsed"}]
        })
        data = resp.json()
        info = {"mint": mint, "mint_authority": None, "freeze_authority": None, "supply": 0, "decimals": 0}
        if data.get("result", {}).get("value"):
            parsed = data["result"]["value"]["data"].get("parsed", {}).get("info", {})
            info["mint_authority"] = parsed.get("mintAuthority")
            info["freeze_authority"] = parsed.get("freezeAuthority")
            info["supply"] = int(parsed.get("supply", 0))
            info["decimals"] = parsed.get("decimals", 0)

        # Get largest holders
        resp2 = await client.post(rpc, json={
            "jsonrpc": "2.0", "id": 2, "method": "getTokenLargestAccounts",
            "params": [mint]
        })
        holders = resp2.json().get("result", {}).get("value", [])
        info["top_holders"] = holders[:10]
        total = sum(float(h.get("uiAmount", 0) or 0) for h in holders)
        top10 = sum(float(h.get("uiAmount", 0) or 0) for h in holders[:10])
        info["top10_concentration"] = (top10 / total * 100) if total > 0 else 100

        return info

def assess_risk(token_info: dict) -> int:
    """Score token risk 0-100 (lower = safer)."""
    score = 0
    # Mint authority not revoked = risky
    if token_info.get("mint_authority"): score += 25
    # Freeze authority not revoked = risky
    if token_info.get("freeze_authority"): score += 20
    # High concentration
    conc = token_info.get("top10_concentration", 100)
    if conc > 80: score += 15
    elif conc > 50: score += 10
    elif conc > 30: score += 5
    return score

async def llm_evaluate(token_info: dict, pool_info: dict) -> tuple:
    """Ask LLM for rugpull risk assessment."""
    prompt = f"""Evaluate this new Solana token for rugpull risk (0.0=safe, 1.0=scam):

Mint: {token_info['mint']}
Mint authority revoked: {token_info['mint_authority'] is None}
Freeze authority revoked: {token_info['freeze_authority'] is None}
Top 10 holder concentration: {token_info.get('top10_concentration', 'unknown'):.1f}%
Pool liquidity: ${pool_info.get('liquidity_usd', 0):.0f}

Reply with ONLY a number 0.0 to 1.0."""

    async with httpx.AsyncClient(timeout=30) as client:
        resp = await client.post("https://api.anthropic.com/v1/messages",
            headers={"x-api-key": LLM_API_KEY, "anthropic-version": "2023-06-01", "Content-Type": "application/json"},
            json={"model": "claude-3-5-haiku-20241022", "max_tokens": 20, "messages": [{"role": "user", "content": prompt}]})
        data = resp.json()
        text = data.get("content", [{}])[0].get("text", "1.0").strip()
        try:
            risk = float(text.split()[0].strip(".,"))
            return min(max(risk, 0), 1), data.get("usage", {})
        except:
            return 1.0, data.get("usage", {})

async def get_jupiter_quote(input_mint: str, output_mint: str, amount: int) -> dict:
    """Get swap quote from Jupiter."""
    async with httpx.AsyncClient(timeout=15) as client:
        resp = await client.get(JUPITER_QUOTE, params={
            "inputMint": input_mint, "outputMint": output_mint,
            "amount": str(amount), "slippageBps": str(SLIPPAGE_BPS)
        })
        return resp.json()

async def execute_swap(quote: dict) -> dict:
    """Execute swap via Jupiter."""
    from solders.keypair import Keypair
    import base58
    keypair = Keypair.from_bytes(base58.b58decode(PRIVATE_KEY))

    async with httpx.AsyncClient(timeout=30) as client:
        resp = await client.post(JUPITER_SWAP, json={
            "quoteResponse": quote,
            "userPublicKey": str(keypair.pubkey()),
            "wrapAndUnwrapSol": True
        })
        swap_data = resp.json()

        # Sign and send transaction
        from solders.transaction import VersionedTransaction
        import base64
        tx_bytes = base64.b64decode(swap_data["swapTransaction"])
        tx = VersionedTransaction.from_bytes(tx_bytes)
        signed = keypair.sign_message(tx.message.serialize())

        rpc = get_rpc_url()
        send_resp = await client.post(rpc, json={
            "jsonrpc": "2.0", "id": 1, "method": "sendTransaction",
            "params": [base64.b64encode(bytes(tx)).decode(), {"skipPreflight": True}]
        })
        return send_resp.json()

async def monitor_new_pools():
    """Monitor Raydium for new pool creation."""
    log.info("Starting Solana sniper bot...")
    log.info(f"Config: buy={BUY_AMOUNT} SOL, TP={TAKE_PROFIT}x, SL={STOP_LOSS}x, max_pos={MAX_POSITIONS}")

    # Poll Raydium new pools API
    seen_pools = set()
    while True:
        try:
            async with httpx.AsyncClient(timeout=15) as client:
                # Check for new Raydium AMM pools
                resp = await client.get("https://api.raydium.io/v2/ammV3/ammPools")
                pools = resp.json().get("data", [])

                for pool in pools[-20:]:  # Check latest 20
                    pool_id = pool.get("id", "")
                    if pool_id in seen_pools: continue
                    seen_pools.add(pool_id)

                    mint_a = pool.get("mintA", {}).get("address", "")
                    mint_b = pool.get("mintB", {}).get("address", "")
                    token_mint = mint_b if mint_a in (SOL_MINT, USDC_MINT) else mint_a
                    if token_mint in (SOL_MINT, USDC_MINT): continue

                    liquidity = float(pool.get("tvl", 0))
                    if liquidity < MIN_LIQUIDITY:
                        log.debug(f"Skip low liq: {token_mint[:12]}... ${liquidity:.0f}")
                        continue

                    log.info(f"NEW POOL: {token_mint[:20]}... liq=${liquidity:.0f}")

                    # Evaluate
                    token_info = await get_token_info(token_mint)
                    base_risk = assess_risk(token_info)
                    llm_risk, usage = await llm_evaluate(token_info, {"liquidity_usd": liquidity})
                    total_risk = int(base_risk * 0.7 + llm_risk * 100 * 0.3)

                    log.info(f"  Risk: {total_risk}/100 (base={base_risk}, llm={llm_risk:.2f})")

                    if total_risk < RISK_THRESHOLD and len(positions) < MAX_POSITIONS:
                        log.info(f"  BUY SIGNAL! Sniping {BUY_AMOUNT} SOL...")
                        amount_lamports = int(BUY_AMOUNT * 1e9)
                        try:
                            quote = await get_jupiter_quote(SOL_MINT, token_mint, amount_lamports)
                            result = await execute_swap(quote)
                            tx_id = result.get("result", "unknown")
                            log.info(f"  BOUGHT: tx={tx_id}")
                            positions[token_mint] = {
                                "entry_time": datetime.now(timezone.utc).isoformat(),
                                "amount_sol": BUY_AMOUNT,
                                "token_mint": token_mint,
                                "tx": tx_id,
                                "risk_score": total_risk
                            }
                            with open(TRADES_LOG, "a") as f:
                                f.write(json.dumps(positions[token_mint]) + "\n")
                        except Exception as e:
                            log.error(f"  Buy failed: {e}")
                    else:
                        log.info(f"  SKIP: risk={total_risk} (threshold={RISK_THRESHOLD})")

        except Exception as e:
            log.error(f"Monitor error: {e}")

        await asyncio.sleep(5)  # Poll every 5 seconds

if __name__ == "__main__":
    asyncio.run(monitor_new_pools())


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