Skill: aviclaw/lighter (version 2.0.1)

--- name: lighter description: Interact with Lighter protocol - a ZK rollup orderbook DEX. Use when you need to trade on Lighter, check prices, manage positions, or query account data. env: required: - LIGHTER_API_KEY - LIGHTER_ACCOUNT_INDEX optional: - LIGHTER_L1_ADDRESS --- # Lighter Protocol Trade on Lighter - a zero-knowledge rollup orderbook DEX with millisecond latency and zero fees. ## Quick Start (Read-Only) ```bash # Markets are public - no credentials needed curl "https://mainnet.zklighter.elliot.ai/api/v1/orderBooks" ``` ## What is Lighter? - Zero fees for retail traders - Millisecond latency - ZK proofs of all operations - Backed by Founders Fund, Robinhood, Coinbase Ventures **API Endpoint:** https://mainnet.zklighter.elliot.ai **Chain ID:** 300 ## ⚠️ Security Considerations ### Third-Party Dependencies This skill can work with **just requests library** for read-only operations. For signing orders, you have two options: **Option A: Minimal (Read-Only)** ```bash pip install requests ``` Only for public data (markets, order books, prices). **Option B: Full Trading** Requires the official Lighter SDK. Review and verify before installing: - SDK Repository: https://github.com/elliottech/lighter-python - Verify the repository owner, stars, and code before running any setup ### External Code **Only proceed with external SDK if you:** 1. Have reviewed the GitHub repository 2. Understand what the code does 3. Use a dedicated burner wallet, not your main wallet ## Environment Variables | Variable | Required | Description | Where to Find | |----------|----------|-------------|---------------| | `LIGHTER_API_KEY` | For orders | API key from Lighter SDK setup | See "Getting an API Key" section below | | `LIGHTER_ACCOUNT_INDEX` | For orders | Your Lighter subaccount index (0-252) | See "Getting Your Account Index" section below | | `LIGHTER_L1_ADDRESS` | Optional | Your ETH address (0x...) used on Lighter | Your MetaMask/Wallet address | ### Setting Up Your Credentials **Step 1: Get your L1 Address** - This is your Ethereum address (e.g., `0x1234...abcd`) - Use the same wallet you connect to Lighter dashboard **Step 2: Get your Account Index** ```bash curl "https://mainnet.zklighter.elliot.ai/api/v1/accountsByL1Address?l1_address=YOUR_ETH_ADDRESS" ``` Response returns `sub_accounts[].index` — that's your account index (typically 0 for main account). **Step 3: Get your API Key** 1. Install Lighter Python SDK: `pip install lighter-python` 2. Follow the setup guide: https://github.com/elliottech/lighter-python/blob/main/examples/system_setup.py 3. The SDK generates API keys tied to your account 4. Store the private key securely — never commit to git **Quick test (read-only, no credentials):** ```bash curl "https://mainnet.zklighter.elliot.ai/api/v1/orderBooks" ``` ## API Usage ### Public Endpoints (No Auth) ```bash # List all markets curl "https://mainnet.zklighter.elliot.ai/api/v1/orderBooks" # Get order book curl "https://mainnet.zklighter.elliot.ai/api/v1/orderBook?market_id=1" # Get recent trades curl "https://mainnet.zklighter.elliot.ai/api/v1/trades?market_id=1" ``` ### Authenticated Endpoints ```bash # Account balance (requires API key in header) curl -H "x-[REDACTED_SECRET]" \ "https://mainnet.zklighter.elliot.ai/api/v1/account?by=index&value=$LIGHTER_ACCOUNT_INDEX" ``` ## Getting Your Account Index See "Setting Up Your Credentials" table above for the quick curl command. ## Getting an API Key See "Setting Up Your Credentials" table above for the step-by-step guide. ## Common Issues **"Restricted jurisdiction":** - Lighter has geographic restrictions - ensure compliance with their terms **SDK signing issues:** - Use create_market_order() instead of create_order() for more reliable execution ## Market IDs | ID | Symbol | |----|--------| | 1 | ETH-USD | | 2 | BTC-USD | | 3 | SOL-USD | ## Links - API: https://mainnet.zklighter.elliot.ai - Dashboard: https://dashboard.zklighter.io - SDK: https://github.com/elliottech/lighter-python --- ## Additional Examples See `USAGE.md` in this skill folder for: - Detailed curl commands for all endpoints - Order book and trade queries - Account and position checks - Signed transaction flow (nonce → sign → broadcast) **Disclaimer:** Review all external code before running. Use dedicated wallets for trading.

path: USAGE.md
content: # Lighter v2 Usage (Direct Mainnet API)

This usage guide intentionally targets **Lighter native mainnet endpoints** directly.

- Base URL: `https://mainnet.zklighter.elliot.ai`
- API prefix: `/api/v1`

---

## Environment

```bash
export LIGHTER_API_KEY="..."
export LIGHTER_ACCOUNT_INDEX="..."
export LIGHTER_L1_ADDRESS="0x..."   # optional
```

---

## Read-only (safe by default)

### 1) List all order books
```bash
curl "https://mainnet.zklighter.elliot.ai/api/v1/orderBooks"
```

### 2) Get one market order book (ETH-USD usually `market_id=1`)
```bash
curl "https://mainnet.zklighter.elliot.ai/api/v1/orderBook?market_id=1"
```

### 3) Get recent trades for a market
```bash
curl "https://mainnet.zklighter.elliot.ai/api/v1/trades?market_id=1"
```

### 4) Resolve account index by L1 address
```bash
curl "https://mainnet.zklighter.elliot.ai/api/v1/accountsByL1Address?l1_address=$LIGHTER_L1_ADDRESS"
```

---

## Authenticated account queries

### 5) Account by index
```bash
curl -H "x-[REDACTED_SECRET]" \
  "https://mainnet.zklighter.elliot.ai/api/v1/account?by=index&value=$LIGHTER_ACCOUNT_INDEX"
```

### 6) API key metadata (if enabled on account)
```bash
curl -H "x-[REDACTED_SECRET]" \
  "https://mainnet.zklighter.elliot.ai/api/v1/apikeys?account_index=$LIGHTER_ACCOUNT_INDEX&api_key_index=255"
```

---

## Operator flows via curl only (no `/lighter` calls)

### Status / markets / risk context
```bash
# order books snapshot (market depth)
curl "https://mainnet.zklighter.elliot.ai/api/v1/orderBooks"

# account snapshot by index
curl -H "x-[REDACTED_SECRET]" \
  "https://mainnet.zklighter.elliot.ai/api/v1/account?by=index&value=$LIGHTER_ACCOUNT_INDEX"

# open orders (if endpoint available in your account tier)
curl -H "x-[REDACTED_SECRET]" \
  "https://mainnet.zklighter.elliot.ai/api/v1/orders?account_index=$LIGHTER_ACCOUNT_INDEX"
```

### Pre-trade simulation pattern (local calc + market data)
```bash
# pull order book for target market
curl "https://mainnet.zklighter.elliot.ai/api/v1/orderBook?market_id=1"
# then compute expected fill/slippage client-side before signing/sending tx
```

### Order execution pattern (signed tx via SDK)
```bash
# 1) get next nonce
curl -H "x-[REDACTED_SECRET]" \
  "https://mainnet.zklighter.elliot.ai/api/v1/nextNonce?account_index=$LIGHTER_ACCOUNT_INDEX&api_key_index=3"

# 2) sign transaction locally (lighter-python SignerClient)
# 3) broadcast signed tx
curl -X POST "https://mainnet.zklighter.elliot.ai/api/v1/sendTx" \
  -H "x-[REDACTED_SECRET]" \
  -H "Content-Type: application/json" \
  -d '{"tx":"<signed_tx_payload>"}'
```

---

## Security notes

- Read-only calls first; simulate before any live order.
- Keep `confirm=true` mandatory for execution paths.
- Store keys only in `~/.openclaw/secrets.env`.
- Never echo or log private keys.

---

## Reference docs

- Lighter API docs: https://apidocs.lighter.xyz/docs/get-started-for-programmers-1
- Mainnet endpoint: https://mainnet.zklighter.elliot.ai
- SDK repo: https://github.com/elliottech/lighter-python


path: requirements.txt
content: requests>=2.31.0
# Optional: lighter-sdk>=1.0.3 eth-account>=0.12.0
# Only install if you need order placement capabilities


path: scripts/account.py
content: """
Lighter Protocol - Account balance (requires API key)
"""
import os
import requests

API_BASE = "https://mainnet.zklighter.elliot.ai/api/v1"

[REDACTED_SECRET]"LIGHTER_API_KEY", "")
ACCOUNT_INDEX = os.environ.get("LIGHTER_ACCOUNT_INDEX", "0")

if not [REDACTED_SECRET]"Error: Set LIGHTER_API_KEY environment variable")
    print("  export LIGHTER_API_KEY=your-api-key")
    exit(1)

headers = {"x-api-key": API_KEY}

response = requests.get(
    f"{API_BASE}/account?by=index&value={ACCOUNT_INDEX}",
    headers=headers
)

if response.status_code == 200:
    data = response.json()
    account = data.get("accounts", [{}])[0]
    print(f"Account {account.get('account_index')}")
    print(f"Balance: ${account.get('available_balance')}")
    print(f"Collateral: ${account.get('collateral')}")
else:
    print(f"Error: {response.status_code}")


path: scripts/get_account_index.py
content: """
Lighter Protocol - Get Account Index from ETH Address
"""
import os
import sys
import requests

API_BASE = "https://mainnet.zklighter.elliot.ai/api/v1"

# Get address from env - NOT from CLI to avoid accidental key exposure
ETH_ADDRESS = os.environ.get("LIGHTER_L1_ADDRESS", "")

if not ETH_ADDRESS:
    print("Error: Set LIGHTER_L1_ADDRESS environment variable")
    print("  export LIGHTER_L1_ADDRESS=your_eth_address")
    sys.exit(1)

# Query account
response = requests.get(
    f"{API_BASE}/accountsByL1Address",
    params={"l1_address": ETH_ADDRESS}
)

if response.status_code == 200:
    data = response.json()
    accounts = data.get("sub_accounts", [])
    if accounts:
        print(f"Account Index: {accounts[0]['index']}")
    else:
        print("No Lighter account found")
else:
    print(f"Error: {response.status_code}")


path: scripts/markets.py
content: """
Lighter Protocol - List markets (read-only)
"""
import requests

API_BASE = "https://mainnet.zklighter.elliot.ai/api/v1"

def list_markets():
    response = requests.get(f"{API_BASE}/orderBooks")
    data = response.json()
    
    markets = data.get("order_books", [])
    print(f"Total markets: {len(markets)}\n")
    
    for m in markets[:20]:
        print(f"{m.get('symbol')}: ID={m.get('market_id')}, Type={m.get('market_type')}")
    
    if len(markets) > 20:
        print(f"\n... and {len(markets) - 20} more")

if __name__ == "__main__":
    list_markets()


path: scripts/order.py
content: """
Lighter Protocol - Place orders
"""
import os
import sys
import asyncio
import lighter
from lighter import Configuration

# Configuration
API_URL = "https://mainnet.zklighter.elliot.ai"

# Load environment
LIGHTER_API_KEY = os.environ.get("LIGHTER_API_KEY", "")
LIGHTER_ACCOUNT_INDEX = int(os.environ.get("LIGHTER_ACCOUNT_INDEX", "0"))

# Decimal places for atomic units
DECIMALS = 8

async def place_order(market_id: int, side: str, amount: float, price: float = None, order_type: str = "LIMIT"):
    if not LIGHTER_API_KEY:
        print("Error: LIGHTER_API_KEY environment variable required")
        print("Set it with: export LIGHTER_API_KEY='your-api-key-from-system-setup'")
        sys.exit(1)
    
    # Initialize signer
    signer = lighter.SignerClient(
        url=API_URL,
        account_index=LIGHTER_ACCOUNT_INDEX,
        api_private_keys={3: LIGHTER_API_KEY}
    )
    
    print(f"Signer initialized for account index {LIGHTER_ACCOUNT_INDEX}")
    
    # Convert to atomic units
    amount_atomic = int(amount * 10**DECIMALS)
    price_atomic = int(price * 10**DECIMALS) if price else 0
    
    # Map order types and sides
    order_type_map = {"LIMIT": 0, "MARKET": 1, "IOC": 2, "FOK": 3}
    time_in_force_map = {"GTC": 0, "IOC": 1, "FOK": 2, "GTX": 3}
    
    is_ask = side.lower() == "sell"
    order_type_int = order_type_map.get(order_type.upper(), 0)
    time_in_force_int = time_in_force_map.get("GTC", 0)
    
    # Place order
    try:
        result = await signer.create_order(
            market_index=market_id,
            client_order_index=1,
            base_amount=amount_atomic,  # Atomic units!
            price=price_atomic,         # Atomic units!
            is_ask=is_ask,
            order_type=order_type_int,  # Integer!
            time_in_force=time_in_force_int  # Integer!
        )
        
        if result[2]:  # Error message
            print(f"Order error: {result[2]}")
        else:
            print(f"✅ Order placed! TX: {result[1].tx_hash if result[1] else 'pending'}")
        
        return result
    except Exception as e:
        print(f"Error: {e}")
        import traceback
        traceback.print_exc()
        sys.exit(1)

def main():
    import argparse
    parser = argparse.ArgumentParser(description="Place order on Lighter")
    parser.add_argument("--market-id", type=int, required=True, help="Market ID (e.g., 1 for ETH-USD)")
    parser.add_argument("--side", required=True, choices=["buy", "sell"], help="Order side")
    parser.add_argument("--amount", type=float, required=True, help="Order amount")
    parser.add_argument("--price", type=float, help="Order price (for limit orders)")
    parser.add_argument("--type", dest="order_type", default="LIMIT", help="Order type")
    
    args = parser.parse_args()
    asyncio.run(place_order(args.market_id, args.side, args.amount, args.price, args.order_type))

if __name__ == "__main__":
    main()


path: scripts/positions.py
content: """
Lighter Protocol - Get positions
"""
import os
import sys
import requests

# Configuration
API_BASE = "https://mainnet.zklighter.elliot.ai/api/v1"

# Load environment
LIGHTER_API_KEY = os.environ.get("LIGHTER_API_KEY", "")
LIGHTER_ACCOUNT_INDEX = int(os.environ.get("LIGHTER_ACCOUNT_INDEX", "0"))

def get_positions():
    if not LIGHTER_API_KEY:
        print("Error: LIGHTER_API_KEY environment variable required")
        print("Set it with: export LIGHTER_API_KEY='your-api-key'")
        sys.exit(1)
    
    headers = {"x-api-key": LIGHTER_API_KEY}
    
    try:
        response = requests.get(
            f"{API_BASE}/account?by=index&value={LIGHTER_ACCOUNT_INDEX}",
            headers=headers
        )
        
        if response.status_code == 200:
            data = response.json()
            account = data.get("accounts", [{}])[0]
            positions = account.get("positions", [])
            
            if not positions:
                print("No open positions")
                return
            
            print(f"Open positions: {len(positions)}\n")
            
            for pos in positions:
                print(f"Market ID: {pos.get('market_index')}")
                print(f"  Side: {'Ask' if pos.get('is_ask') else 'Bid'}")
                print(f"  Size: {pos.get('size')}")
                print(f"  Entry Price: {pos.get('entry_price')}")
                print(f"  Notional Value: {pos.get('notional_value')}")
                print()
            
            return positions
        else:
            print(f"Error: {response.status_code} - {response.text}")
            sys.exit(1)
            
    except Exception as e:
        print(f"Error: {e}")
        sys.exit(1)

if __name__ == "__main__":
    get_positions()


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