lume
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:
-
Check login state. Silently read
${CLAUDE_SKILL_DIR}/.lume-credentials. -
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 (includingbestBid/bestAskpricing). Note:get_all_events()returns lightweightOutcomeobjects with onlyid,label, andtoken_id— no pricing data. You must useget_event()to getbestBidandbestAskon outcomes. - From the detailed results, find the first event that has an outcome with
a
bestBid. Usefrom_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 theavailable_decimalfield for display. Note:get_balance()returns the platform ledger balance which may not reflect on-chain funds — always preferget_onchain_balance()for actual wallet balance.
- For public reads (not logged in), create a
-
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 loginto start trading, or ask me about any market.If NOT logged in + no trending data:
{N} active events on Lume right now. Run
/lume loginto 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?
-
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:
-
Check if already logged in. Read the credentials file at
${CLAUDE_SKILL_DIR}/.lume-credentials. If it exists and contains aPRIVATE_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). -
Ask the user using
AskUserQuestion:Do you have an existing private key, or would you like to generate a new wallet?
- Import existing — I have a private key
- Generate new — Create a fresh wallet for me
-
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-credentialsfile. -
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.
-
Save credentials. Write the file
${CLAUDE_SKILL_DIR}/.lume-credentials:PRIVATE_KEY=0x...This file is gitignored via the
.lume-credentialspattern. -
Verify. Use the SDK to create an
AgentClient, callregister(), and display the EOA address and proxy wallet. Confirm login was successful. -
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:
- Check if
${CLAUDE_SKILL_DIR}/.lume-credentialsexists. - If not, tell the user: "You need to log in first. Run
/lume loginto set up your wallet." and stop. - If it exists, load the
PRIVATE_KEYvalue and pass it to the SDK client:PRIVATE_KEY=$(grep '^PRIVATE_KEY=' "${CLAUDE_SKILL_DIR}/.lume-credentials" | cut -d'=' -f2) - 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
- Setup -- Install SDK, create
AgentClient - Register --
agent.register()to authenticate and get proxy wallet - Onboard --
register_wallet_user()to deploy Safe + approvals (gasless) - Fund -- User sends USDC on Monad to
agent.proxy_wallet - Relay fee --
pay_relay_fee()to enable gasless transactions (1 USDC) - 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
-
Register --
agent.register()creates the user account and sets up the proxy wallet address. -
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). -
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 callingagent.register().Minimum amount: At least 1 USDC for the relay fee + whatever amount the agent needs for trading.
-
Pay relay fee --
agent.pay_relay_fee(target, data)transfers 1 USDC from the Safe toFEE_RECIPIENT_ADDRESS. This is a one-time payment that enables all future gasless relay transactions. This call is gasless but requires building SafeexecTransactioncalldata (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.priceaccepts 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_decimalandOrderBookLevel.price_decimalproperties 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)