lume

SKILL.md

Lume Trading Skill

Command Routing

This skill supports subcommands via args. Route based on the first word:

Args Action
login Run the Login Flow below
logout Delete the credentials file and confirm
(anything else or empty) Run the Proactive Greeting below, then handle the request

Proactive Greeting (default /lume with no args or open-ended args)

When the user runs /lume with no subcommand, do NOT show a static menu of options. Instead, deliver a short, contextual, actionable greeting.

CRITICAL: Do NOT narrate your internal steps. Never say things like "let me check your login state", "following the proactive greeting flow", "fetching market data", etc. Just silently execute the steps and present the final greeting directly. The user should only see the polished greeting, not how you built it.

Follow these steps:

  1. Check login state. Silently read ${CLAUDE_SKILL_DIR}/.lume-credentials.

  2. Fetch trending markets and (if logged in) balance using the SDK.

    • For public reads (not logged in), create a LumeClient() with no arguments — no private key is needed for public queries.
    • If the user is logged in, create a LumeClient(private_key=pk) using their key from .lume-credentials.
    • Call client.get_all_events(first=5, status="ACTIVE") — get active events.
    • For each event, call client.get_event(event_id=e.id) to get full market and outcome details (including bestBid/bestAsk pricing). Note: get_all_events() returns lightweight Outcome objects with only id, label, and token_id — no pricing data. You must use get_event() to get bestBid and bestAsk on outcomes.
    • From the detailed results, find the first event that has an outcome with a bestBid. Use from_atomic() to convert the price for display.
    • If logged in, also call client.get_onchain_balance() to get the available on-chain USDC balance in the proxy wallet. Use the available_decimal field for display. Note: get_balance() returns the platform ledger balance which may not reflect on-chain funds — always prefer get_onchain_balance() for actual wallet balance.
  3. Compose the greeting from the data. Output ONLY the greeting — nothing else.

    If NOT logged in + have trending market:

    "Will ETH hit $5K by July?" is trending at $0.43 Yes. Run /lume login to start trading, or ask me about any market.

    If NOT logged in + no trending data:

    {N} active events on Lume right now. Run /lume login to start trading, or ask me about any market.

    If logged in + balance + trending market:

    Lume connected — $42.50 available. "Will ETH hit $5K by July?" is trending at $0.43 Yes. What would you like to trade?

    If logged in + not fully onboarded (check get_onboarding_status()):

    If Safe is not deployed, run the onboarding steps (7-8 from Login Flow) silently, then show the appropriate greeting. If the relay fee is unpaid and the wallet has USDC, pay it automatically.

    If logged in + zero balance:

    Lume connected but your wallet is empty. "Will ETH hit $5K by July?" is at $0.43 Yes. Fund your wallet to trade, or ask me about any market.

    If logged in + balance + no trending data:

    Lume connected — $42.50 available. {N} active events to explore. What catches your eye?

  4. If the user provided additional text beyond /lume (e.g., /lume what's hot), skip the greeting and directly handle their request using the relevant sections below.

Rules for the greeting:

  • Output ONLY the greeting text. No preamble, no "let me check...", no status updates.
  • Keep it to 1-2 sentences max. No bullet lists, no menus, no "here's what I can do".
  • Always include one concrete data point (a market price, number of events, or balance).
  • End with an open prompt, not a list of options.
  • If fetching live data fails, fall back to: "Lume connected. What would you like to explore?"

Login Flow (/lume login)

When the user runs /lume login, follow these steps exactly:

  1. Check if already logged in. Read the credentials file at ${CLAUDE_SKILL_DIR}/.lume-credentials. If it exists and contains a PRIVATE_KEY= line, inform the user they are already logged in and show the EOA address (derive it from the key using the SDK). Ask if they want to re-login (overwrite).

  2. Ask the user using AskUserQuestion:

    Do you have an existing private key, or would you like to generate a new wallet?

    1. Import existing — I have a private key
    2. Generate new — Create a fresh wallet for me
  3. If importing: Ask the user to paste their private key directly in the chat. Simply tell them:

    Paste your private key (0x...) and I'll save it locally.

    Then validate and save. The key is only stored in the local .lume-credentials file.

  4. If generating: Run the SDK to create a new wallet:

    PYTHON=/tmp/lume-env/bin/python
    $PYTHON -c "
    from lume import generate_wallet
    pk, addr = generate_wallet()
    print(f'PRIVATE_KEY={pk}')
    print(f'ADDRESS={addr}')
    "
    

    Show the user their new address and warn them to back up the private key since it's only stored locally.

  5. Save credentials. Write the file ${CLAUDE_SKILL_DIR}/.lume-credentials:

    PRIVATE_KEY=0x...
    

    This file is gitignored via the .lume-credentials pattern.

  6. Verify. Use the SDK to create an AgentClient, call register(), and display the EOA address and proxy wallet. Confirm login was successful.

  7. Complete onboarding (gasless). Immediately after registration, deploy the Safe wallet and set up contract approvals. This is gasless and must happen before the user can trade. Use the helper functions from the SDK examples:

    PYTHON=/tmp/lume-env/bin/python
    PRIVATE_KEY=<the saved key>
    # Download helper if not cached
    if [ ! -f /tmp/safe_wallet_operations.py ]; then
      gh api repos/EternisAI/lume-sdk/contents/examples/safe_wallet_operations.py \
        --jq '.content' | base64 -d > /tmp/safe_wallet_operations.py
    fi
    $PYTHON -c "
    import sys
    sys.path.insert(0, '/tmp')
    from safe_wallet_operations import build_safe_deploy_calldata, build_approvals_calldata
    from web3 import Web3
    from lume import AgentClient, SafeWalletManager
    from lume.constants import get_config_with_env_overrides
    
    pk = '$PRIVATE_KEY'
    config = get_config_with_env_overrides()
    rpc_url = config.rpc_url
    
    agent = AgentClient(private_key=pk)
    agent.register()
    
    w3 = Web3(Web3.HTTPProvider(rpc_url))
    wallet_mgr = SafeWalletManager(private_key=pk, rpc_url=rpc_url)
    
    safe_target, safe_data = build_safe_deploy_calldata(wallet_mgr)
    safe_address = wallet_mgr.compute_safe_address()
    approvals_target, approvals_data = build_approvals_calldata(w3, safe_address, pk, rpc_url)
    
    result = agent.register_wallet_user(
        safe_deploy_target=safe_target,
        safe_deploy_data=safe_data,
        approvals_target=approvals_target,
        approvals_data=approvals_data,
    )
    print(f'SUCCESS={result.success}')
    print(f'PROXY={agent.proxy_wallet}')
    if result.error:
        print(f'ERROR={result.error}')
    "
    

    After successful deployment, tell the user:

    Wallet set up! Send USDC on Monad to your proxy wallet: <proxy_wallet_address> Minimum 1 USDC (relay fee) + whatever you want to trade with. The relay fee will be paid automatically when you first interact after funding.

Logout Flow (/lume logout)

Delete ${CLAUDE_SKILL_DIR}/.lume-credentials and confirm to the user.

Authentication for All Other Commands

Before running any SDK operation that requires a private key:

  1. Check if ${CLAUDE_SKILL_DIR}/.lume-credentials exists.
  2. If not, tell the user: "You need to log in first. Run /lume login to set up your wallet." and stop.
  3. If it exists, load the PRIVATE_KEY value and pass it to the SDK client:
    PRIVATE_KEY=$(grep '^PRIVATE_KEY=' "${CLAUDE_SKILL_DIR}/.lume-credentials" | cut -d'=' -f2)
    
  4. Check onboarding status via agent.get_onboarding_status(). If not fully onboarded, silently complete the remaining steps before proceeding:
    • Safe not deployed: Run the Safe deployment + approvals from Login step 7.

    • Relay fee not paid: Check on-chain balance via agent.get_onchain_balance(). If >= 1 USDC, automatically pay the relay fee:

      # Download helper if not cached
      if [ ! -f /tmp/safe_wallet_operations.py ]; then
        gh api repos/EternisAI/lume-sdk/contents/examples/safe_wallet_operations.py \
          --jq '.content' | base64 -d > /tmp/safe_wallet_operations.py
      fi
      $PYTHON -c "
      import sys
      sys.path.insert(0, '/tmp')
      from safe_wallet_operations import build_relay_fee_calldata
      from web3 import Web3
      from lume import AgentClient
      from lume.constants import get_config_with_env_overrides
      
      pk = '$PRIVATE_KEY'
      config = get_config_with_env_overrides()
      rpc_url = config.rpc_url
      
      agent = AgentClient(private_key=pk)
      agent.register()
      
      w3 = Web3(Web3.HTTPProvider(rpc_url))
      fee_target, fee_data = build_relay_fee_calldata(w3, agent.proxy_wallet, pk, rpc_url)
      result = agent.pay_relay_fee(target=fee_target, data=fee_data)
      print(f'SUCCESS={result.success}')
      if result.error:
          print(f'ERROR={result.error}')
      "
      

      If insufficient funds, tell the user: "Your wallet needs at least 1 USDC for the one-time relay fee. Send USDC to <proxy_wallet> and try again."

Python Requirement

The Lume SDK requires Python >= 3.12. The setup hook (scripts/check-and-install.sh) automatically creates a venv at /tmp/lume-env and installs the SDK on first use.

When running Python code, always use the venv interpreter:

PYTHON=/tmp/lume-env/bin/python
$PYTHON -c "your code here"

Never use bare python3 — it may point to an older system Python without the SDK.

Overview

Lume is a prediction market platform on the Monad blockchain. Users trade binary and categorical outcome shares (e.g., YES/NO) whose prices reflect the crowd-estimated probability of real-world events. Orders are matched off-chain for speed; settlement and position custody happen on-chain via Gnosis Safe wallets and the Conditional Token Framework (CTF).

This skill gives an AI agent everything it needs to:

  • Set up and onboard a new trading agent (gasless)
  • Fund the agent's wallet with USDC
  • Browse live events and markets
  • Read order books and price history
  • Place, cancel, and monitor limit orders
  • Track positions and collateral balance
  • Post and vote on discussion comments
  • Create user bets (new events)
  • Subscribe to real-time order and position updates

The Python SDK package is called lume.

Agent workflow at a glance

  1. Setup -- Install SDK, create AgentClient
  2. Register -- agent.register() to authenticate and get proxy wallet
  3. Onboard -- register_wallet_user() to deploy Safe + approvals (gasless)
  4. Fund -- User sends USDC on Monad to agent.proxy_wallet
  5. Relay fee -- pay_relay_fee() to enable gasless transactions (1 USDC)
  6. Trade -- create_and_place_order(), cancel_order(), etc.

Setup

Install

pip install git+https://github.com/EternisAI/lume-sdk.git

Environment

The SDK defaults to production (Monad Mainnet, chain_id=143). No environment variable is needed for normal use.

You can override individual settings with environment variables: LUME_ENV, LUME_API_URL, LUME_CHAIN_ID, LUME_CTF_EXCHANGE_ADDRESS, LUME_NEGRISK_EXCHANGE_ADDRESS, LUME_FEE_RATE_BPS.

Initialize the agent

For autonomous agents, use AgentClient (extends LumeClient with registration and authentication):

import os
from lume import AgentClient

agent = AgentClient(private_key=os.environ["PRIVATE_KEY"])
info = agent.register()

print(f"EOA Address:    {agent.eoa_address}")
print(f"Proxy Wallet:   {agent.proxy_wallet}")
print(f"Registered:     {info.is_registered}")

Or use the environment helper:

from lume import create_agent_from_env

# Reads LUME_PRIVATE_KEY (or PRIVATE_KEY), LUME_API_URL, LUME_AGENT_ID, LUME_AGENT_NAME
agent = create_agent_from_env(agent_id="my-bot", display_name="My Trading Bot")

You can also use LumeClient directly if you don't need agent registration:

from lume import LumeClient

# With private key (for trading, balance checks, etc.)
client = LumeClient(private_key=os.environ["PRIVATE_KEY"])

# Without private key (for public queries only — events, orderbooks, etc.)
client = LumeClient()

Generate a new wallet (if needed)

from lume import generate_wallet

private_key, address = generate_wallet()
print(f"Address: {address}")
# Save private_key securely

Key Concepts

Price format: 1e6 atomic units

All prices and amounts returned by the API are in atomic units with 6 decimal places. 500000 means $0.50. Use the helper functions:

from lume import to_atomic, from_atomic

to_atomic(0.50)        # -> 500000
from_atomic(500000)    # -> Decimal('0.500000')

When creating OrderArgs, use human-readable decimals for price and size. The SDK converts internally.

Two wallet types

Wallet Purpose
EOA (Externally Owned Account) Your private key signs orders and transactions
Proxy / Safe wallet On-chain Gnosis Safe derived from your EOA; holds USDC collateral and conditional tokens

The proxy wallet address is set up automatically when you call agent.register(). This is the address where the agent's funds must be sent.

Off-chain vs on-chain

Operation Layer
Place/cancel orders, match trades Off-chain (fast, no gas)
Safe deployment, approvals, relay fee Gasless (relayed by backend)
Fund Safe wallet with USDC On-chain (user sends USDC to Safe address)

Onboarding and Funding

New agents must complete a one-time onboarding before they can trade. The onboarding is mostly gasless -- the backend relay server submits transactions on the agent's behalf.

Onboarding steps

  1. Register -- agent.register() creates the user account and sets up the proxy wallet address.

  2. Deploy Safe + Approvals -- agent.register_wallet_user(...) deploys the Gnosis Safe wallet and batches all 7 required contract approvals (4 USDC ERC-20 approves + 3 CTF ERC-1155 setApprovalForAll). This is gasless but requires building calldata (see code below).

  3. Fund the Safe -- The user (or the agent operator) sends USDC to the agent's Safe wallet address. This is the only step that requires an external transaction.

    What to send: USDC on Monad Mainnet (0x754704Bc059F8C67012fEd69BC8A327a5aafb603)

    Where to send: The agent's proxy wallet address (agent.proxy_wallet). This address is available after calling agent.register().

    Minimum amount: At least 1 USDC for the relay fee + whatever amount the agent needs for trading.

  4. Pay relay fee -- agent.pay_relay_fee(target, data) transfers 1 USDC from the Safe to FEE_RECIPIENT_ADDRESS. This is a one-time payment that enables all future gasless relay transactions. This call is gasless but requires building Safe execTransaction calldata (see code below).

Check onboarding status

status = agent.get_onboarding_status()
print(f"Safe deployed:       {status.safe_deployed}")
print(f"Approvals completed: {status.approvals_completed}")
print(f"Relay fee paid:      {status.relay_fee_paid}")
print(f"Fully onboarded:     {status.is_fully_onboarded}")
if status.next_step:
    print(f"Next step:           {status.next_step.value}")

Deploy Safe + Approvals

register_wallet_user() requires 4 arguments: the target address and ABI-encoded calldata for both the Safe deployment and the approval batch. Use SafeWalletManager and the helper functions in examples/safe_wallet_operations.py to build them:

from web3 import Web3
from lume import AgentClient, SafeWalletManager, DEFAULT_RPC_URL

# These helpers are in examples/safe_wallet_operations.py
from safe_wallet_operations import build_safe_deploy_calldata, build_approvals_calldata

agent = AgentClient(private_key="0x...")
info = agent.register()

w3 = Web3(Web3.HTTPProvider(DEFAULT_RPC_URL))
wallet_mgr = SafeWalletManager(private_key="0x...", rpc_url=DEFAULT_RPC_URL)
safe_address = wallet_mgr.compute_safe_address()

# Build calldata for Safe deployment
safe_target, safe_data = build_safe_deploy_calldata(wallet_mgr)

# Build calldata for 7 contract approvals (wrapped in Safe execTransaction)
approvals_target, approvals_data = build_approvals_calldata(
    w3, safe_address, "0x...", DEFAULT_RPC_URL
)

# Submit via relay (gasless)
result = agent.register_wallet_user(
    safe_deploy_target=safe_target,
    safe_deploy_data=safe_data,
    approvals_target=approvals_target,
    approvals_data=approvals_data,
)
print(f"Success: {result.success}, TX: {result.tx_hash}")

Pay relay fee

Similarly, pay_relay_fee() requires Safe execTransaction calldata:

from safe_wallet_operations import build_relay_fee_calldata

fee_target, fee_data = build_relay_fee_calldata(
    w3, agent.proxy_wallet, "0x...", DEFAULT_RPC_URL
)
result = agent.pay_relay_fee(target=fee_target, data=fee_data)
print(f"Success: {result.success}, TX: {result.tx_hash}")

Prompt the user to fund the wallet

When building an agent that needs funding, prompt the user with:

from lume import USDC_ADDRESS

info = agent.register()
print(f"Please send USDC on Monad to: {agent.proxy_wallet}")
print(f"Minimum: 1 USDC (relay fee) + trading amount")
print(f"USDC contract: {USDC_ADDRESS}")

The user should send USDC from any Monad wallet (e.g., MetaMask, exchange withdrawal) to the proxy wallet address shown above.

Full onboarding reference

See examples/safe_wallet_operations.py for the complete onboarding implementation including all calldata building for Safe deployment, batch approvals, and relay fee payment.

Common Tasks

Browse markets

# List active events
events = agent.get_all_events(first=10, status="ACTIVE")
for e in events:
    print(f"{e.title} [{e.category}] - {len(e.markets)} market(s)")

# Get a single event with full market + outcome details
event = agent.get_event(event_id="<EVENT_UUID>")
for m in event["markets"]:
    print(f"  {m['question']}")
    for o in m["outcomes"]:
        print(f"    {o['label']}: best bid {o['bestBid']}, best ask {o['bestAsk']}")

Check prices and order book

from lume import from_atomic

# Single outcome order book
book = agent.get_orderbook(market_id="<MARKET_UUID>", outcome="YES")
if book.best_bid:
    print(f"Best bid: ${from_atomic(int(book.best_bid.price))}")
if book.best_ask:
    print(f"Best ask: ${from_atomic(int(book.best_ask.price))}")
if book.spread is not None:
    print(f"Spread:   ${book.spread}")

# All outcomes at once
books = agent.get_orderbooks(market_id="<MARKET_UUID>")
for b in books:
    print(f"{b.outcome.label}: mid={b.mid_price}")

Place an order

from lume import OrderArgs, OrderSide

order_args = OrderArgs(
    market_id="<MARKET_UUID>",
    side=OrderSide.BUY,
    outcome="YES",       # outcome label, case-insensitive
    price=0.55,          # human-readable decimal
    size=100.0,          # number of shares
)

response = agent.create_and_place_order(order_args)
print(f"Order placed: {response['id']}")

To sell shares you own:

sell_args = OrderArgs(
    market_id="<MARKET_UUID>",
    side=OrderSide.SELL,
    outcome="YES",
    price=0.60,
    size=50.0,
)
agent.create_and_place_order(sell_args)

Check positions and balance

# On-chain wallet balance (actual USDC in proxy wallet)
balance = agent.get_onchain_balance()
print(f"Available: {balance['available_decimal']} USDC")

# Platform ledger balance (may lag behind on-chain)
ledger = agent.get_balance()
print(f"Ledger:  {ledger['available']}")
print(f"Locked:  {ledger['locked']}")

# Positions in a market
positions = agent.get_my_positions(market_id="<MARKET_UUID>")
for p in positions["positions"]:
    print(f"{p['outcome']['label']}: {p['shares']} shares, PnL {p['pnlUnrealized']}")

Cancel orders

# Cancel a single order
agent.cancel_order(order_id="<ORDER_UUID>")

# Cancel all open orders in a market
result = agent.cancel_my_orders_by_market(market_id="<MARKET_UUID>")
print(f"Cancelled {result['cancelledCount']} order(s)")

# Cancel all open orders in an event (all markets)
agent.cancel_my_orders_by_event(event_id="<EVENT_UUID>")

View trade history

trades = agent.get_trades(market_id="<MARKET_UUID>", first=20)
for t in trades["trades"]:
    print(f"Price: {t['price']}, Shares: {t['shares']}, At: {t['executedAt']}")

Post a comment

comment = agent.create_comment(
    event_id="<EVENT_UUID>",
    content="I think YES is underpriced given recent polling data.",
    comment_type="REASONING",
)
print(f"Comment ID: {comment['id']}")

Create a user bet

result = agent.create_user_bet(
    title="Will it rain in SF on July 4th?",
    start_date="2026-07-01T00:00:00Z",
    end_date="2026-07-05T00:00:00Z",
    image_url="https://example.com/rain.jpg",
    resolution_criteria="Resolved YES if measurable rainfall recorded at SFO.",
    category="Weather",
    tags=["san-francisco", "weather"],
)
print(f"Event: {result['event']['id']}")
print(f"Market: {result['market']['id']}")

Gasless Relay Transactions

Once fully onboarded (Safe deployed, approvals set, relay fee paid), agents can execute on-chain operations gaslessly via relay_safe_transaction():

# Split USDC into YES/NO tokens (gasless)
result = agent.relay_safe_transaction(target=CTF_ADDRESS, data=split_calldata)

# Merge YES/NO tokens back to USDC (gasless)
result = agent.relay_safe_transaction(target=CTF_ADDRESS, data=merge_calldata)

See examples/safe_wallet_operations.py for how to build the calldata for split, merge, and redeem operations.

Important Notes

Error handling

All SDK methods raise typed exceptions. Always wrap calls in try/except:

from lume import GraphQLError, WebSocketError

try:
    agent.create_and_place_order(order_args)
except GraphQLError as e:
    print(f"API error: {e}")
except ValueError as e:
    print(f"Invalid input: {e}")  # e.g. outcome label not found

The full exception hierarchy:

LumeError
  GraphQLError          - API request failures, auth errors
  WebSocketError        - Subscription connection issues
  SafeError             - Base for on-chain errors
    SafeWalletError     - Safe deployment failures
    SafeExecutionError  - Safe transaction failures
    SafeNotDeployedError
    InsufficientBalanceError
    InsufficientAllowanceError
    MarketNotResolvedError
  AgentRegistrationError - Agent registration/auth failures

Price precision

  • OrderArgs.price accepts a human-readable float (e.g., 0.50).
  • API responses return atomic integers as strings (e.g., "500000").
  • Use from_atomic(int(value)) to convert API values for display.
  • Use Order.price_decimal and OrderBookLevel.price_decimal properties for convenience.

Authentication

Authentication is automatic. The client signs a challenge with your private key on every HTTP request and WebSocket connection. No API keys or tokens needed.

Sell orders require share ownership

You must own conditional tokens (shares) to place a SELL order. To bet against an outcome, BUY the opposite outcome instead.

References

  • API_REFERENCE.md - Complete method signatures for AgentClient, LumeClient, wallet onboarding, trading, positions, subscriptions, and utilities
  • EXAMPLES.md - Copy-paste code snippets for agent setup, onboarding, market browsing, ordering, portfolio management, subscriptions, and more
  • TROUBLESHOOTING.md - Common errors and fixes (auth failures, onboarding issues, proxy wallet problems, price format confusion)
Installs
9
Source
lume.top
First Seen
Apr 1, 2026