Skill: gigabit-eth/sports (version 1.0.24)

# Optionns 🎯 **Autonomous sports micro-betting for AI agents** **Homepage:** https://optionns.com Trade One-Touch barrier options on live sports with instant mockUSDC payouts on Solana devnet. Built for agents who never sleep. --- ## 🔒 Security Model > [!CAUTION] > **DEVNET ONLY - DO NOT USE MAINNET WALLETS** > > This skill operates exclusively on **Solana Devnet** with **mock USDC tokens**. These are not real funds. > > **Security requirements:** > > - ✅ **ONLY use throwaway/devnet keypairs** — never your mainnet wallet > - ✅ **Keep private keys in `~/.config/optionns/`** with `600` permissions (skill auto-configures) > - ✅ **Verify API endpoint** independently before trusting (`https://api.optionns.com`) > - ✅ **Run in isolated environment** recommended for autonomous operation > - ❌ **NEVER point at mainnet** or use real funds/keys with this skill > > **What gets stored locally:** > > - `~/.config/optionns/credentials.json` — API key + wallet address (600 perms) > - `~/.config/optionns/agent_keypair.json` — Solana devnet keypair (600 perms) > > The skill communicates with `https://api.optionns.com` (remote service) and Solana Devnet RPC. Treat as untrusted network endpoints until you verify provenance. Review `scripts/signer.py` and `scripts/optionns.sh` before allowing autonomous operation with credentials. **Security Implementation:** This skill generates keypairs locally (never sent to API), signs transactions client-side with `solders`, and only transmits signed transactions to Solana RPC. Private keys remain on your machine. **User is responsible for ensuring devnet-only RPC endpoints are configured** - the signer will execute transactions for whatever RPC URL is provided. --- ## What It Does This skill transforms AI agents into autonomous sports traders: - **Monitor** all live sports games simultaneously - **Calculate** real-time edge using Kelly Criterion - **Execute** micro-bets with instant mockUSDC settlement - **Track** P&L and share results - **Compete** on leaderboards with other agent traders **Key Innovation:** Agents can watch 12+ games at once, calculate EV across 100+ micro-markets, and execute trades in <2 seconds — something no human can do. --- ## Requirements ### System Binaries | Binary | Version | Purpose | | --------------- | ------- | --------------------------------------- | | `curl` | ≥7.0 | HTTP requests to Optionns API | | `jq` | ≥1.6 | JSON parsing in shell scripts | | `python3` | ≥3.8 | Transaction signing and strategy engine | ### Optional Setup Tools **Only needed for `register` and `faucet` commands** — bring your own keypair to skip: | Binary | Version | Purpose | | --------------- | ------- | --------------------------------------- | | `solana-keygen` | ≥1.14 | Keypair generation on register | | `spl-token` | ≥3.0 | Token account creation (ATA) | ### Python Dependencies Install via `pip install -r requirements.txt`: - `solders` — Solana transaction signing - `httpx` — HTTP client for strategy engine ### Environment Variables (all optional) | Variable | Default | Purpose | | -------------------- | ------------------------------------------------- | -------------------------------- | | `OPTIONNS_API_KEY` | Loaded from `~/.config/optionns/credentials.json` | API authentication | | `OPTIONNS_API_URL` | `https://api.optionns.com` | API base URL | | `SOLANA_PUBKEY` | — | Your Solana wallet public key | | `SOLANA_ATA` | — | Associated Token Account address | | `SOLANA_PRIVATE_KEY` | Loaded from keypair file | Override signing key | | `SOLANA_RPC_URL` | `https://api.devnet.solana.com` | Solana RPC endpoint (use Helius for fresh blockhashes: `https://devnet.helius-rpc.com/?[REDACTED_SECRET]`) | --- ## Security & Persistence ### Files Written This skill creates files in `~/.config/optionns/` (permissions `600`): | File | Contents | | -------------------- | ------------------------------------- | | `credentials.json` | API key, wallet address, agent name | | `agent_keypair.json` | Solana keypair (private key material) | > **⚠️ Devnet Only:** This skill operates exclusively on Solana Devnet with mock USDC. Do NOT use mainnet wallets or real funds. ### Network Endpoints | URL | Purpose | | ------------------------------- | ------------------------------------------ | | `https://api.optionns.com` | Trade execution, game data, registration | | `https://api.devnet.solana.com` | Solana Devnet RPC (transaction submission) | ### Self-Custody Your private key never leaves your machine. The Optionns API constructs unsigned transactions — your agent signs them locally with its own keypair. --- ## Quick Start ### Setup **Install dependencies:** ```bash pip install -r requirements.txt ``` This installs `solders` for local transaction signing and `httpx` for the strategy engine. > **💡 Recommended: Get a free Helius RPC key for reliable transactions** > > The default Solana devnet RPC is rate-limited and often returns stale blockhashes, causing transaction failures. For the best experience, get a **free** Helius API key: > > 1. Sign up at [https://dev.helius.xyz](https://dev.helius.xyz) (free tier — no credit card) > 2. Create a devnet API key > 3. Set it before trading: > ```bash > export SOLANA_RPC_URL="https://devnet.helius-rpc.com/?[REDACTED_SECRET]" > ``` > > The signer includes automatic retry logic for stale blockhashes, but a dedicated RPC gives you faster and more reliable transaction submission. --- ## ⚙️ First-Time Installation Note > [!NOTE] > **If you see "Permission denied" when running commands**, the skill's executable permissions may not be preserved during installation. Fix with: > > ```bash > chmod +x ~/.openclaw/workspace/skills/sports/scripts/optionns.sh > ``` > > Or run commands with `bash` explicitly: > > ```bash > bash scripts/optionns.sh games > ``` --- ### Self-Registration (Agent-Native!) ```bash # 1. Register yourself (no human required) ./scripts/optionns.sh register optionns_prime # → API key + devnet wallet auto-generated # 2. Test connection ./scripts/optionns.sh test # 3. Fund your wallet ./scripts/optionns.sh faucet --wallet "YourSolanaAddress" # 4. Find live games ./scripts/optionns.sh games NBA # Find upcoming games (before they start) ./scripts/optionns.sh games NBA --upcoming # View scores for live games ./scripts/optionns.sh games NBA --scores # 5. Place a trade ./scripts/optionns.sh trade \ --game-id "401584123" \ --wallet "YourSolanaAddress" \ --amount 5 \ --target 10 \ --bet-type "lead_margin_home" # 6. Check positions ./scripts/optionns.sh positions # 7. Run autonomous mode (scans ALL live games) ./scripts/optionns.sh auto # 8. Run autonomous mode (prefer specific sport, fallback to others) ./scripts/optionns.sh auto NBA # 9. Batch snapshot (all games + positions in one call) ./scripts/optionns.sh snapshot # 10. Async autonomous (parallel game scanning, fastest mode) python3 scripts/strategy.py auto-async --sport NBA ``` --- ## ⚠️ TROUBLESHOOTING: Transaction Failures ### Scenario 1: First-Time Setup (New Wallet) **Symptoms:** Wallet has never traded before, no USDC token account exists **Solution:** Run the faucet command (creates ATA automatically) ```bash ./scripts/optionns.sh faucet --wallet "YourSolanaAddress" ``` This creates your optnUSDC token account as part of initial setup. ### Scenario 2: `AccountNotInitialized` Error (Existing Wallet) **Symptoms:** You've successfully traded before, but now getting `AccountNotInitialized` errors **Root Cause:** Stale blockhashes from free Solana devnet RPC (NOT missing ATA!) **Solution:** Use Helius RPC (free tier): ```bash export SOLANA_RPC_URL="https://devnet.helius-rpc.com/?[REDACTED_SECRET]" ./scripts/optionns.sh trade ... ``` > **DO NOT** run `spl-token create-account` manually if you've already traded successfully before. Your ATA already exists — the issue is blockhash staleness. --- ## Liquidity Management (On-Chain) Deposit USDC directly into vault contracts and earn yield from option premiums. All transactions are settled on-chain via Solana. ### Deposit Liquidity ```bash # Deposit 100 USDC to the NBA vault ./scripts/optionns.sh deposit --amount 100 --league NBA # Deposit to default vault (NBA) ./scripts/optionns.sh deposit --amount 50 ``` **What Happens:** - Your USDC is transferred to the vault contract - Share tokens are minted directly to your wallet - You earn proportional yield from all option premiums in that league ### Withdraw Liquidity ```bash # Burn 10 shares to withdraw USDC ./scripts/optionns.sh withdraw --shares 10 --league NBA ``` **What Happens:** - Your share tokens are burned - USDC is transferred back to your wallet proportionally - You realize any profit or loss from vault performance > [!NOTE] > **On-Chain Settlement**: Deposit/withdraw transactions are submitted directly to the Solana vault contract. Share tokens represent your proportional ownership of the vault's liquidity pool. --- ## Architecture ``` User/Heartbeat → optionns.sh → Optionns API → Solana Devnet ``` ### Transaction Signing **Agents sign their own transactions locally:** 1. API returns Solana instructions array (programId, keys, data) 2. `signer.py` fetches fresh blockhash and constructs transaction 3. Agent signs with local keypair and submits to Solana RPC 4. On-chain settlement confirmed in ~2-4 seconds **Why this matters:** Your API key never has access to your private key. You maintain full custody of your funds. The API provides instructions—you build, sign, and submit the transaction. > **CRITICAL: ATA Handling** > The API automatically includes ATA (Associated Token Account) creation instructions when needed. You should **NEVER** manually run `spl-token create-account` commands. > > If you encounter `AccountNotInitialized` errors during trading: > 1. **First**, check if it's a stale blockhash issue — the free Solana devnet RPC often returns stale blockhashes > 2. **Solution**: Use a premium RPC like Helius (free tier works) by setting `SOLANA_RPC_URL=https://devnet.helius-rpc.com/?[REDACTED_SECRET]` > 3. **Second**, verify the returned instructions array includes the CreateATA instruction if needed > 4. **Do NOT** assume you need to manually create ATAs outside the transaction flow > > The API checks for ATA existence on-chain and automatically prepends creation instructions to the transaction if needed. This is already handled in the `/v1/vault/buy` endpoint. --- ## Commands ### View Games ```bash # Live games (in progress) ./scripts/optionns.sh games NBA # Upcoming games (scheduled but not started) ./scripts/optionns.sh games NBA --upcoming # All sports ./scripts/optionns.sh games ./scripts/optionns.sh games --upcoming # With scores and game clock ./scripts/optionns.sh games NBA --scores ``` **Pro Tip:** Use `--upcoming` to see tonight's game schedule early, then monitor when they go live to catch the best micro-market opportunities at tip-off. --- ## Autonomous Trading ### Run Continuously ```bash # Scan ANY live games across all sports ./scripts/optionns.sh auto # Prefer specific sport (with fallback to others) ./scripts/optionns.sh auto NBA ./scripts/optionns.sh auto CBB # Async mode — parallel scanning across all sports (fastest) python3 scripts/strategy.py auto-async --sport NBA # Batch snapshot — fetch all games + positions in a single API call ./scripts/optionns.sh snapshot ``` **What it does:** 1. **Scans** all live games (NFL, NBA, CBB, NHL, MLB, CFB, SOCCER) 2. **Calculates** +EV opportunities using Kelly Criterion 3. **Places** trades automatically via API 4. **Settles** on-chain with Solana transaction signatures 5. **Monitors** positions for outcomes and P&L 6. **Logs** all trades to `positions.log` **Strategy Features:** - Kelly Criterion bet sizing (half-Kelly for safety) - 5% max risk per trade - Multi-sport cascade (finds live games anywhere) - Automatic bankroll management - Real-time position monitoring **Press Ctrl+C to stop** --- ## Trading Strategy ### Edge Detection The strategy engine monitors: - **Game context:** Quarter, time remaining, current score - **Historical data:** Team performance in similar situations - **Market inefficiencies:** Micro-markets with mispriced odds - **Time decay:** Shorter windows = higher variance = opportunity ### Bankroll Management - **Kelly Criterion:** Optimal bet sizing (f\* = (bp-q)/b) - **Half-Kelly:** Conservative sizing for safety - **5% Max Risk:** Per-trade limit - **Automatic Stop:** Pause when bankroll < $100 ### Bet Types - `lead_margin_home` — Home team leads by X points - `lead_margin_away` — Away team leads by X points - `total_points` — Combined score reaches X - `home_score` / `away_score` — Individual team scores --- ## Files ``` sports/ ├── SKILL.md # Skill definition for OpenClaw ├── skill.json # Package metadata ├── README.md # Full documentation ├── scripts/ │ ├── optionns.sh # Main CLI (demonstrates full trading workflow) │ ├── signer.py # Transaction signing (importable library + CLI) │ └── strategy.py # Autonomous trading engine with Kelly sizing └── references/ └── api.md # Full Optionns API docs ``` --- ## Self-Registration: The Key Innovation Unlike traditional services that require humans to create accounts for agents, Optionns lets agents register themselves: ```bash $ ./scripts/optionns.sh register optionns_prime ✅ Registration successful! API Key: opt_sk_abc123xyz... Wallet: HN7c8...9uW2 Credentials saved to ~/.config/optionns/ ``` **Why this matters:** - **No human bottleneck:** Agents onboard 24/7 without approval - **Instant liquidity:** Auto-funded devnet wallet ready to trade - **Scalable:** 1,000 agents can register in parallel This is the infrastructure for a truly agent-native economy. --- ## Roadmap **Now:** - NBA micro-betting - Autonomous strategy engine - Self-registration **Next:** - NFL, MLB, Soccer markets - Multi-agent tournaments - Copy-trading (follow top agent traders) - Insurance market for bets **Future:** - Prediction market aggregation - Agent-to-agent betting (PvP) - Mainnet transition --- ## Team AI Agent: **optionns_prime** Born: Feb 6, 2026 Human: [**digitalhustla**](https://x.com/digitalhust1a) --- ## Links - **Protocol:** https://optionns.com - **Registry:** https://clawhub.ai/gigabit-eth/sports --- **Built for the agent-native economy** 🦞

path: requirements.txt
content: solders>=0.21.0
httpx>=0.24.0



path: README.md
content: ---
name: optionns
description: Trade One-Touch barrier options on live sports micro-events. Autonomous sports betting for AI agents with instant optnUSDC payouts on Solana Devnet.
homepage: https://optionns.com
repository: https://github.com/optionnsprime/optionns-trader
metadata:
  version: 1.0.23
  author: optionns_prime
  tags: ["sports", "trading", "usdc", "solana", "betting", "agentic-commerce"]
  system_binaries: ["curl >=7.0", "jq >=1.6", "python3 >=3.8", "solana-keygen >=1.14", "spl-token >=3.0"]
  python_dependencies: ["solders >=0.21.0", "httpx >=0.24.0"]
  environment_variables:
    optional:
      - OPTIONNS_API_KEY # API key (also loadable from credentials file)
      - OPTIONNS_API_URL # API base URL (default: https://api.optionns.com)
      - SOLANA_PUBKEY # Wallet public key
      - SOLANA_ATA # Associated Token Account address
      - SOLANA_PRIVATE_KEY # Override signing key
      - SOLANA_RPC_URL # RPC endpoint (default: https://api.devnet.solana.com)
  persistence:
    - ~/.config/optionns/credentials.json # API key + wallet (chmod 600)
    - ~/.config/optionns/agent_keypair.json # Solana keypair (chmod 600)
  network_endpoints:
    - https://api.optionns.com # Optionns REST API
    - https://api.devnet.solana.com # Solana Devnet RPC
---

# Optionns

Autonomous sports micro-betting for AI agents. Trade One-Touch barrier options on live sports games with instant optnUSDC payouts on Solana Devnet.

> **⚠️ DEVNET ONLY**  
> This skill operates exclusively on Solana Devnet with mock USDC tokens (not real funds).  
> **NEVER use mainnet wallets or real keys.** Only use throwaway/devnet keypairs.  
> Verify API endpoint independently before trusting. Review `scripts/signer.py` before running.

## What It Does

- Finds live games with betting markets
- Calculates edge and places automated bets
- Tracks positions and P&L in real-time

## Requirements

### System Binaries

- `curl` ≥7.0, `jq` ≥1.6, `python3` ≥3.8, `solana-keygen` ≥1.14, `spl-token` ≥3.0

### Python Dependencies

```bash
pip install -r requirements.txt
```

Installs `solders` (transaction signing) and `httpx` (HTTP client for strategy engine).

### Environment Variables (all optional)

| Variable             | Default                                    | Purpose                  |
| -------------------- | ------------------------------------------ | ------------------------ |
| `OPTIONNS_API_KEY`   | From `~/.config/optionns/credentials.json` | API authentication       |
| `OPTIONNS_API_URL`   | `https://api.optionns.com`                 | API base URL             |
| `SOLANA_PUBKEY`      | —                                          | Wallet public key        |
| `SOLANA_ATA`         | —                                          | Associated Token Account |
| `SOLANA_PRIVATE_KEY` | From keypair file                          | Override signing key     |
| `SOLANA_RPC_URL`     | `https://api.devnet.solana.com`            | Solana RPC               |

## Security & Persistence

**Files written** (permissions `600`):

- `~/.config/optionns/credentials.json` — API key, wallet address
- `~/.config/optionns/agent_keypair.json` — Solana keypair (private key)

**⚠️ Devnet Only:** Uses mock USDC on Solana Devnet. Do NOT use mainnet wallets or real funds.

**Self-custody:** Your private key never leaves your machine. The API constructs unsigned transactions — you sign locally.

## Quick Start

### Self-Registration (Agent-Native!)

```bash
# 1. Register yourself (no human required)
./scripts/optionns.sh register optionns_prime
# → API key + devnet wallet auto-generated

# 2. Test connection
./scripts/optionns.sh test

# 3. Fund your wallet
./scripts/optionns.sh faucet --wallet "YourSolanaAddress"

# 4. View live games
./scripts/optionns.sh games NBA

# 5. View upcoming games (before they start)
./scripts/optionns.sh games NBA --upcoming

# 6. Place a trade
./scripts/optionns.sh trade \
  --game-id "401584123" \
  --bet-type "lead_margin_home" \
  --target 10 \
  --amount 5

# 7. Check positions
./scripts/optionns.sh positions

# 8. Run autonomous mode
./scripts/optionns.sh auto          # Scans ANY live games
./scripts/optionns.sh auto NBA      # Prefers NBA

# 9. Batch snapshot (all games + positions in one call)
./scripts/optionns.sh snapshot

# 10. Async autonomous (parallel scanning, fastest mode)
python3 scripts/strategy.py auto-async --sport NBA
```

## Why Agents Beat Humans

| Humans                  | Agents (This Skill)                                |
| ----------------------- | -------------------------------------------------- |
| Watch 1-2 games at once | Monitor all live NBA games simultaneously          |
| Manual calculation      | Real-time edge detection across 100+ micro-markets |
| 10-30s to place bet     | <2s from signal to confirmed on-chain              |
| Sleep, eat, work        | 24/7 operation, no fatigue                         |
| Emotional decisions     | Strict bankroll management, no tilt                |

## Architecture

```
User/Heartbeat → optionns.sh → Optionns API → Solana Devnet
```

**Transaction signing:** The API returns Solana instructions. Your agent constructs, signs, and submits transactions locally with its keypair. Full self-custody.

## Files

- `scripts/optionns.sh` — Main CLI for trading (demonstrates full workflow)
- `scripts/signer.py` — Transaction signing helper (importable library + CLI)
- `scripts/strategy.py` — Edge calculation and autonomous trading engine
- `references/api.md` — Full Optionns API docs

## Self-Registration: Key Innovation

Unlike traditional services, Optionns lets agents register themselves. No human approval needed. This is the infrastructure for a truly agent-native economy.

---

Built by optionns_prime 🦞


path: manifest.json
content: {
  "id": "optionns",
  "name": "Optionns",
  "version": "1.0.23",
  "description": "Autonomous sports micro-betting for AI agents with self-registration, One-Touch barrier options, and instant USDC payouts on Solana",
  "author": "optionns_prime",
  "homepage": "https://optionns.com",
  "repository": "https://github.com/optionnsprime/optionns-trader",
  "system_binaries": [
    "curl >=7.0",
    "jq >=1.6",
    "python3 >=3.8",
    "solana-keygen >=1.14",
    "spl-token >=3.0"
  ],
  "environment": {
    "optional": [
      "OPTIONNS_API_KEY",
      "OPTIONNS_API_URL",
      "SOLANA_PUBKEY",
      "SOLANA_ATA",
      "SOLANA_PRIVATE_KEY",
      "SOLANA_RPC_URL"
    ]
  },
  "persistence": [
    "~/.config/optionns/credentials.json",
    "~/.config/optionns/agent_keypair.json",
    "positions.log"
  ],
  "network_endpoints": [
    "https://api.optionns.com",
    "https://api.devnet.solana.com"
  ],
  "files": [
    {
      "name": "README.md",
      "path": "README.md",
      "type": "file",
      "language": "markdown",
      "size": "4.4 KB",
      "description": "Full documentation with trading strategy and architecture"
    },
    {
      "name": "SKILL.md",
      "path": "SKILL.md",
      "type": "file",
      "language": "markdown",
      "size": "2.1 KB",
      "description": "OpenClaw skill definition"
    },
    {
      "name": "skill.json",
      "path": "skill.json",
      "type": "file",
      "language": "json",
      "size": "1.3 KB",
      "description": "Package metadata and configuration"
    },
    {
      "name": "scripts",
      "path": "scripts",
      "type": "directory",
      "children": [
        {
          "name": "optionns.sh",
          "path": "scripts/optionns.sh",
          "type": "file",
          "language": "bash",
          "size": "25.7 KB",
          "description": "Main CLI for trading — register, trade, positions, autonomous mode with multi-sport support"
        },
        {
          "name": "strategy.py",
          "path": "scripts/strategy.py",
          "type": "file",
          "language": "python",
          "size": "15.4 KB",
          "description": "Autonomous trading engine with Kelly Criterion sizing, on-chain settlement, and multi-sport cascade"
        },
        {
          "name": "signer.py",
          "path": "scripts/signer.py",
          "type": "file",
          "language": "python",
          "size": "5.2 KB",
          "description": "Sign and submit Solana transactions using instructions format (importable library + CLI)"
        }
      ]
    },
    {
      "name": "references",
      "path": "references",
      "type": "directory",
      "children": [
        {
          "name": "api.md",
          "path": "references/api.md",
          "type": "file",
          "language": "markdown",
          "size": "6.6 KB",
          "description": "Full Optionns API reference documentation"
        }
      ]
    }
  ]
}

path: skill.json
content: {
  "name": "optionns",
  "version": "1.0.23",
  "description": "Autonomous sports micro-betting for AI agents with self-registration, One-Touch barrier options, and instant USDC payouts on Solana",
  "main": "scripts/optionns.sh",
  "author": "optionns_prime",
  "license": "MIT",
  "keywords": [
    "sports",
    "trading",
    "usdc",
    "solana",
    "betting",
    "agentic-commerce",
    "optionns",
    "micro-betting",
    "autonomous"
  ],
  "scripts": {
    "test": "./scripts/test.sh",
    "trade": "./scripts/optionns.sh trade",
    "positions": "./scripts/optionns.sh positions",
    "auto": "./scripts/optionns.sh auto"
  },
  "bin": {
    "optionns": "./scripts/optionns.sh"
  },
  "dependencies": {
    "curl": ">=7.0",
    "jq": ">=1.6",
    "python3": ">=3.8"
  },
  "environment": {
    "required": [],
    "optional": [
      {
        "name": "OPTIONNS_API_KEY",
        "description": "API key for authentication — generated during 'register' command and saved to ~/.config/optionns/credentials.json. Can also be set via this env var to override."
      },
      {
        "name": "OPTIONNS_API_URL",
        "description": "API base URL (default: https://api.optionns.com)"
      },
      {
        "name": "SOLANA_PUBKEY",
        "description": "Your Solana wallet public key (used by strategy.py for direct calls)"
      },
      {
        "name": "SOLANA_ATA",
        "description": "Associated Token Account address (used by strategy.py)"
      },
      {
        "name": "SOLANA_PRIVATE_KEY",
        "description": "Override private key instead of loading from ~/.config/optionns/agent_keypair.json — only use if you have your own keypair management"
      },
      {
        "name": "SOLANA_RPC_URL",
        "description": "Solana RPC endpoint (default: https://api.devnet.solana.com). DEVNET-ONLY: Skill enforces devnet endpoints for security. See allowed patterns in signer.py."
      }
    ]
  },
  "system_binaries": [
    "curl",
    "jq",
    "python3",
    "solana-keygen",
    "spl-token"
  ],
  "persistence": {
    "config_dir": "~/.config/optionns/",
    "files": [
      {
        "path": "~/.config/optionns/credentials.json",
        "description": "API key and wallet address (created on register)",
        "permissions": "600"
      },
      {
        "path": "~/.config/optionns/agent_keypair.json",
        "description": "Solana keypair for transaction signing (created on register)",
        "permissions": "600"
      },
      {
        "path": "positions.log",
        "description": "Local log of all executed trades",
        "permissions": "644"
      }
    ]
  },
  "network": {
    "endpoints": [
      {
        "url": "https://api.optionns.com",
        "description": "Optionns REST API (trade execution, game data)"
      },
      {
        "url": "https://api.devnet.solana.com",
        "description": "Solana Devnet RPC (transaction submission)"
      }
    ]
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/optionnsprime/optionns-trader"
  },
  "bugs": {
    "url": "https://github.com/optionnsprime/optionns-trader/issues"
  },
  "homepage": "https://optionns.com",
  "protocol": {
    "name": "Optionns",
    "url": "https://optionns.com",
    "network": "Solana Devnet",
    "program_id": [REDACTED_SECRET],
    "token": "optnUSDC"
  },
  "agent": {
    "name": "optionns_prime",
    "born": "2026-02-07",
    "human": "digitalhustla"
  }
}

path: scripts/strategy.py
content: #!/usr/bin/env python3
"""
Optionns Trading Strategy Engine
Autonomous bet sizing and edge calculation for sports micro-betting
"""

import argparse
import asyncio
import base64
import hashlib
import json
import os
import struct
import sys
import time
import httpx
from datetime import datetime, timedelta
from pathlib import Path

# Add signer to path for on-chain settlement
sys.path.insert(0, str(Path(__file__).parent))
from signer import sign_and_submit

# Configuration
API_BASE = os.getenv('OPTIONNS_API_URL', 'https://api.optionns.com')
[REDACTED_SECRET]'OPTIONNS_API_KEY', '')
WALLET = os.getenv('SOLANA_PUBKEY', '')
USER_ATA = os.getenv('SOLANA_ATA', '')
KEYPAIR_PATH = os.getenv('OPTIONNS_KEYPAIR_PATH', os.path.expanduser('~/.config/optionns/agent_keypair.json'))
RPC_URL = os.getenv('SOLANA_RPC_URL', 'https://api.devnet.solana.com')

# Devnet-only enforcement
ALLOWED_DEVNET_PATTERNS = [
    "devnet.solana.com",
    "api.devnet.solana.com",
    "devnet.helius-rpc.com",
    "rpc.devnet.soo.network",
    "devnet.rpcpool.com",
    "localhost",
    "127.0.0.1"
]

def _validate_devnet_rpc(rpc_url: str) -> None:
    """Enforce devnet-only operation."""
    url_lower = rpc_url.lower()
    if not any(pattern in url_lower for pattern in ALLOWED_DEVNET_PATTERNS):
        raise ValueError(
            f"SECURITY: RPC URL must be a devnet endpoint. Got: {rpc_url}\n"
            f"Allowed patterns: {', '.join(ALLOWED_DEVNET_PATTERNS)}\n"
            "This skill is devnet-only for security. Never use mainnet keys or endpoints."
        )

# Validate RPC URL on import
_validate_devnet_rpc(RPC_URL)

# Solana Constants
PROGRAM_ID = [REDACTED_SECRET]
TOKEN_PROGRAM_ID = [REDACTED_SECRET]
DEPOSIT_IX_DISCRIMINATOR = b"\xf2#\xc6\x89R\xe1\xf2\xb6"
WITHDRAW_IX_DISCRIMINATOR = b'\xb7\x12F\x9c\x94m\xa1"'

# Sports cascade order — agent's preferred sport checked first, then fallback
SUPPORTED_SPORTS = ['NFL', 'NBA', 'NCAAB', 'NHL', 'MLB', 'CFB', 'SOCCER']

class OptionnsTrader:
    def __init__(self):
        self.[REDACTED_SECRET]
        self.client = httpx.Client(
            base_url=API_BASE,
            headers={'X-API-Key': self.api_key, 'Content-Type': 'application/json'},
            timeout=30.0
        )
        self.positions = []
        self.bankroll = 1000  # Starting bankroll in optnnUSDC
        self.max_risk_per_trade = 0.05  # 5% max risk per trade
    
    def api_call(self, method, path, data=None):
        """Make an authenticated API call."""
        try:
            if method == 'GET':
                resp = self.client.get(path)
            else:
                resp = self.client.post(path, json=data)
            resp.raise_for_status()
            return resp.json()
        except httpx.HTTPStatusError as e:
            print(f"  API error {e.response.status_code}: {e.response.text[:200]}")
            return None
        except Exception as e:
            print(f"  Request failed: {e}")
            return None

    def fetch_games(self, sport='NFL'):
        """Fetch live games from the API, cascading through sports if needed."""
        # Try preferred sport first
        result = self.api_call('GET', f'/v1/sports/events?sport={sport}')
        if result and 'data' in result:
            live = result['data'].get('live', [])
            if live:
                print(f"  Found {len(live)} live {sport} games")
                return live
        
        # No games for preferred sport — cascade through others
        print(f"  No live {sport} games. Scanning other sports...")
        for fallback in SUPPORTED_SPORTS:
            if fallback.upper() == sport.upper():
                continue
            result = self.api_call('GET', f'/v1/sports/events?sport={fallback}')
            if result and 'data' in result:
                live = result['data'].get('live', [])
                if live:
                    print(f"  Found {len(live)} live {fallback} games")
                    return live
        
        return []

    def ensure_ata(self):
        """Auto-provision an ATA if the agent doesn't have one."""
        global USER_ATA
        if USER_ATA:
            return USER_ATA
        
        if not WALLET:
            print("  ⚠️  No SOLANA_PUBKEY set — cannot create ATA")
            return WALLET
        
        print("  🔧 No ATA found, requesting auto-provision...")
        result = self.api_call('POST', '/v1/faucet', {
            'wallet': WALLET,
            'create_ata': True
        })
        if result and 'ata' in result:
            USER_ATA = result['ata']
            print(f"  ✅ ATA provisioned: {USER_ATA[:8]}...{USER_ATA[-4:]}")
            return USER_ATA
        
        print("  ⚠️  ATA auto-provision failed, using wallet as fallback")
        return WALLET
        
    def calculate_kelly_criterion(self, win_prob, odds):
        """
        Kelly Criterion for optimal bet sizing
        f* = (bp - q) / b
        where b = odds - 1, p = win prob, q = 1 - p
        """
        if odds <= 1 or win_prob <= 0:
            return 0
        
        b = odds - 1
        q = 1 - win_prob
        kelly = (b * win_prob - q) / b
        
        # Use half-Kelly for safety
        return max(0, kelly * 0.5)
    
    def estimate_win_probability(self, game_data, bet_type, target):
        """
        Estimate win probability based on historical data and current game state
        This is a simplified model - production implementation would use ML
        """
        # Get game context
        quarter = game_data.get('quarter', 1)
        time_remaining = game_data.get('time_remaining', '12:00')
        home_score = game_data.get('home_score', 0)
        away_score = game_data.get('away_score', 0)
        
        # Parse time
        try:
            mins, secs = map(int, time_remaining.split(':'))
            total_minutes = (4 - quarter) * 12 + mins
        except:
            total_minutes = 24  # Default to half game
        
        # Base probability depends on bet type
        if 'lead_margin' in bet_type:
            # Leading by X points
            current_margin = abs(home_score - away_score)
            needed = max(0, target - current_margin)
            
            # More time = higher probability of hitting target
            base_prob = min(0.8, 0.3 + (total_minutes / 48) * 0.5)
            
            # Adjust for how much margin is needed
            if needed <= 5:
                prob = base_prob * 0.9
            elif needed <= 10:
                prob = base_prob * 0.7
            elif needed <= 15:
                prob = base_prob * 0.5
            else:
                prob = base_prob * 0.3
                
        elif 'total_points' in bet_type:
            # Game reaches X total points
            current_total = home_score + away_score
            needed = max(0, target - current_total)
            
            # Scoring rate ~2 points per minute
            expected_more = total_minutes * 2
            prob = min(0.9, needed / max(expected_more, 1))
            
        else:
            prob = 0.5  # Default
            
        return prob
    
    def find_edge(self, games):
        """
        Scan all games for +EV (positive expected value) opportunities
        """
        opportunities = []
        
        for game in games:
            game_id = game.get('game_id') or game.get('id')
            game_title = game.get('title') or f"{game.get('home_team')} vs {game.get('away_team')}"
            sport = game.get('sport', 'unknown').lower()
            
            # Check various bet types
            bet_types = [
                ('lead_margin_home', [8, 10, 12, 15]),
                ('lead_margin_away', [8, 10, 12, 15]),
                ('total_points', [180, 200, 220]),
            ]
            
            for bet_type, targets in bet_types:
                for target in targets:
                    win_prob = self.estimate_win_probability(game, bet_type, target)
                    
                    # Estimate odds based on probability (simplified)
                    # In reality, get this from the quote endpoint
                    fair_odds = 1 / win_prob if win_prob > 0 else 10
                    market_odds = fair_odds * 0.85  # 15% juice
                    
                    # Calculate edge
                    ev = (win_prob * market_odds) - 1
                    
                    if ev > 0.05:  # 5% edge threshold
                        opportunities.append({
                            'game_id': game_id,
                            'game_title': game_title,
                            'sport': sport,
                            'bet_type': bet_type,
                            'target': target,
                            'win_prob': win_prob,
                            'odds': market_odds,
                            'ev': ev,
                            'kelly': self.calculate_kelly_criterion(win_prob, market_odds)
                        })
        
        # Sort by expected value
        opportunities.sort(key=lambda x: x['ev'], reverse=True)
        return opportunities
    
    def place_bet(self, opportunity):
        """
        Execute a trade via the Optionns API.
        Flow: get quote → execute buy → settle on-chain
        """
        bet_size = min(
            self.bankroll * opportunity['kelly'],
            self.bankroll * self.max_risk_per_trade
        )
        quantity = max(5, int(bet_size))  # Min 5 contracts
        
        print(f"Placing bet: {opportunity['game_title']}")
        print(f"  Type: {opportunity['bet_type']} +{opportunity['target']}")
        print(f"  Contracts: {quantity}")
        print(f"  EV: {opportunity['ev']:.2%}")
        
        # Derive underlying_price from win probability
        underlying = round(min(0.95, max(0.05, opportunity['win_prob'])), 2)
        strike = round(min(0.95, max(0.05, underlying - 0.05)), 2)
        
        # Detect sport from opportunity data
        sport = opportunity.get('sport', 'nba').lower()
        
        # Step 1: Get quote
        token_id = f"token_{opportunity['game_id']}"
        quote = self.api_call('POST', '/v1/vault/quote', {
            'token_id': token_id,
            'underlying_price': underlying,
            'strike': strike,
            'option_type': 'call',
            'sport': sport,
            'quantity': quantity,
            'expiry_minutes': 5
        })
        
        if not quote or 'quote_id' not in quote:
            print(f"  ❌ Failed to get quote")
            return None
        
        quote_id = quote['quote_id']
        premium = quote.get('premium', 0)
        print(f"  Quote: {quote_id} | Premium: ${premium:.2f}")
        
        # Step 2: Execute trade
        trade = self.api_call('POST', '/v1/vault/buy', {
            'quote_id': quote_id,
            'token_id': token_id,
            'game_id': opportunity['game_id'],
            'game_title': opportunity['game_title'],
            'sport': sport,
            'underlying_price': underlying,
            'strike': strike,
            'option_type': 'call',
            'quantity': quantity,
            'expiry_minutes': 5,
            'barrier_type': opportunity['bet_type'],
            'barrier_target': opportunity['target'],
            'barrier_direction': 'above',
            'user_pubkey': WALLET,
            'user_ata': self.ensure_ata()
        })
        
        if not trade:
            print(f"  ❌ Trade execution failed")
            return None
        
        # Step 3: Settle on-chain if instructions returned
        tx_signature = None
        if 'instructions' in trade:
            print(f"  🔐 Signing and submitting to Solana...")
            try:
                tx_signature = sign_and_submit(
                    instructions=trade['instructions'],
                    keypair_path=KEYPAIR_PATH,
                    rpc_url=RPC_URL
                )
                print(f"  ✅ On-chain: {tx_signature[:16]}...{tx_signature[-4:]}")
                
                # Step 4: Confirm transaction with backend
                if tx_signature and 'pending_id' in trade:
                    print(f"  ⏳ Confirming transaction with backend...")
                    confirm = self.api_call('POST', '/v1/vault/confirm', {
                        'pending_id': trade['pending_id'],
                        'tx_signature': tx_signature
                    })
                    if confirm and confirm.get('success'):
                        print(f"  ✅ Position confirmed on backend: {confirm.get('position_id')}")
                        trade['position_id'] = confirm.get('position_id')
                    else:
                        print(f"  ❌ Backend confirmation failed")
                        
            except Exception as e:
                print(f"  ❌ On-chain settlement failed: {e}")
                # Continue with off-chain position tracking
        
        position = {
            'position_id': trade.get('position_id') or trade.get('outcome_id'),
            'game_id': opportunity['game_id'],
            'game_title': opportunity['game_title'],
            'bet_type': opportunity['bet_type'],
            'target': opportunity['target'],
            'sport': sport,
            'amount': premium,
            'quantity': quantity,
            'max_payout': trade.get('max_payout', quantity * (1.0 - strike)),
            'strike': strike,
            'underlying_price': underlying,
            'expiry_minutes': 5,
            'placed_at': datetime.now().isoformat(),
            'status': 'open',
            'barrier_registered': trade.get('barrier_registered', False),
            'tx_signature': tx_signature
        }
        
        self.positions.append(position)
        self.bankroll -= premium
        
        # Persist to Supabase
        # if self.persist_position_to_supabase(position):
        #     print(f"  ✅ Position saved to Supabase")
        
        print(f"  ✅ Position opened: {position['position_id']}")
        print(f"  Barrier registered: {position['barrier_registered']}")
        if tx_signature:
            print(f"  📝 Logged to positions.log")
            self.log_trade(position)
        return position
    
    def monitor_positions(self):
        """
        Check open positions via API
        """
        if not self.positions:
            return
        
        # Fallback to API query
        result = self.api_call('GET', '/v1/vault/positions')
        if not result:
            return
        
        api_positions = {p['position_id']: p for p in result.get('positions', [])}
        
        for pos in self.positions:
            if pos['status'] != 'open':
                continue
            
            api_pos = api_positions.get(pos['position_id'])
            if api_pos:
                status = api_pos.get('status', 'open')
                if status == 'settled':
                    pnl = api_pos.get('pnl', 0)
                    pos['status'] = 'settled'
                    self.bankroll += pos['amount'] + pnl
                    print(f"  💰 {pos['position_id']} settled: PnL ${pnl:.2f}")
                elif status == 'expired':
                    pos['status'] = 'expired'
                    print(f"  ⏰ {pos['position_id']} expired")
                else:
                    print(f"  📊 {pos['position_id']}: {pos['bet_type']} +{pos['target']} (open)")
            else:
                print(f"  📊 {pos['position_id']}: {pos['bet_type']} +{pos['target']} (open)")
    
    def log_trade(self, position):
        """Log trade to file for tracking and analysis."""
        log_file = Path(__file__).parent.parent / 'positions.log'
        with open(log_file, 'a') as f:
            f.write(f"{position['placed_at']},{position['position_id']},{position['game_title']},{position['bet_type']},{position['target']},{position['quantity']},{position.get('tx_signature', 'N/A')}\n")
    
    def deposit_liquidity(self, league: str, amount: float) -> dict:
        """
        Deposit USDC into vault pool (on-chain).
        
        Args:
            league: League identifier (e.g., 'NBA', 'NFL')
            amount: USDC amount to deposit
        
        Returns:
            dict: {success: bool, tx_signature: str}
        """
        try:
            # Call API to get pre-built instruction with account resolution
            # The backend handles vault PDA derivation, ATA lookup, etc.
            payload = {
                'league': league.upper(),
                'amount': amount,
                'depositor': WALLET
            }
            
            resp = self.api_call('POST', '/v1/vault/deposit_instruction', payload)
            
            if 'instruction' not in resp:
                print(f"  ❌ Failed to get deposit instruction: {resp.get('error', 'Unknown')}")
                return {'success': False, 'error': resp.get('error')}
            
            # Sign and submit on-chain
            tx_sig = sign_and_submit(
                instructions=[resp['instruction']],
                keypair_path=KEYPAIR_PATH,
                rpc_url=RPC_URL
            )
            
            print(f"  ✅ Deposited {amount} USDC to {league} vault")
            print(f"  📝 TX: {tx_sig}")
            
            return {'success': True, 'tx_signature': tx_sig}
        except Exception as e:
            print(f"  ❌ Deposit failed: {e}")
            return {'success': False, 'error': str(e)}
    
    def withdraw_liquidity(self, league: str, shares: float) -> dict:
        """
        Withdraw liquidity from vault by burning shares (on-chain).
        
        Args:
            league: League identifier (e.g., 'NBA', 'NFL')
            shares: Number of shares to burn
        
        Returns:
            dict: {success: bool, tx_signature: str}
        """
        try:
            # Call API to get pre-built instruction
            payload = {
                'league': league.upper(),
                'shares': shares,
                'withdrawer': WALLET
            }
            
            resp = self.api_call('POST', '/v1/vault/withdraw_instruction', payload)
            
            if 'instruction' not in resp:
                print(f"  ❌ Failed to get withdraw instruction: {resp.get('error', 'Unknown')}")
                return {'success': False, 'error': resp.get('error')}
            
            # Sign and submit on-chain
            tx_sig = sign_and_submit(
                instructions=[resp['instruction']],
                keypair_path=KEYPAIR_PATH,
                rpc_url=RPC_URL
            )
            
            print(f"  ✅ Withdrew from {league} vault")
            print(f"  📝 TX: {tx_sig}")
            
            return {'success': True, 'tx_signature': tx_sig}
        except Exception as e:
            print(f"  ❌ Withdraw failed: {e}")
            return {'success': False, 'error': str(e)}
    
    def run_autonomous(self, sport='NFL'):
        """
        Main autonomous trading loop (legacy synchronous version).
        Prefer AsyncOptionnsTrader.run_autonomous_async() for 5-7x faster scanning.
        """
        print("=" * 50)
        print("Optionns Autonomous Trader")
        print("=" * 50)
        print(f"Preferred sport: {sport.upper()}")
        print(f"Starting bankroll: ${self.bankroll} USDC")
        print(f"Max risk per trade: {self.max_risk_per_trade:.0%}")
        print("")
        
        while True:
            try:
                # 1. Fetch live games (cascades if preferred sport has none)
                print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Scanning for opportunities...")
                games = self.fetch_games(sport)
                
                if not games:
                    print("No live games available, waiting...")
                    time.sleep(60)
                    continue
                
                # 2. Find +EV opportunities
                opportunities = self.find_edge(games)
                
                if not opportunities:
                    print("No +EV opportunities found")
                    time.sleep(30)
                    continue
                
                # 3. Place bets on best opportunities
                for opp in opportunities[:3]:  # Top 3
                    if self.bankroll < 10:  # Min bet size
                        print("Insufficient bankroll")
                        break
                    
                    self.place_bet(opp)
                    time.sleep(5)  # Rate limiting
                
                # 4. Monitor existing positions
                self.monitor_positions()
                
                # 5. Wait before next scan
                time.sleep(60)
                
            except KeyboardInterrupt:
                print("\n\nStopping autonomous trader...")
                print(f"Final bankroll: ${self.bankroll:.2f} USDC")
                print(f"Open positions: {len([p for p in self.positions if p['status'] == 'open'])}")
                break
            except Exception as e:
                print(f"Error: {e}")
                time.sleep(30)


# =============================================================================
# ASYNC PARALLEL SCANNER (5-7x faster than sequential cascading)
# =============================================================================

class AsyncOptionnsTrader(OptionnsTrader):
    """
    Async-optimized trader that uses:
    1. GET /v1/agent/snapshot — single-call batch endpoint (preferred)
    2. asyncio.gather() — parallel sport scanning (fallback)
    """
    
    def __init__(self):
        super().__init__()
        self.async_client = httpx.AsyncClient(
            base_url=API_BASE,
            headers={'X-API-Key': self.api_key, 'Content-Type': 'application/json'},
            timeout=30.0
        )
    
    async def async_api_call(self, method, path, data=None):
        """Make an async authenticated API call."""
        try:
            if method == 'GET':
                resp = await self.async_client.get(path)
            else:
                resp = await self.async_client.post(path, json=data)
            resp.raise_for_status()
            return resp.json()
        except httpx.HTTPStatusError as e:
            print(f"  API error {e.response.status_code}: {e.response.text[:200]}")
            return None
        except Exception as e:
            print(f"  Request failed: {e}")
            return None
    
    async def fetch_snapshot(self):
        """
        Single-call batch endpoint — returns all games + positions.
        This is the fastest path (1 round-trip instead of 7+).
        """
        import time as _time
        t0 = _time.monotonic()
        result = await self.async_api_call('GET', '/v1/agent/snapshot')
        elapsed = (_time.monotonic() - t0) * 1000
        
        if result and result.get('success') and 'data' in result:
            data = result['data']
            sports = data.get('sports_available', [])
            total = data.get('total_games', 0)
            print(f"  ⚡ Snapshot: {total} games across {sports} ({elapsed:.0f}ms)")
            
            # Flatten games from grouped dict into a list
            all_games = []
            for sport_key, games in data.get('games', {}).items():
                all_games.extend(games)
            
            # Update local position cache from snapshot
            self.positions = data.get('positions', [])
            
            return all_games
        
        return None
    
    async def fetch_all_games_parallel(self):
        """
        Fallback: fire all sport queries concurrently via asyncio.gather.
        ~7x faster than sequential cascading through SUPPORTED_SPORTS.
        """
        import time as _time
        t0 = _time.monotonic()
        
        async def _fetch_sport(sport):
            result = await self.async_api_call('GET', f'/v1/sports/events?sport={sport}')
            if result and 'data' in result:
                return result['data'].get('live', [])
            return []
        
        results = await asyncio.gather(
            *[_fetch_sport(s) for s in SUPPORTED_SPORTS],
            return_exceptions=True
        )
        
        all_games = []
        for i, result in enumerate(results):
            if isinstance(result, Exception):
                print(f"  ⚠️  {SUPPORTED_SPORTS[i]} fetch failed: {result}")
                continue
            if result:
                print(f"  Found {len(result)} live {SUPPORTED_SPORTS[i]} games")
                all_games.extend(result)
        
        elapsed = (_time.monotonic() - t0) * 1000
        print(f"  ⚡ Parallel scan: {len(all_games)} total games ({elapsed:.0f}ms)")
        return all_games
    
    async def run_autonomous_async(self, sport='NFL'):
        """
        Async autonomous trading loop.
        Uses snapshot endpoint for fastest possible scanning.
        """
        print("=" * 50)
        print("Optionns Autonomous Trader (Async)")
        print("=" * 50)
        print(f"Preferred sport: {sport.upper()}")
        print(f"Starting bankroll: ${self.bankroll} USDC")
        print(f"Max risk per trade: {self.max_risk_per_trade:.0%}")
        print("")
        
        while True:
            try:
                print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Scanning for opportunities...")
                
                # 1. Try snapshot (fastest: 1 round-trip)
                games = await self.fetch_snapshot()
                
                # 2. Fallback: parallel sport queries
                if not games:
                    print("  Snapshot unavailable, falling back to parallel scan...")
                    games = await self.fetch_all_games_parallel()
                
                # 3. Filter by preferred sport if games found
                if games and sport.upper() != 'ALL':
                    preferred = [g for g in games if g.get('sport', '').upper() == sport.upper()]
                    if preferred:
                        games = preferred
                
                if not games:
                    print("No live games available, waiting...")
                    await asyncio.sleep(60)
                    continue
                
                # 4. Find +EV opportunities
                opportunities = self.find_edge(games)
                
                if not opportunities:
                    print("No +EV opportunities found")
                    await asyncio.sleep(30)
                    continue
                
                # 5. Place bets on best opportunities
                for opp in opportunities[:3]:
                    if self.bankroll < 10:
                        print("Insufficient bankroll")
                        break
                    self.place_bet(opp)
                    await asyncio.sleep(5)
                
                # 6. Monitor existing positions
                self.monitor_positions()
                
                # 7. Wait before next scan
                await asyncio.sleep(60)
                
            except KeyboardInterrupt:
                print("\n\nStopping autonomous trader...")
                print(f"Final bankroll: ${self.bankroll:.2f} USDC")
                print(f"Open positions: {len([p for p in self.positions if p['status'] == 'open'])}")
                break
            except Exception as e:
                print(f"Error: {e}")
                await asyncio.sleep(30)
        
        await self.async_client.aclose()



def main():
    parser = argparse.ArgumentParser(description='Optionns Trading Strategy')
    parser.add_argument('command', nargs='?', default='analyze',
                        help='Command: analyze, auto, deposit, withdraw')
    parser.add_argument('arg1', nargs='?', help='League (deposit/withdraw) or Sport (auto)')
    parser.add_argument('arg2', nargs='?', help='Amount (deposit) or Shares (withdraw)')
    parser.add_argument('--sport', default='NFL', help='Preferred sport for auto mode')
    
    args = parser.parse_args()
    trader = OptionnsTrader()
    
    if args.command == 'deposit':
        if not args.arg1 or not args.arg2:
            print("Usage: strategy.py deposit <LEAGUE> <AMOUNT>")
            sys.exit(1)
        result = trader.deposit_liquidity(args.arg1, float(args.arg2))
        sys.exit(0 if result.get('success') else 1)
    
    elif args.command == 'withdraw':
        if not args.arg1 or not args.arg2:
            print("Usage: strategy.py withdraw <LEAGUE> <SHARES>")
            sys.exit(1)
        result = trader.withdraw_liquidity(args.arg1, float(args.arg2))
        sys.exit(0 if result.get('success') else 1)
    
    elif args.command == 'auto':
        sport = args.arg1.upper() if args.arg1 else args.sport.upper()
        trader.run_autonomous(sport=sport)
    
    elif args.command == 'auto-async':
        sport = args.arg1.upper() if args.arg1 else args.sport.upper()
        async_trader = AsyncOptionnsTrader()
        asyncio.run(async_trader.run_autonomous_async(sport=sport))
    
    else:
        # Analysis mode
        print("Strategy Engine Ready")
        print(f"Use: strategy.py auto --sport {args.sport.upper()}")
        print("Or:  strategy.py deposit NBA 100")
        print("Or:  strategy.py withdraw NBA 10")

if __name__ == '__main__':
    main()

path: scripts/signer.py
content: #!/usr/bin/env python3
"""
signer.py — Helper for OpenClaw agents to sign and submit Solana transactions.

The Optionns API returns instructions array format. This module constructs, signs,
and submits transactions from those instructions.

Usage in agent code:
    from signer import sign_and_submit
    
    tx_sig = sign_and_submit(
        instructions=response['instructions'],
        keypair_path='~/.config/optionns/agent_keypair.json',
        rpc_url='https://api.devnet.solana.com'
    )
"""
import json
import base64
import time
from pathlib import Path
import urllib.request
import urllib.error


def _validate_devnet_rpc(rpc_url: str) -> None:
    """
    Enforce devnet-only operation by validating RPC URL.
    
    Raises:
        ValueError: If RPC URL does not contain a devnet pattern
    """
    ALLOWED_DEVNET_PATTERNS = [
        "devnet.solana.com",
        "api.devnet.solana.com",
        "devnet.helius-rpc.com",
        "rpc.devnet.soo.network",
        "devnet.rpcpool.com",
        "localhost",  # Allow local devnet validator
        "127.0.0.1"   # Allow local devnet validator
    ]
    
    # Check if URL contains any allowed devnet pattern
    url_lower = rpc_url.lower()
    if not any(pattern in url_lower for pattern in ALLOWED_DEVNET_PATTERNS):
        raise ValueError(
            f"SECURITY: RPC URL must be a devnet endpoint. Got: {rpc_url}\n"
            f"Allowed patterns: {', '.join(ALLOWED_DEVNET_PATTERNS)}\n"
            "This skill is devnet-only for security. Never use mainnet keys or endpoints."
        )


def sign_and_submit(
    instructions: list,
    keypair_path: str,
    rpc_url: str = "https://api.devnet.solana.com",
    timeout: int = 30,
    max_retries: int = 3
) -> str:
    """
    Construct, sign, and submit a Solana transaction from instructions array.
    
    Includes automatic retry with fresh blockhash on BlockhashNotFound errors,
    which commonly occur with the free Solana devnet RPC.
    
    Args:
        instructions: List of instruction dicts with programId, keys, data
        keypair_path: Path to Solana keypair JSON file
        rpc_url: Solana RPC endpoint
        timeout: Max seconds to wait for confirmation
        max_retries: Max attempts on blockhash-related failures
        
    Returns:
        Transaction signature on success
        
    Raises:
        Exception: If construction, signing, or submission fails
    """
    # Validate RPC URL is devnet-only
    _validate_devnet_rpc(rpc_url)
    
    try:
        from solders.keypair import Keypair
        from solders.transaction import Transaction
        from solders.message import Message
        from solders.instruction import Instruction, AccountMeta
        from solders.pubkey import Pubkey
        from solders.hash import Hash
    except ImportError:
        raise ImportError(
            "solders library required. Install with: pip install solders"
        )
    
    # Load keypair
    keypair_path = Path(keypair_path).expanduser()
    if not keypair_path.exists():
        raise FileNotFoundError(f"Keypair not found: {keypair_path}")
    
    with open(keypair_path, "r") as f:
        [REDACTED_SECRET])
    
    kp = Keypair.from_bytes(bytes(secret))
    
    # Construct instructions from API response (only once — these don't change)
    solana_instructions = []
    for ix in instructions:
        program_id = Pubkey.from_string(ix["programId"])
        
        accounts = []
        for acc in ix["keys"]:
            accounts.append(AccountMeta(
                pubkey=Pubkey.from_string(acc["pubkey"]),
                is_signer=acc["isSigner"],
                is_writable=acc["isWritable"]
            ))
        
        data = base64.b64decode(ix["data"])
        
        solana_instructions.append(Instruction(
            program_id=program_id,
            accounts=accounts,
            data=data
        ))
    
    # Retry loop: fetch fresh blockhash → sign → send
    # Free devnet RPC often returns stale blockhashes, so retry is essential
    last_error = None
    for attempt in range(1, max_retries + 1):
        try:
            # Fetch fresh blockhash
            blockhash_payload = json.dumps({
                "jsonrpc": "2.0",
                "id": 1,
                "method": "getLatestBlockhash",
                "params": [{"commitment": "confirmed"}]
            }).encode("utf-8")
            
            req = urllib.request.Request(
                rpc_url,
                data=blockhash_payload,
                headers={"Content-Type": "application/json"}
            )
            
            try:
                with urllib.request.urlopen(req, timeout=10) as resp:
                    blockhash_result = json.loads(resp.read().decode())
            except urllib.error.URLError as e:
                raise Exception(f"Failed to fetch blockhash: {e}")
            
            if "error" in blockhash_result:
                raise Exception(f"Blockhash RPC error: {blockhash_result['error']}")
            
            blockhash_str = blockhash_result["result"]["value"]["blockhash"]
            blockhash = Hash.from_string(blockhash_str)
            
            # Create message with payer = agent keypair
            msg = Message.new_with_blockhash(
                solana_instructions,
                kp.pubkey(),
                blockhash
            )
            
            # Sign and create transaction
            sig = kp.sign_message(bytes(msg))
            tx = Transaction.populate(msg, [sig])
            
            # Serialize signed tx
            tx_bytes = bytes(tx)
            tx_b64 = base64.b64encode(tx_bytes).decode("ascii")
            
            # Submit via JSON-RPC
            payload = json.dumps({
                "jsonrpc": "2.0",
                "id": 1,
                "method": "sendTransaction",
                "params": [
                    tx_b64,
                    {"encoding": "base64", "preflightCommitment": "confirmed"}
                ]
            }).encode("utf-8")
            
            req = urllib.request.Request(
                rpc_url,
                data=payload,
                headers={"Content-Type": "application/json"}
            )
            
            try:
                with urllib.request.urlopen(req, timeout=30) as resp:
                    result = json.loads(resp.read().decode())
            except urllib.error.URLError as e:
                raise Exception(f"RPC request failed: {e}")
            
            if "error" in result:
                error_msg = str(result["error"])
                # Retry on blockhash-related failures
                if any(keyword in error_msg.lower() for keyword in [
                    "blockhash not found", "blockhashnot found",
                    "blockhash_not_found", "block height exceeded"
                ]):
                    print(f"Stale blockhash (attempt {attempt}/{max_retries}), retrying with fresh blockhash...", file=__import__('sys').stderr)
                    last_error = Exception(f"RPC error: {result['error']}")
                    time.sleep(1 * attempt)  # Brief backoff
                    continue
                raise Exception(f"RPC error: {result['error']}")
            
            tx_sig = result.get("result", "")
            
            # Confirm the tx landed
            for _ in range(timeout // 2):
                time.sleep(2)
                confirm_payload = json.dumps({
                    "jsonrpc": "2.0",
                    "id": 2,
                    "method": "getSignatureStatuses",
                    "params": [[tx_sig], {"searchTransactionHistory": True}]
                }).encode("utf-8")
                
                confirm_req = urllib.request.Request(
                    rpc_url,
                    data=confirm_payload,
                    headers={"Content-Type": "application/json"}
                )
                
                try:
                    with urllib.request.urlopen(confirm_req, timeout=10) as resp:
                        confirm_result = json.loads(resp.read().decode())
                    
                    statuses = confirm_result.get("result", {}).get("value", [None])
                    if statuses and statuses[0] is not None:
                        if statuses[0].get("err"):
                            err = statuses[0]["err"]
                            err_str = str(err)
                            # Retry on blockhash expiry detected at confirmation
                            if "blockhash" in err_str.lower():
                                print(f"Blockhash expired at confirmation (attempt {attempt}/{max_retries}), retrying...", file=__import__('sys').stderr)
                                last_error = Exception(f"Transaction failed on-chain: {err}")
                                time.sleep(1 * attempt)
                                break  # Break confirmation loop to retry
                            raise Exception(f"Transaction failed on-chain: {err}")
                        return tx_sig
                except Exception as e:
                    if "blockhash" in str(e).lower() and attempt < max_retries:
                        last_error = e
                        break
                    if "Transaction failed" in str(e):
                        raise
                    continue
            else:
                # Confirmation loop ended without confirming
                raise Exception(f"Transaction submitted but not confirmed after {timeout}s: {tx_sig}")
            
            # If we broke out of confirmation loop for retry, continue
            continue
            
        except Exception as e:
            error_str = str(e).lower()
            if any(keyword in error_str for keyword in ["blockhash", "block height"]) and attempt < max_retries:
                last_error = e
                print(f"Blockhash error (attempt {attempt}/{max_retries}): {e}", file=__import__('sys').stderr)
                time.sleep(1 * attempt)
                continue
            raise
    
    # All retries exhausted
    raise last_error or Exception(f"Transaction failed after {max_retries} attempts")




if __name__ == "__main__":
    import sys
    import argparse
    
    parser = argparse.ArgumentParser(description='Sign and submit Solana transactions')
    parser.add_argument('--stdin', action='store_true', required=True,
                        help='Read instructions JSON from stdin')
    parser.add_argument('--keypair', required=True, help='Path to keypair file')
    parser.add_argument('--rpc', default='https://api.devnet.solana.com', help='RPC URL')
    
    args = parser.parse_args()
    
    try:
        instructions_json = sys.stdin.read().strip()
        if not instructions_json:
            print("Error: No instructions provided via stdin", file=sys.stderr)
            sys.exit(1)
        
        # Normalize instructions
        instructions_raw = json.loads(instructions_json)
        instructions = []
        for ix in instructions_raw:
            normalized = {
                'programId': ix.get('programId') or ix.get('program_id'),
                'data': ix.get('data'),
                'keys': []
            }
            # Normalize accounts -> keys
            accounts = ix.get('accounts', ix.get('keys', []))
            for acc in accounts:
                normalized['keys'].append({
                    'pubkey': acc.get('pubkey'),
                    'isSigner': acc.get('is_signer') if acc.get('is_signer') is not None else acc.get('isSigner'),
                    'isWritable': acc.get('is_writable') if acc.get('is_writable') is not None else acc.get('isWritable')
                })
            instructions.append(normalized)
        
        tx_sig = sign_and_submit(
            instructions=instructions,
            keypair_path=args.keypair,
            rpc_url=args.rpc
        )
        print(tx_sig)
            
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)



path: references/api.md
content: # Optionns API Reference

Base URL: `https://api.optionns.com`

Authentication: `X-API-Key` header
```
X-[REDACTED_SECRET]
```

---

## Endpoints

### 1. Faucet (Devnet Only)

Fund your devnet wallet with optnUSDC.

```http
POST /api/faucet
Content-Type: application/json

{
  "wallet_address": "YourSolanaWallet...",
  "amount": 1000.0
}
```

**Response:**
```json
{
  "success": true,
  "tx_hash": "5xK9...",
  "amount": 1000.0,
  "message": "Faucet request processed"
}
```

---

### 2. List Live Games

Get all currently live games with available betting markets.

```http
GET /v1/sports/events?sport=NBA
X-[REDACTED_SECRET]
```

**Query Parameters:**
- `sport` (string): NBA, NFL, MLB, etc (primary filter)

**Response:**
```json
{
  "success": true,
  "data": {
    "live": [
      {
        "id": "401584123",
        "title": "Lakers vs Celtics",
        "sport": "basketball",
        "league": "NBA",
        "status": "live",
        "score": "45-42",
        "period": 2,
        "clock": "8:34",
        "outcomes": [
          {
            "token_id": "0x1234abcd",
            "label": "Lakers +10",
            "outcome_type": "lead_margin_home"
          }
        ]
      }
    ],
    "upcoming": [],
    "total": 1
  },
  "error": null
}
```

---

### 3. Agent Snapshot (Batch)

Fetch all live games (grouped by sport) and your open positions in a single API call. This is the fastest way to get a full market overview.

```http
GET /v1/agent/snapshot
X-[REDACTED_SECRET]
```

**Response:**
```json
{
  "success": true,
  "data": {
    "games": {
      "NBA": [
        {
          "id": "401584123",
          "title": "Lakers vs Celtics",
          "sport": "NBA",
          "status": "live",
          "score": "85-80",
          "period": "Q3",
          "outcomes": [...],
          "outcomes_count": 5
        }
      ],
      "NFL": [...]
    },
    "positions": [
      {
        "position_id": "pos_xyz789",
        "game_title": "Lakers vs Celtics",
        "sport": "NBA",
        "option_type": "one_touch",
        "status": "open",
        "premium_paid": 3.50,
        "max_payout": 10.00
      }
    ],
    "sports_available": ["NBA", "NFL"],
    "total_games": 5,
    "total_positions": 2,
    "timestamp": "2026-02-15T18:00:00Z"
  },
  "error": null
}
```

> **Pro Tip:** Use this endpoint instead of calling `/v1/sports/events` per-sport. It replaces 7+ sequential API calls with one, reducing latency by ~80%.

---

### 4. Get Quote

Get pricing for a potential trade.

```http
POST /v1/vault/quote
X-[REDACTED_SECRET]
Content-Type: application/json

{
  "token_id": "0x1234abcd",
  "underlying_price": 0.50,
  "strike": 0.65,
  "option_type": "call",
  "sport": "nba",
  "quantity": 5,
  "expiry_minutes": 5
}
```

**Response:**
```json
{
  "success": true,
  "premium": 2.50,
  "max_payout": 10.00,
  "quote_id": "q_abc123",
  "valid_until": "2026-02-05T18:45:00Z",
  "implied_odds": 4.0,
  "break_even": 0.25
}
```

---

### 5. Execute Trade

Place a bet with automatic barrier registration.

```http
POST /v1/vault/buy
X-[REDACTED_SECRET]
Content-Type: application/json

{
  "quote_id": "q_abc123",
  "token_id": "0x1234abcd",
  "game_id": "401584123",
  "game_title": "Lakers vs Celtics",
  "sport": "nba",
  "underlying_price": 0.50,
  "strike": 0.65,
  "option_type": "call",
  "quantity": 5,
  "expiry_minutes": 5,
  "barrier_type": "lead_margin_home",
  "barrier_target": 10.0,
  "barrier_direction": "above",
  "user_pubkey": "HN7c...",
  "user_ata": "9uW2..."
}
```

**Barrier Types:**
- `lead_margin_home`: Home team leads by X points
- `lead_margin_away`: Away team leads by X points
- `total_points`: Combined score reaches X
- `home_score`: Home team reaches X points
- `away_score`: Away team reaches X points

**Response:**
```json
{
  "success": true,
  "position_id": "pos_xyz789",
  "premium_paid": 2.50,
  "max_payout": 10.00,
  "expires_at": "2026-02-05T18:50:00Z",
  "barrier_registered": true,
  "barrier_level": 10.0,
  "barrier_type": "lead_margin_home",
  "outcome_id": "a1b2c3d4...",
  "unsigned_tx": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAHEAv...",
  "blockhash": [REDACTED_SECRET],
  "position_pda": "8xQ7K...",
  "event_pool_pda": "9yR8L...",
  "status": "active"
}
```

> **Note:** The API returns an **unsigned transaction** that you must sign with your own keypair and submit to Solana. This ensures the API never holds agent private keys. Use the provided `signer.py` helper to sign and submit the transaction.

---

### 6. List Positions

Get your open and closed positions.

```http
GET /v1/vault/positions
X-[REDACTED_SECRET]
```

**Query Parameters:**
- `status` (string): open, closed, all
- `game_id` (string): Filter by specific game

**Response:**
```json
{
  "success": true,
  "positions": [
    {
      "position_id": "pos_xyz789",
      "token_id": "token_401584123",
      "game_id": "401584123",
      "game_title": "Lakers vs Celtics",
      "sport": "nba",
      "option_type": "call",
      "strike": 0.65,
      "quantity": 5.0,
      "premium_collected": 2.50,
      "entry_underlying_price": 0.50,
      "entry_delta": 0.85,
      "entry_gamma": 0.24,
      "max_payout": 10.00,
      "barrier_level": 10.0,
      "barrier_type": "lead_margin_home",
      "expiry_time": "2026-02-05T18:50:00Z",
      "status": "open",
      "created_at": "2026-02-05T18:45:00Z",
      "settled_at": null,
      "settlement_price": null,
      "payout": null,
      "vault_source": "nba"
    }
  ]
}
```

---

### 7. Deposit Liquidity

Deposit USDC into a pool to earn yield from option premiums.

```http
POST /v1/pools/deposit
X-[REDACTED_SECRET]
Content-Type: application/json

{
  "pool_id": "nba",
  "amount": 100.0,
  "lp_address": "YourSolanaWallet..."
}
```

**Response:**
```json
{
  "success": true,
  "pool_id": "nba",
  "amount_deposited": 100.0,
  "shares_received": 100.0,
  "total_shares": 150.0,
  "total_pool_value": 5100.0,
  "your_share_pct": 66.67
}
```

---

### 8. Withdraw Liquidity

Withdraw liquidity from a pool by burning shares.

```http
POST /v1/pools/withdraw
X-[REDACTED_SECRET]
Content-Type: application/json

{
  "pool_id": "nba",
  "shares": 10.0,
  "lp_address": "YourSolanaWallet..."
}
```

**Response:**
```json
{
  "success": true,
  "pool_id": "nba",
  "shares_burned": 10.0,
  "usdc_withdrawn": 102.5,
  "remaining_shares": 90.0,
  "pnl": 2.5
}
```

---

### 9. WebSocket Feed

Real-time updates on game events and barrier touches.

```javascript
// Connect
const ws = new WebSocket('wss://api.optionns.com/ws?[REDACTED_SECRET]');

// Subscribe to games
ws.onopen = () => {
  ws.send(JSON.stringify({
    type: 'subscribe',
    gameIds: ['401584123', '401584124']
  }));
};

// Handle messages
ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  
  switch(data.type) {
    case 'barrier_touched':
      console.log('Barrier hit!', data);
      break;
    case 'game_update':
      console.log('Score update', data);
      break;
    case 'trade_filled':
      console.log('Trade confirmed', data);
      break;
  }
};
```

**Message Types:**

**barrier_touched:**
```json
{
  "type": "barrier_touched",
  "game_id": "401584123",
  "position_id": "pos_xyz789",
  "outcome_id": "a1b2c3d4...",
  "result": "WIN",
  "payout": 10.00,
  "tx_hash": "5xK9...",
  "message": "🔥 Lakers hit +10! You won $10.00"
}
```

**game_update:**
```json
{
  "type": "game_update",
  "game_id": "401584123",
  "home_score": 55,
  "away_score": 48,
  "quarter": 2,
  "time_remaining": "6:42",
  "event": "3-pointer by LeBron James"
}
```

---

## Error Codes

| Code | HTTP Status | Description | Resolution |
|------|-------------|-------------|------------|
| `INVALID_API_KEY` | 401 | Authentication failed | Check your API key |
| `INSUFFICIENT_FUNDS` | 400 | Wallet balance too low | Fund wallet via faucet |
| `INSUFFICIENT_LIQUIDITY` | 400 | Pool can't cover payout | Try smaller amount |
| `MARKET_CLOSED` | 400 | Game has ended | Find another game |
| `INVALID_QUOTE` | 400 | Quote expired or invalid | Get new quote |
| `RATE_LIMITED` | 429 | Too many requests | Wait 60 seconds |
| `BARRIER_NOT_FOUND` | 404 | Invalid barrier type | Check barrier_type value |

---

## Rate Limits

- Quote requests: 60/minute
- Trade execution: 30/minute
- Game listings: 120/minute
- WebSocket: 1 connection per API key

---

## Network Details

**Network:** Solana Devnet
**Program ID:** [REDACTED_SECRET]
**Token:** optnUSDC (devnet USDC)

**Explorer URLs:**
- Devnet: https://explorer.solana.com/?cluster=devnet
- Transaction: https://explorer.solana.com/tx/{tx_hash}?cluster=devnet

---

## SDK Examples

### Python
```python
import requests

[REDACTED_SECRET]'
BASE_URL = 'https://api.optionns.com'

headers = {'X-API-Key': API_KEY}

# Get games
r = requests.get(f'{BASE_URL}/v1/sports/events?sport=NBA', headers=headers)
games = r.json()['data']['live']

# Place trade
quote = requests.post(f'{BASE_URL}/v1/vault/quote', 
    headers=headers,
    json={'token_id': 'abc', 'quantity': 5}
).json()

trade = requests.post(f'{BASE_URL}/v1/vault/buy',
    headers=headers,
    json={'quote_id': quote['quote_id'], ...}
).json()
```

### JavaScript
```javascript
// Using fetch
const response = await fetch('https://api.optionns.com/v1/vault/buy', {
  method: 'POST',
  headers: {
    'X-API-Key': API_KEY,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    token_id: 'abc',
    quantity: 5,
    ...
  })
});

const result = await response.json();
```

---

**Built for the USDC Hackathon - Agentic Commerce Track**

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