First-Time Setup (Onboarding Flow)
IMPORTANT: On EVERY new conversation, the agent MUST run these checks before doing anything else.
Automatic Startup Checks
Step 0: Check for skill updates (ALWAYS run first)
└─> bash scripts/update.sh
└─> If update found: inform user "Skill updated to vX.Y.Z — reloading with latest features"
└─> If already up to date: proceed silently (don't mention it)
Step 1: Check wallet status
└─> node scripts/wallet-discover.js
Step 2: Interpret the result:
- configured: false → Enter ONBOARDING MODE
- configured: true → Greet user with their address, proceed normally
Why always check for updates? The skill evolves frequently with new option types, SDK features, and bug fixes. Running update.sh ensures the agent always has the latest trading capabilities. Updates NEVER modify wallet secrets (.env, WDK_SEED).
Onboarding Mode (No Wallet Detected)
When no wallet is configured, guide the user through setup in this order:
No wallet detected
│
▼
┌─────────────────────────────────────────┐
│ "Welcome to Thetanuts Options Trading! │
│ Let's get you set up in 3 steps." │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Step 1: Create or import a wallet │
│ • New user → wallet-create.js │
│ • Have a seed? → wallet-import.js │
│ │
│ WARN: Use a DEDICATED wallet. │
│ Never reuse your primary wallet seed. │
│ Custody stays LOCAL on your machine. │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Step 2: Verify & fund the wallet │
│ • Run wallet-discover.js → show address │
│ • Run wallet-balance.js --chain │
│ base-mainnet │
│ • If zero balance: explain funding │
│ - Need ETH on Base for gas fees │
│ - Need USDC for PUT trades │
│ - Need WETH for CALL trades │
│ - Bridge via bridge.base.org │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Step 3: Market overview & first trade │
│ • Fetch prices: get-prices.ts │
│ • WebSearch for market news │
│ • Ask risk preference (LOW/MED/HIGH) │
│ • Show first strategy recommendation │
└─────────────────────────────────────────┘
Welcome Message (After Onboarding or Returning User)
Once wallet is configured, the agent MUST automatically:
- Show wallet address and balances (run
wallet-discover.js+wallet-balance.js) - Fetch current market prices (run
get-prices.ts) - Show a brief market snapshot with available option types
Present this without the user asking:
Welcome to Thetanuts Options Trading!
Your wallet: 0x...
Chain: Base Mainnet
Balances: X ETH (gas) | Y USDC | Z WETH
Live Market:
• ETH: $X,XXX (24h: +X.X%)
• BTC: $XX,XXX (24h: +X.X%)
Available options: PUT, CALL, spreads, butterflies, condors
What would you like to do?
• "Show me ETH puts" → View available strikes and prices
• "Recommend a strategy" → I'll check market news and suggest based on your risk preference
• "Check my positions" → View open trades and P&L on expired ones
New to options? Just say "teach me about options"
Security Reminders (shown during onboarding)
- DEDICATED WALLET: Use a dedicated wallet seed for this integration. Never reuse your primary wallet.
- LOCAL CUSTODY: Your seed phrase stays on your machine. The agent never sends it anywhere.
- TRANSACTIONS: Irreversible once broadcast. Always verify before sending.
- APPROVALS: Token approvals allow contracts to spend your tokens. Only approve trusted contracts.
- GAS: Ensure wallet has ETH on Base for gas fees.
- RFQ KEYS: Back up
.thetanuts-keys/directory — lost keys cannot decrypt past offers.
Thetanuts Options Trading
What is TNUT Agent?
TNUT Agent is your AI-powered assistant for trading crypto options on Thetanuts Finance.
What it does:
- Manages local wallets safely (custody stays on your machine)
- Accesses market intelligence (prices, orderbook, MM quotes)
- Inspects options and positions
- Prepares and executes trades through the Thetanuts protocol
Local-first design:
- Wallet custody stays LOCAL - your seed phrase never leaves your machine
- Signing stays LOCAL - transactions are signed on your device
- Backend provides intelligence, data, and routing support only
Capabilities:
| Category | What You Can Do |
|---|---|
| Wallet | Create, import, check balances |
| Market Data | Live prices, MM quotes, orderbook orders |
| Trading | Fill orderbook orders, submit RFQs, approve tokens |
| Positions | View open positions, calculate payoffs |
Options 101
What are Options?
Options are contracts that give you the right (but not obligation) to buy or sell an asset at a specific price by a specific date.
PUT Options
A PUT gives the holder the right to SELL at the strike price.
| Action | You... | Profit When | Risk |
|---|---|---|---|
| Buy PUT | Pay premium upfront | Price drops below strike | Lose premium if price stays up |
| Sell PUT | Receive premium | Price stays above strike | Lose if price drops below strike |
Example:
- ETH is at $2000
- You BUY a $1900 PUT for $10 premium
- If ETH drops to $1800 → Your PUT is worth $100 (profit: $90)
- If ETH stays at $2000 → Your PUT expires worthless (loss: $10 premium)
CALL Options
A CALL gives the holder the right to BUY at the strike price.
| Action | You... | Profit When | Risk |
|---|---|---|---|
| Buy CALL | Pay premium upfront | Price rises above strike | Lose premium if price stays down |
| Sell CALL | Receive premium | Price stays below strike | Lose if price rises above strike |
Example:
- ETH is at $2000
- You BUY a $2100 CALL for $15 premium
- If ETH rises to $2300 → Your CALL is worth $200 (profit: $185)
- If ETH stays at $2000 → Your CALL expires worthless (loss: $15 premium)
Key Terms
| Term | Definition |
|---|---|
| Strike | The price at which the option can be exercised |
| Expiry | When the option expires (settlement date, 8:00 UTC) |
| Premium | The price paid for the option |
| Settlement | Cash payment based on price difference at expiry |
| Collateral | Funds locked to back the option (for sellers) |
Collateral Requirements by Option Type
IMPORTANT: Different option types require different collateral tokens:
| Option Type | Implementation | Collateral Required |
|---|---|---|
| PUT | PUT | USDC (quote asset) |
| CALL | INVERSE_CALL | WETH (base asset) |
- PUT options: Collateral in USDC. Formula:
(collateral × 1e8) / strike - CALL options: Collateral in WETH. Formula:
collateral / 1e12(1 WETH = 1 contract)
The build-rfq.ts script automatically selects the correct collateral based on option type.
Multi-Strike Option Structures
Beyond vanilla PUT and CALL options, Thetanuts supports advanced multi-strike structures for sophisticated trading strategies.
All Option Types
| Type | Strikes | Collateral | Description |
|---|---|---|---|
| PUT | 1 | USDC | Standard cash-settled put |
| INVERSE_CALL | 1 | WETH | Cash-settled call (base collateral) |
| CALL_SPREAD | 2 | USDC | Buy lower strike call, sell higher strike call |
| PUT_SPREAD | 2 | USDC | Buy higher strike put, sell lower strike put |
| CALL_FLY | 3 | USDC | Call butterfly - profit near middle strike |
| PUT_FLY | 3 | USDC | Put butterfly - profit near middle strike |
| CALL_CONDOR | 4 | USDC | Call condor - wider profit range |
| PUT_CONDOR | 4 | USDC | Put condor - wider profit range |
| IRON_CONDOR | 4 | USDC | Put spread + call spread (neutral) |
| PHYSICAL_CALL | 1 | WETH | Physically settled call |
| PHYSICAL_PUT | 1 | USDC | Physically settled put |
Strike Ordering Rules
CRITICAL: Strikes must be ordered correctly or the RFQ will fail.
| Structure | Type | Strike Order | Example |
|---|---|---|---|
| Vanilla | PUT/CALL | N/A (1 strike) | --strike 1900 |
| Spread | PUT | Descending (high→low) | --strikes 1900,1800 |
| Spread | CALL | Ascending (low→high) | --strikes 2000,2100 |
| Butterfly | PUT | Descending | --strikes 1900,1850,1800 |
| Butterfly | CALL | Ascending | --strikes 2000,2050,2100 |
| Condor | ALL | Always ascending | --strikes 1800,1900,2100,2200 |
Collateral Formulas by Structure (Human-Readable)
These are simplified formulas for quick estimation. For exact on-chain values, use previewFillOrder() or client.utils.calculateCollateral().
| Structure | Collateral | Simplified Formula | Example |
|---|---|---|---|
| Vanilla PUT | USDC | collateral / strike | $1900 USDC at $1900 strike = 1 contract |
| INVERSE_CALL | WETH | collateral (1:1) | 1 WETH = 1 contract |
| CALL_SPREAD | USDC | collateral / (upper - lower) | $100 USDC / $100 width = 1 contract |
| PUT_SPREAD | USDC | collateral / (upper - lower) | $100 USDC / $100 width = 1 contract |
| Butterfly | USDC | collateral / (middle - lower) | $50 USDC / $50 wing = 1 contract |
| Condor | USDC | collateral / (strike2 - strike1) | $100 USDC / $100 inner width = 1 contract |
| IRON_CONDOR | USDC | collateral / max(K2-K1, K4-K3) | Based on wider wing |
| PHYSICAL_CALL | WETH | Same as INVERSE_CALL | 1 WETH = 1 contract |
| PHYSICAL_PUT | USDC | Same as Vanilla PUT | collateral / strike |
Key rule: Only INVERSE_CALL and PHYSICAL_CALL use WETH (base asset). Everything else uses USDC — including CALL_SPREAD, CALL_FLY, and CALL_CONDOR.
When to Use Each Structure
| Structure | Market View | Risk Level | Risk/Reward | Best For |
|---|---|---|---|---|
| PUT | Bearish | HIGH | Large profit (up to strike - premium), lose premium | Strong downside conviction |
| CALL | Bullish | HIGH | Unlimited profit, lose premium | Strong upside conviction |
| PUT_SPREAD | Moderately bearish | MEDIUM | Capped profit/loss | Cheaper than vanilla put |
| CALL_SPREAD | Moderately bullish | MEDIUM | Capped profit/loss | Cheaper than vanilla call |
| PUT_FLY | Neutral/range-bound | LOW | High reward if pinned, tiny cost | Low cost, precise target |
| CALL_FLY | Neutral/range-bound | LOW | High reward if pinned, tiny cost | Low cost, precise target |
| CONDOR | Neutral/range-bound | MEDIUM | Lower reward, wider range | More forgiving than fly |
| IRON_CONDOR | Neutral | LOW | Collect premium both sides | Range-bound markets |
| PHYSICAL_CALL | Bullish (want delivery) | HIGH | Receive underlying asset | Physical settlement needed |
| PHYSICAL_PUT | Bearish (want delivery) | HIGH | Deliver underlying asset | Physical settlement needed |
Risk-Categorized Strategy Recommendations
When a user asks for a strategy recommendation, FIRST ask about their risk preference, then match to appropriate strategies within that tier.
Step 1: Ask the User's Risk Level
Present these three options:
| Risk Level | What It Means | Max Loss | Profit Potential |
|---|---|---|---|
| LOW | Defined max loss, premium collection, high probability of small wins | Capped & known upfront | Capped but consistent |
| MEDIUM | Spreads with balanced risk/reward, moderate probability | Capped at spread width | Capped at spread width |
| HIGH | Full directional exposure, low probability of large wins | Full premium (buyer) or very large (seller) | Unlimited (buyer) |
Step 2: Strategy Decision Tree by Risk Level
User wants a strategy recommendation
│
▼
┌──────────────────────────────────┐
│ Step 1: Check market news │
│ (WebSearch - see News section) │
└──────────────────────────────────┘
│
▼
┌──────────────────────────────────┐
│ Step 2: Ask risk level │
│ LOW / MEDIUM / HIGH │
└──────────────────────────────────┘
│
├── LOW RISK
│ │
│ ├── Neutral view ──────► IRON_CONDOR (sell)
│ │ Collect premium both sides, profit if range-bound
│ │ Max loss: spread width - premium
│ │ Max gain: premium collected
│ │ Win prob: ~60-70% (price stays in range)
│ │
│ ├── Slightly bullish ──► PUT_FLY (buy) or Sell PUT via orderbook
│ │ Butterfly: tiny cost, big payout if pinned
│ │ Max loss: premium paid (very small)
│ │ Max gain: wing width - premium
│ │
│ ├── Slightly bearish ──► CALL_FLY (buy) or Sell CALL via orderbook
│ │ Same as above, opposite direction
│ │
│ └── Range-bound ───────► Butterfly (CALL_FLY / PUT_FLY)
│ Cheapest defined-risk play
│ Max loss: premium paid (~$3-5/contract)
│ Max gain: wing width (~$47-50/contract)
│
├── MEDIUM RISK
│ │
│ ├── Bullish ──────────► CALL_SPREAD (buy)
│ │ Cheaper than vanilla call, capped both sides
│ │ Max loss: premium paid
│ │ Max gain: spread width - premium
│ │ Risk/Reward: typically 1:1 to 1:3
│ │
│ ├── Bearish ─────────► PUT_SPREAD (buy)
│ │ Cheaper than vanilla put, capped both sides
│ │ Max loss: premium paid
│ │ Max gain: spread width - premium
│ │
│ └── Range-bound ─────► CONDOR (CALL_CONDOR / PUT_CONDOR)
│ Wider profit range than butterfly
│ Max loss: wing width - premium collected
│ Max gain: premium collected (sell) or inner width (buy)
│
└── HIGH RISK
│
├── Bullish ──────────► CALL (vanilla buy) / INVERSE_CALL
│ Unlimited profit potential if price rises
│ Max loss: full premium paid
│ Max gain: unlimited
│
├── Bearish ─────────► PUT (vanilla buy)
│ Unlimited profit if price crashes
│ Max loss: full premium paid
│ Max gain: strike - premium (if price → 0)
│
└── Selling premium ──► Naked PUT sell or CALL sell
Collect premium, very large risk if wrong
Max loss: strike price (PUT) or unlimited (CALL)
Max gain: premium collected
Risk Summary Card (Always Show When Recommending)
Every strategy recommendation MUST include this card:
┌────────────────────────────────────────────────────┐
│ Strategy: [STRATEGY NAME] │
│ Risk: [LOW / MEDIUM / HIGH] │
│ Direction: [BULLISH / BEARISH / NEUTRAL] │
├────────────────────────────────────────────────────┤
│ Max Loss: $[amount] ([description]) │
│ Max Gain: $[amount] ([description]) │
│ Break-even: $[price] │
│ Win Zone: Price between $[low] and $[high] │
│ Collateral: [amount] [token] │
└────────────────────────────────────────────────────┘
Collateral Efficiency Comparison
Different structures give you different exposure per dollar of collateral:
| Structure | $100 Collateral Gets You | Relative Efficiency | Risk Level |
|---|---|---|---|
| Vanilla PUT (at $2000 strike) | ~0.05 contracts | 1x (baseline) | HIGH |
| PUT_SPREAD ($100 width) | 1 contract | 20x more | MEDIUM |
| Butterfly ($50 wing width) | 2 contracts | 40x more | LOW |
| Condor ($100 inner width) | 1 contract | 20x more | MEDIUM |
| Iron Condor ($100 inner width) | 1 contract | 20x more | LOW |
Key Insight: For users with small collateral (under $100), LOW and MEDIUM risk strategies (spreads, butterflies) provide significantly more market exposure than HIGH risk vanilla options.
Strategy Selection Inputs
When recommending a strategy, consider these inputs in order of priority:
| Priority | Input | How to Determine | Impact on Recommendation |
|---|---|---|---|
| 1 | Risk Level | Ask user: low, medium, or high? | PRIMARY filter — determines available strategies |
| 2 | Market View | Ask user: bullish, bearish, or neutral? | Determines direction within risk tier |
| 3 | News/Events | WebSearch for recent market events | Informs conviction level and timing |
| 4 | Collateral Size | Check wallet balance | Small = prefer spreads/butterflies for efficiency |
| 5 | Conviction Level | Ask: strong view or moderate? | High conviction may justify higher risk tier |
| 6 | Expiry Timeframe | Check available expiries | Short-term neutral = butterfly, Longer = condor |
News & Events Integration
Before EVERY strategy recommendation, the agent MUST search for recent market-moving events. This ensures context-aware recommendations, not purely technical ones.
How to Fetch Market News
Use WebSearch with these queries (run at least 2):
| Query | Purpose |
|---|---|
"crypto market news today" | General market sentiment |
"ethereum price catalyst" or "bitcoin price catalyst" | Asset-specific events |
"FOMC meeting crypto" or "CPI inflation crypto" | Macro events affecting crypto |
"crypto options expiry this week" | Options-specific events (pin risk, gamma squeeze) |
"ethereum upgrade" or "bitcoin ETF flows" | Protocol/regulatory catalysts |
If WebSearch returns a relevant article, use WebFetch to get deeper details.
Translating News to Strategy
| Event Type | Expected Impact | Suggested Direction | Risk Note |
|---|---|---|---|
| Rate cut / dovish Fed | Bullish | CALL-side strategies | MEDIUM — market may "sell the news" |
| Rate hike / hawkish Fed | Bearish | PUT-side strategies | MEDIUM — may already be priced in |
| Major exchange hack/exploit | Bearish (short-term) | PUT-side strategies | HIGH — volatility spike |
| ETF approval / large inflows | Bullish | CALL-side strategies | MEDIUM — catalyst may be priced in |
| Protocol upgrade (successful) | Bullish for that asset | CALL-side strategies | LOW-MEDIUM |
| Regulatory crackdown | Bearish | PUT-side or neutral | HIGH — uncertainty and fear |
| Large options expiry (high OI) | Neutral → pin risk | Butterflies / Condors (LOW risk) | LOW — defined risk |
| No significant news | Neutral / range-bound | Premium selling (Iron Condor) | LOW — collect theta decay |
| Conflicting signals | Uncertain | Neutral structures or wait | Reduce size, use LOW risk |
News-Informed Recommendation Template
When presenting a strategy recommendation, always include market context:
Market Context (from recent news):
• [Event 1]: [brief description] → [bullish/bearish/neutral]
• [Event 2]: [brief description] → [bullish/bearish/neutral]
Overall market bias: [BULLISH / BEARISH / NEUTRAL / UNCERTAIN]
Based on [RISK LEVEL] risk preference and current market conditions:
[Strategy recommendation with Risk Summary Card]
Multi-Strike RFQ Examples
2-Strike Spread (CALL_SPREAD):
npx tsx scripts/build-rfq.ts \
--underlying ETH \
--type CALL \
--strikes 2000,2100 \
--expiry 1774684800 \
--contracts 1 \
--direction buy
Buy $2000 call, sell $2100 call. Max profit = $100 - premium. Max loss = premium.
3-Strike Butterfly (PUT_FLY):
npx tsx scripts/build-rfq.ts \
--underlying ETH \
--type PUT \
--strikes 1900,1850,1800 \
--expiry 1774684800 \
--contracts 1 \
--direction buy
Profit maximized if ETH settles at $1850. Low cost, high reward if pinned.
4-Strike Condor (IRON_CONDOR):
npx tsx scripts/build-rfq.ts \
--underlying ETH \
--type PUT \
--strikes 1800,1900,2100,2200 \
--expiry 1774684800 \
--contracts 1 \
--direction sell
Sell iron condor. Collect premium if ETH stays between $1900-$2100.
OptionBook vs RFQ (Factory)
The SDK supports two trading systems. Choose based on your use case:
| OptionBook | RFQ (Factory) | |
|---|---|---|
| What | Fill existing market-maker orders | Create custom options via sealed-bid auction |
| When to use | Quick trades on listed options | Custom strikes, expiries, multi-leg structures |
| Structures | Vanilla only | Vanilla, spread, butterfly, condor, iron condor |
| Key methods | fillOrder(), previewFillOrder() | buildRFQRequest(), requestForQuotation() |
| Pricing | Order prices from fetchOrders() | MM pricing from getAllPricing() |
| Data source | Book indexer (/api/v1/book/) | Factory indexer (/api/v1/factory/) |
| User data | getUserPositionsFromIndexer() | getUserRfqs(), getUserOptionsFromRfq() |
| Stats | getBookProtocolStats(), getBookDailyStats() | getFactoryProtocolStats(), getFactoryDailyStats() |
| Collateral | Paid upfront by taker | collateralAmount = 0 (held by factory) |
| Settlement | Cash only | Cash or physical |
Orderbook Trading
What it is: Fill existing orders posted by market makers - instant execution.
You → Fill existing order → Instant trade
Best for:
- Standard strikes/expiries with existing liquidity
- Quick trades when you see a good price
- Smaller to medium sizes
How it works:
- Fetch orderbook:
npx tsx scripts/fetch-orders.ts --type PUT - See available BIDs (buyers) and ASKs (sellers)
- Fill the order you want:
npx tsx scripts/fill-order.ts --order-index 0 --collateral 10 --seed "..." --execute
RFQ (Request for Quote)
What it is: Request a custom quote from market makers - they respond within 45 seconds.
You → Submit RFQ → MM sees it → MM sends encrypted offer → You settle (or accept early)
Best for:
- Custom strikes/expiries not on orderbook
- No existing liquidity at your terms
- Larger sizes (MMs may offer better pricing)
- Multi-leg structures (spreads, butterflies, condors)
How it works:
- Build RFQ:
npx tsx scripts/build-rfq.ts --underlying ETH --type PUT --strike 1900 --expiry <timestamp> --contracts 0.1 --direction buy - Send transaction with the returned
toanddata - Wait up to 45 seconds for MM response
- Settle when offer received (or accept early - see Early Settlement)
Decision Matrix
| Scenario | Use | Why |
|---|---|---|
| "I see a good price on orderbook" | Orderbook | Instant execution |
| "Standard strike, liquidity exists" | Orderbook | Faster, simpler |
| "Custom strike not on orderbook" | RFQ | Only way to get it |
| "Large size trade" | RFQ | May get better pricing |
| "Need it filled NOW" | Orderbook | RFQ takes up to 6 min |
| "No orders at my strike" | RFQ | Request custom quote |
| "Multi-leg structure" | RFQ | Orderbook only supports vanilla |
| "Physical settlement" | RFQ | Orderbook is cash-only |
Agent Decision Logic:
- First, check orderbook for liquidity at your strike
- If liquidity exists → recommend Orderbook
- If no liquidity → recommend RFQ and explain why
Agent Decision Logic (Orderbook-First)
When a user wants to trade, the agent ALWAYS checks the orderbook first before recommending a trading method.
Decision Flowchart
User wants to trade
│
▼
┌─────────────────────────┐
│ check-orderbook.ts │
│ Check for liquidity │
└─────────────────────────┘
│
▼
┌─────────────────────────┐
│ Exact strike match? │
└─────────────────────────┘
│ │
Yes No
│ │
▼ ▼
┌─────────┐ ┌─────────────────┐
│Orderbook│ │Nearby strikes? │
│ Fill │ │(within ±5%) │
└─────────┘ └─────────────────┘
│ │
Yes No
│ │
▼ ▼
┌─────────┐ ┌─────┐
│Show │ │ RFQ │
│options │ │ │
└─────────┘ └─────┘
How to Use check-orderbook.ts
Before recommending orderbook or RFQ, run:
npx tsx scripts/check-orderbook.ts --underlying ETH --type PUT --strike 1900 --expiry 1774684800 --direction sell
The script returns:
recommendation: "orderbook" or "rfq"reason: Explanation of whyorderbookOrders: Matching orders with pricesnearbyStrikes: Alternative strikes if exact not foundnextStep: Command to run next
Example Scenarios
Scenario 1: Liquidity Found
User: "Sell a PUT at $1900"
Agent: Runs check-orderbook.ts
Result: {
"recommendation": "orderbook",
"reason": "Found orderbook liquidity at strike $1900. Best bid: $8.08. Available: 75 contracts."
}
Agent: "Found a buyer at $8.08/contract. Use orderbook for instant execution."
Scenario 2: No Exact Match, Nearby Available
User: "Sell a PUT at $1850"
Agent: Runs check-orderbook.ts
Result: {
"recommendation": "rfq",
"reason": "No orderbook liquidity at $1850. Nearby strikes: $1900 (+2.7%), $1925 (+4.1%)"
}
Agent: "No orders at $1850, but $1900 is available (+2.7%). Would you like that, or submit RFQ for $1850?"
Scenario 3: Partial Fill
User: "Sell 100 contracts at $1900"
Agent: Runs check-orderbook.ts --size 100
Result: {
"recommendation": "orderbook",
"partialFillAvailable": true,
"partialSize": 75,
"reason": "Found 75 contracts (you requested 100). Partial fill available."
}
Agent: "Only 75 contracts available on orderbook. Fill partial now, or use RFQ for full 100?"
Trading Workflows
Workflow 1: Fill an Orderbook Order (Instant)
Step 1: Check wallet
└─> node scripts/wallet-discover.js
Step 2: Check balance
└─> node scripts/wallet-balance.js --chain base-mainnet --tokens 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
Step 3: Fetch orderbook
└─> npx tsx scripts/fetch-orders.ts --type PUT
Step 4: Preview fill (see what you'll get)
└─> npx tsx scripts/fill-order.ts --order-index 0 --collateral 10 --seed "..."
Step 5: Execute fill
└─> npx tsx scripts/fill-order.ts --order-index 0 --collateral 10 --seed "..." --execute --wait
Workflow 2: Submit an RFQ (Custom Terms)
Step 1: Check wallet & balance (same as above)
Step 2: Get MM pricing to see market levels
└─> npx tsx scripts/get-mm-pricing.ts ETH --type PUT
Step 3: Build RFQ with your terms
└─> npx tsx scripts/build-rfq.ts --underlying ETH --type PUT --strike 1900 --expiry 1774684800 --contracts 0.1 --direction buy
Step 4: Approve tokens (if selling/first time)
└─> npx tsx scripts/approve-token.ts --token 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 --spender 0x1aDcD391CF15Fb699Ed29B1D394F4A64106886e5 --max --seed "..." --wait
Step 5: Send RFQ transaction
└─> npx tsx scripts/send-transaction.ts --to <from step 3> --data <from step 3> --seed "..." --wait
Step 6: Wait for MM offer (up to 45 seconds)
Step 7: Verify RFQ fill
└─> npx tsx scripts/check-rfq-fill.ts --address <wallet> --ticker <expected> --since <submission_timestamp>
└─> If filled: Shows position details
└─> If not filled: Shows suggestions for next steps
Workflow 3: Check Positions
npx tsx scripts/get-positions.ts 0xYourWalletAddress
Workflow 3b: Portfolio Performance & ROI Review
After trades expire, show the user their realized returns. This workflow uses existing scripts — no new tools needed.
How to Calculate ROI
Step 1: Fetch all positions
└─> npx tsx scripts/get-positions.ts <wallet_address>
Step 2: Categorize by status
- Settled/expired positions → REALIZED P&L
- Active/open positions → UNREALIZED (show separately)
Step 3: For each expired position
- Use the pnlUsd field from get-positions.ts (if available)
- If pnlUsd is missing, calculate manually:
└─> For PUT/CALL: npx tsx scripts/calculate-payout.ts --type <PUT|CALL> --strike <strike> --settlement <settlement_price> --contracts <amount> [--is-buyer]
└─> For spreads/butterflies/condors: use client.option.calculatePayout(optionAddress, settlementPrice) on-chain
(Note: calculate-payout.ts only supports vanilla PUT/CALL — multi-strike payouts need the SDK on-chain method)
Step 4: Aggregate and present using the template below
ROI Calculation Logic
For BUYERS (paid premium):
- Cost basis = premium paid (collateral committed)
- Return = pnlUsd or calculated payout
- ROI = (return / cost_basis) × 100%
- Expired worthless → ROI = -100%
- Example: Paid $10 premium, option settled for $25 payout → ROI = +150%
For SELLERS (received premium):
- Credit received = premium collected
- Settlement cost = pnlUsd (negative = you paid out)
- ROI = (net_pnl / collateral_locked) × 100%
- Expired worthless for buyer (good for seller) → ROI = +(premium / collateral) × 100%
- Example: Collected $8 premium on $100 collateral, expired OTM → ROI = +8%
Portfolio Performance Summary Template
Present results in this format:
Portfolio Performance Summary
══════════════════════════════════════════════════════════
Wallet: 0x...
Period: [earliest expired trade] to [latest expired trade]
REALIZED P&L (Expired Positions)
┌──────────────────────┬──────┬─────────┬─────────┐
│ Trade │ Side │ P&L │ ROI │
├──────────────────────┼──────┼─────────┼─────────┤
│ ETH-28MAR-2000-P │ SELL │ +$12.50 │ +6.25% │
│ ETH-28MAR-2100-C │ BUY │ -$8.00 │ -100% │
│ ETH-04APR-1900/1800 │ BUY │ +$45.00 │ +150% │
├──────────────────────┼──────┼─────────┼─────────┤
│ TOTAL │ │ +$49.50 │ │
└──────────────────────┴──────┴─────────┴─────────┘
Win rate: 2/3 (66.7%)
Average P&L per trade: +$16.50
Best trade: ETH PUT_SPREAD (+$45.00, +150%)
Worst trade: ETH CALL (-$8.00, expired worthless)
OPEN POSITIONS
┌──────────────────────┬──────┬───────────┬──────────┬────────┐
│ Trade │ Side │ Contracts │ Expiry │ Status │
├──────────────────────┼──────┼───────────┼──────────┼────────┤
│ ETH-18APR-2000-P │ BUY │ 0.5 │ Apr 18 │ Active │
└──────────────────────┴──────┴───────────┴──────────┴────────┘
When to Show Performance
- User asks "how are my trades doing?", "show my P&L", "what's my ROI?"
- User asks "check my positions" AND has expired positions → include ROI for expired ones
- Proactively mention performance when user has recently expired positions they haven't reviewed
- After any trade settles, offer to show the outcome: "Your ETH PUT just expired. Want to see how it did?"
Workflow 4: Early Settlement (Accept MM Offer Before Deadline)
After a market maker submits an encrypted offer to your RFQ, you can decrypt and accept it early — no need to wait for the full deadline.
Step 1: Submit RFQ (Workflow 2, Steps 1-5)
Step 2: MM sends encrypted offer (within 45 seconds)
Step 3: Decrypt the offer using your ECDH keypair
└─> Uses client.rfqKeys.decryptOffer()
Step 4: Accept the offer early
└─> Uses client.optionFactory.encodeSettleQuotationEarly(quotationId, offerAmount, nonce, offeror)
└─> Send transaction with returned {to, data}
Real examples:
- RFQ 784 (PUT BUTTERFLY $1700/$1800/$1900): MM offered at 04:05:45 UTC, early settle at 04:07:09 UTC (3 min before deadline)
- TX:
0x105f75cdfb64a3796100f6d667bc4f7fec3836d2b5aa5c43b66073a1b40964ee
- TX:
- RFQ 785 (PUT CONDOR $1600/$1700/$1800/$1900): MM offered 0.003248 USDC, early settle at 04:15:00 UTC
- TX:
0xa89fb6dbad43b430399bbdec878927185e602b7df9b5390f71d2d11c33e4d850
- TX:
Workflow 5: Cancel RFQ & Claim Refund
If a user wants to cancel an active RFQ before it settles, or if no MM responded:
┌─────────────────────────────────────────────────────┐
│ RFQ submitted │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ MM responded? │ │
│ └─────────────────────┘ │
│ │ │ │
│ Yes No │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────┐ ┌──────────────────────────────┐ │
│ │ Accept │ │ RFQ expires with no offers │ │
│ │ or │ │ → No collateral was locked │ │
│ │ Cancel? │ │ → Nothing to refund │ │
│ └─────────┘ └──────────────────────────────┘ │
│ │ │ │
│ Accept Cancel │
│ │ │ │
│ ▼ ▼ │
│ Settle Cancel RFQ │
│ └─> cancelQuotation(quotationId) │
│ └─> Collateral returned to wallet │
└─────────────────────────────────────────────────────┘
Cancel an Active RFQ
// Cancel before settlement — requester only
const { to, data } = client.optionFactory.encodeCancelQuotation(quotationId);
// Send transaction with {to, data} via send-transaction.ts
Cancel an Offer (Market Maker Side)
// MM cancels their offer before settlement
const { to, data } = client.optionFactory.encodeCancelOfferForQuotation(quotationId);
Check RFQ Status
// Query current state of an RFQ
const quotation = await client.optionFactory.getQuotation(quotationId);
// quotation status: 'active' | 'settled' | 'cancelled'
// quotation.isActive — whether RFQ is still open
// quotation.currentWinner — winning offeror's address
Refund Rules
| Scenario | Collateral | What Happens |
|---|---|---|
| Buy RFQ, no MM responds | No collateral locked by buyer | Nothing to refund — buyer only pays premium if settled |
| Buy RFQ, user cancels before settlement | No collateral locked | RFQ cancelled, no action needed |
| Sell RFQ, no MM responds | Collateral held by factory | RFQ expires, collateral returned automatically |
| Sell RFQ, user cancels before settlement | Collateral held by factory | cancelQuotation() releases collateral back to wallet |
| Sell RFQ, settled | Collateral locked in option | Collateral backs the option until expiry/settlement |
| Orderbook fill (buy or sell) | Collateral paid/locked at fill | Irreversible — no cancellation for orderbook fills |
IMPORTANT: Orderbook fills are INSTANT and IRREVERSIBLE. Only RFQs can be cancelled (before settlement).
Buy vs Sell Order: Collateral & Token Flow
No ETH is sent as transaction value for option trades. All collateral flows happen via ERC-20 token transfers (USDC, WETH). ETH is only used for gas fees.
When BUYING Options
Buyer Flow:
1. Premium is paid in collateral tokens (USDC for PUTs, WETH for CALLs)
2. Token approval needed: approve-token.ts (one-time per token/spender)
3. Collateral is transferred to the contract when the trade executes
4. If RFQ: collateralAmount = 0 in the request (factory holds it)
5. If Orderbook: collateral deducted at fill time
What you PAY: Premium (in USDC or WETH) + gas (in ETH)
What you GET: Option position (right to profit if price moves your way)
At expiry: Payout if in-the-money, or nothing if out-of-the-money
When SELLING Options
Seller Flow:
1. Collateral is LOCKED to back the option (USDC for PUTs, WETH for CALLs)
2. Token approval needed: approve-token.ts (one-time per token/spender)
3. Collateral transferred to contract and held until expiry
4. Premium is received from buyer
What you PAY: Collateral lock (USDC or WETH) + gas (in ETH)
What you GET: Premium received upfront
At expiry: Keep premium if OTM, or payout difference if ITM
Remaining collateral returned after settlement
Token Requirements Summary
| Action | Collateral Token | Gas Token | Approval Needed |
|---|---|---|---|
| Buy PUT | USDC (premium) | ETH | USDC → OptionFactory/OptionBook |
| Buy CALL | WETH (premium) | ETH | WETH → OptionFactory/OptionBook |
| Sell PUT | USDC (collateral lock) | ETH | USDC → OptionFactory/OptionBook |
| Sell CALL | WETH (collateral lock) | ETH | WETH → OptionFactory/OptionBook |
Always check balance BEFORE trading:
# Check USDC balance (for PUTs)
node scripts/wallet-balance.js --chain base-mainnet --tokens 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
# Check WETH balance (for CALLs)
node scripts/wallet-balance.js --chain base-mainnet --tokens 0x4200000000000000000000000000000000000006
# Check ETH balance (for gas)
node scripts/wallet-balance.js --chain base-mainnet
Winner Selection & Payout by Option Type
The winner (winningOfferor) in an RFQ is always the market maker with the best offer price. Winner selection works the same way for ALL option types — PUT, CALL, spreads, butterflies, condors, iron condors, and physical options.
Winner Fields in SDK
The winner address appears in three places depending on context:
| Context | Field | When to Use |
|---|---|---|
| On-chain state | QuotationState.currentWinner | During active RFQ — the current best offeror |
| State API | StateRfq.winner | After settlement — the final winning MM |
| Settlement event | QuotationSettledEvent.winningOfferor | Emitted when RFQ settles |
Payout Formulas by Option Type
| Type | Buyer Payout Formula | Max Payout | Settlement |
|---|---|---|---|
| PUT | max(strike - price, 0) × contracts | strike × contracts | Cash (USDC) |
| INVERSE_CALL | max(price - strike, 0) × contracts | Unlimited | Cash (WETH) |
| CALL_SPREAD | min(max(price - K1, 0), K2 - K1) × contracts | (K2 - K1) × contracts | Cash (USDC) |
| PUT_SPREAD | min(max(K1 - price, 0), K1 - K2) × contracts | (K1 - K2) × contracts | Cash (USDC) |
| CALL_FLY | Peaks at middle strike, 0 at wings | wing width × contracts | Cash (USDC) |
| PUT_FLY | Peaks at middle strike, 0 at wings | wing width × contracts | Cash (USDC) |
| CALL_CONDOR | Flat max between K2-K3, tapers at wings | inner width × contracts | Cash (USDC) |
| PUT_CONDOR | Flat max between K2-K3, tapers at wings | inner width × contracts | Cash (USDC) |
| IRON_CONDOR | Profit if price between K2-K3 | premium collected | Cash (USDC) |
| PHYSICAL_CALL | Buyer receives underlying asset | N/A (delivery) | Physical |
| PHYSICAL_PUT | Buyer delivers underlying, receives USDC | N/A (delivery) | Physical |
SDK Payout Calculation Support
The SDK utility client.utils.calculatePayout() supports these types:
type PayoutType = 'call' | 'put' | 'call_spread' | 'put_spread';
For butterflies, condors, and iron condors: use client.option.calculatePayout(optionAddress, settlementPrice) which calculates on-chain for any option type, or use client.option.simulatePayout() for simulation.
// For vanilla and spreads — use utility function
const payout = client.utils.calculatePayout({
type: 'call_spread',
strikes: [2000n * 10n**8n, 2100n * 10n**8n],
settlementPrice: 2050n * 10n**8n,
numContracts: 1n * 10n**18n,
});
// For butterflies, condors, iron condors — use on-chain calculation
const payout = await client.option.calculatePayout(optionAddress, settlementPrice);
Settlement Events
When an option settles at expiry, these events are emitted:
| Event | Fields | Description |
|---|---|---|
OptionExpiredEvent | settlementPrice | Oracle records the final price |
OptionPayoutEvent | buyer, amountPaidOut | Buyer receives payout (if ITM) |
CollateralReturnedEvent | seller, amountReturned | Seller gets remaining collateral back |
Physical Settlement (PHYSICAL_CALL / PHYSICAL_PUT)
Physical options involve actual asset delivery instead of cash settlement:
| Type | Buyer Pays | Buyer Receives | Seller Provides |
|---|---|---|---|
| PHYSICAL_CALL | strike × contracts (USDC) | contracts of underlying (WETH) | WETH collateral |
| PHYSICAL_PUT | contracts of underlying (WETH) | strike × contracts (USDC) | USDC collateral |
The SDK has dedicated builders for physical options:
client.optionFactory.buildPhysicalRFQ()client.optionFactory.buildPhysicalSpreadRFQ()client.optionFactory.buildPhysicalButterflyRFQ()client.optionFactory.buildPhysicalCondorRFQ()
Understanding Collateral vs Contracts
When viewing orders, availableAmount represents the maker's collateral budget, not the number of contracts. The actual number of purchasable contracts depends on the option type and collateral requirements.
Collateral Formulas by Option Type (On-Chain)
These are the on-chain formulas using raw decimal values. For practical use, prefer previewFillOrder() which handles all conversions automatically.
| Option Type | # Strikes | Collateral | On-Chain Formula | Example |
|---|---|---|---|---|
| Vanilla PUT | 1 | USDC | (collateral × 1e8) / strike | 10,000 USDC at $95k = 0.105 contracts |
| INVERSE_CALL | 1 | WETH | collateral / 1e12 | 1 WETH = 1 contract |
| CALL_SPREAD | 2 | USDC | (collateral × 1e8) / spreadWidth | 10,000 USDC / $10k = 1 contract |
| PUT_SPREAD | 2 | USDC | (collateral × 1e8) / spreadWidth | 10,000 USDC / $10k = 1 contract |
| BUTTERFLY | 3 | USDC | (collateral × 1e8) / maxSpread | Based on widest strike range |
| CONDOR | 4 | USDC | (collateral × 1e8) / maxSpread | Based on widest strike range |
| PHYSICAL_CALL | 1 | WETH | Same as INVERSE_CALL | 1 WETH = 1 contract |
| PHYSICAL_PUT | 1 | USDC | Same as Vanilla PUT | Same formula |
IMPORTANT: Only single-strike CALL options (INVERSE_CALL, PHYSICAL_CALL) use WETH collateral. ALL multi-strike structures use USDC, even CALL_SPREAD, CALL_FLY, and CALL_CONDOR.
Using previewFillOrder
Always use previewFillOrder() to see the actual contract count before filling:
const order = orders[0];
// Preview shows calculated max contracts based on collateral requirements
const preview = client.optionBook.previewFillOrder(order);
console.log(`Max contracts: ${preview.maxContracts}`);
console.log(`Collateral token: ${preview.collateralToken}`);
console.log(`Price per contract: ${preview.pricePerContract}`);
// Preview with specific premium amount
const preview10 = client.optionBook.previewFillOrder(order, 10_000000n); // 10 USDC premium
console.log(`Contracts for 10 USDC: ${preview10.numContracts}`);
Why This Matters
For a PUT option with a $95,000 strike:
- Maker provides: 10,000 USDC collateral
- Max contracts: 10,000 / 95,000 ≈ 0.105 contracts (not 10,000!)
The previewFillOrder() method handles these calculations automatically for all option types.
SDK Modules Reference
| Module | Purpose | Requires Signer |
|---|---|---|
client.erc20 | Token approvals, balances, transfers | Write ops only |
client.optionBook | Fill/cancel orders, get fees, preview fills | Write ops only |
client.api | Fetch orders, positions, stats | No |
client.optionFactory | RFQ lifecycle management | Write ops only |
client.option | Position management, payouts | Write ops only |
client.events | Query blockchain events (OfferMade, QuotationRequested) | No |
client.ws | Real-time WebSocket subscriptions | No |
client.pricing | Option pricing, Greeks | No |
client.mmPricing | Market maker pricing with fee adjustments | No |
client.rfqKeys | ECDH keypair management for sealed-bid RFQ encryption | No |
client.utils | Decimal conversions, payoffs | No |
Referrer Fee Sharing
The SDK supports a referrer address for fee sharing on order fills. When a referrer is set, a portion of the trading fees is allocated to the referrer.
// Option 1: Set referrer at client initialization (applies to all fills)
const client = new ThetanutsClient({
chainId: 8453,
provider,
signer,
referrer: '0x92b8ac05b63472d1D84b32bDFBBf3e1887331567',
});
// All fillOrder calls will use this referrer automatically
await client.optionBook.fillOrder(order);
// Option 2: Pass referrer per fill call (overrides client default)
await client.optionBook.fillOrder(order, undefined, '0xYourReferrerAddress');
// Option 3: Use encode methods (for viem/wagmi/AA wallets)
const { to, data } = client.optionBook.encodeFillOrder(
order,
collateralAmount,
'0x92b8ac05b63472d1D84b32bDFBBf3e1887331567'
);
const hash = await walletClient.sendTransaction({ to, data });
// Query referrer fee split
const feeBps = await client.optionBook.getReferrerFeeSplit('0x...');
console.log(`Referrer fee: ${feeBps} bps`);
// Query accumulated fees
const fees = await client.optionBook.getFees(usdcAddress, '0x...');
console.log(`Accumulated fees: ${fees}`);
If no referrer is provided, the zero address (0x000...) is used (no fee sharing).
RFQ Key Management
The SDK uses ECDH (Elliptic Curve Diffie-Hellman) key pairs for encrypted offers in the RFQ system. Keys are automatically persisted based on your environment:
| Environment | Default Storage | Persistence |
|---|---|---|
| Node.js | FileStorageProvider | Keys saved to .thetanuts-keys/ directory |
| Browser | LocalStorageProvider | Keys saved to localStorage |
Automatic Key Management
// Keys are automatically persisted - no configuration needed
const keyPair = await client.rfqKeys.getOrCreateKeyPair();
console.log('Public Key:', keyPair.compressedPublicKey);
// Keys are saved automatically and survive process restarts
Key Backup Warning
IMPORTANT: Back up your RFQ private keys! Keys are stored in
.thetanuts-keys/with secure permissions. If lost, you cannot decrypt offers made to your public key. There is no recovery mechanism.
Real-Time WebSocket Subscriptions
Subscribe to live updates for orders and prices:
const client = new ThetanutsClient({ chainId: 8453, provider });
// 1. Connect
await client.ws.connect();
// 2. Subscribe to order updates
const unsubOrders = client.ws.subscribeOrders((update) => {
console.log(`Order ${update.event}:`, update);
});
// 3. Subscribe to price updates for ETH
const unsubPrices = client.ws.subscribePrices((update) => {
console.log(`ETH price: $${update.price}`);
}, 'ETH');
// 4. Handle connection state changes
const unsubState = client.ws.onStateChange((state) => {
console.log(`WebSocket state: ${state}`);
});
// 5. Disconnect when done
// unsubOrders(); unsubPrices(); unsubState();
// client.ws.disconnect();
The WebSocket module auto-reconnects by default (up to 10 attempts).
Error Handling
All SDK methods throw ThetanutsError with typed error codes:
import { isThetanutsError, OrderExpiredError, InsufficientAllowanceError } from '@thetanuts-finance/thetanuts-client';
try {
await client.optionBook.fillOrder(order, 10_000000n);
} catch (error) {
if (error instanceof OrderExpiredError) {
console.log('Order expired, fetching fresh orders...');
const freshOrders = await client.api.fetchOrders();
} else if (error instanceof InsufficientAllowanceError) {
console.log('Approving tokens first...');
await client.erc20.ensureAllowance(usdcAddress, optionBookAddress, amount);
} else if (isThetanutsError(error)) {
switch (error.code) {
case 'SLIPPAGE_EXCEEDED': console.log('Price moved too much'); break;
case 'INSUFFICIENT_BALANCE': console.log('Not enough tokens'); break;
case 'SIGNER_REQUIRED': console.log('Signer required'); break;
case 'CONTRACT_REVERT': console.log('Contract call failed'); break;
}
}
}
Error Codes
| Code | Description |
|---|---|
ORDER_EXPIRED | Order has expired or will expire soon |
SLIPPAGE_EXCEEDED | Price moved beyond tolerance |
INSUFFICIENT_ALLOWANCE | Token approval needed |
INSUFFICIENT_BALANCE | Not enough tokens |
NETWORK_UNSUPPORTED | Network not supported |
HTTP_ERROR | API request failed |
CONTRACT_REVERT | Smart contract call failed |
INVALID_PARAMS | Invalid parameters provided |
ORDER_NOT_FOUND | Order not found |
SIZE_EXCEEDED | Fill size exceeds available |
SIGNER_REQUIRED | Signer needed for transaction |
WEBSOCKET_ERROR | WebSocket connection error |
SDK Configuration Options
interface ThetanutsClientConfig {
chainId: 8453; // Required: Chain ID
provider: Provider; // Required: ethers.js provider
signer?: Signer; // Optional: For transactions
referrer?: string; // Optional: Referrer address for fees
apiBaseUrl?: string; // Optional: Override API URL
indexerApiUrl?: string; // Optional: Override indexer URL
pricingApiUrl?: string; // Optional: Override pricing URL
wsUrl?: string; // Optional: Override WebSocket URL
env?: 'dev' | 'prod'; // Optional: Environment (default: prod)
logger?: ThetanutsLogger; // Optional: Custom logger
keyStorageProvider?: StorageProvider; // Optional: Custom RFQ key storage
}
Custom Logger
const client = new ThetanutsClient({
chainId: 8453,
provider,
logger: {
debug: (msg, meta) => myLogger.debug(msg, meta),
info: (msg, meta) => myLogger.info(msg, meta),
warn: (msg, meta) => myLogger.warn(msg, meta),
error: (msg, meta) => myLogger.error(msg, meta),
},
});
Production Checklist
Before deploying to production, verify the following:
- RPC Provider: Use a reliable provider (Alchemy, Infura, QuikNode) instead of public
https://mainnet.base.orgwhich has strict rate limits - Referrer Configuration: Set the
referreraddress to earn fee-sharing revenue on fills - Error Logging: Pass a custom
loggerto capture errors in Sentry/Datadog - Gas Buffer: SDK adds 20% gas buffer for Account Abstraction wallets (Coinbase Smart Wallet, Safe)
- Collateral Approval Flow: Always call
client.erc20.ensureAllowance()beforefillOrder() - WebSocket Reconnection: Auto-reconnects up to 10 attempts by default
- Order Expiry Checks: Check
order.expirybefore filling to avoid wasted gas estimates - RFQ Key Backup: Back up
.thetanuts-keys/directory — lost keys cannot decrypt past offers
Decimal Handling
The SDK provides utilities for safe decimal conversions:
| Type | Decimals | Example |
|---|---|---|
| USDC | 6 | 1000000 = 1 USDC |
| WETH | 18 | 1000000000000000000 = 1 WETH |
| cbBTC | 8 | 100000000 = 1 cbBTC |
| Strike/Price | 8 | 185000000000 = $1850 |
// Convert to on-chain values
const usdc = client.utils.toBigInt('100.5', 6); // 100500000n
const strike = client.utils.strikeToChain(1850); // 185000000000n
// Convert from on-chain values
const display = client.utils.fromBigInt(100500000n, 6); // '100.5'
const price = client.utils.strikeFromChain(185000000000n); // 1850
Example Conversations
"I just installed this, what do I do?"
Agent: [Triggers onboarding flow]
- Runs
wallet-discover.js→ no wallet found - Enters onboarding mode
- Guides through wallet creation
- Shows balance and funding instructions
- Fetches market news via WebSearch
- Asks risk preference
- Makes first strategy suggestion
Agent: Welcome to Thetanuts Options Trading! Let's get you set up.
I see you don't have a wallet configured yet.
Would you like to create a new wallet or import an existing one?
[After wallet creation...]
Your wallet: 0x7a3F...9e2B
Chain: Base Mainnet
Balance: 0 ETH | 0 USDC | 0 WETH
To start trading, you'll need to fund your wallet:
• ETH on Base for gas fees (bridge via bridge.base.org)
• USDC for PUT options | WETH for CALL options
Once funded, tell me:
1. What's your risk preference? (Low / Medium / High)
2. Are you bullish, bearish, or neutral on ETH?
I'll check the latest market news and recommend a strategy!
"What's happening in crypto that I should trade?"
Agent:
- Uses WebSearch:
"crypto market news today" - Uses WebSearch:
"ethereum bitcoin price catalyst this week" - Summarizes findings and maps to strategy
- Asks risk preference
- Recommends with Risk Summary Card
Agent: Let me check the latest market news...
Market Context:
• Fed meeting tomorrow — markets expect rate hold, dovish tone → BULLISH
• Large BTC options expiry ($2.3B) on Friday → potential pin risk → NEUTRAL
• ETH Layer-2 TVL hitting ATH → positive sentiment → BULLISH
Overall bias: MODERATELY BULLISH with near-term volatility expected
What risk level are you comfortable with?
LOW: Iron Condor — collect premium, profit if ETH stays in range
Good for the expected range-bound action before Fed meeting
MEDIUM: Call Spread — capped upside play on the bullish bias
Benefit from dovish Fed without unlimited downside
HIGH: Vanilla Call — unlimited upside if Fed sparks a rally
Lose full premium if market sells the news
"Show me LOW risk strategies"
Agent:
- Fetches current prices and MM pricing
- Filters for LOW risk structures
- Shows Risk Summary Cards for each
Agent: Here are the LOW RISK strategies available right now:
┌────────────────────────────────────────────────────┐
│ Strategy: Iron Condor (ETH) │
│ Risk: LOW │
│ Direction: NEUTRAL │
├────────────────────────────────────────────────────┤
│ Strikes: $1800 / $1900 / $2100 / $2200 │
│ Max Loss: $85/contract │
│ Max Gain: $15/contract (premium collected) │
│ Win Zone: ETH stays between $1900-$2100 │
│ Current: ETH $2,050 (right in the sweet spot!) │
│ Collateral: ~$100 USDC per contract │
└────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────┐
│ Strategy: Put Butterfly (ETH) │
│ Risk: LOW │
│ Direction: NEUTRAL (target $2,000) │
├────────────────────────────────────────────────────┤
│ Strikes: $1950 / $2000 / $2050 │
│ Max Loss: ~$3/contract (premium paid) │
│ Max Gain: ~$47/contract (if pinned at $2,000) │
│ Win Zone: ETH settles near $2,000 │
│ Collateral: ~$50 USDC per contract │
└────────────────────────────────────────────────────┘
Which strategy interests you? Or want to see MEDIUM or HIGH risk options?
"How did my trades do?"
Agent:
- Runs
get-positions.tswith user's wallet address - Separates expired from active positions
- Computes ROI for each expired position
- Presents portfolio performance summary
Agent: Let me check your trading performance...
Portfolio Performance Summary
══════════════════════════════════════════════════════════
Wallet: 0x7a3F...9e2B
Period: Mar 14, 2026 to Apr 11, 2026
REALIZED P&L (Expired Positions)
┌───────────────────────┬──────┬──────────┬─────────┐
│ Trade │ Side │ P&L │ ROI │
├───────────────────────┼──────┼──────────┼─────────┤
│ ETH-28MAR-2000-P │ SELL │ +$12.50 │ +6.25% │
│ ETH-28MAR-2100-C │ BUY │ -$8.00 │ -100% │
│ ETH-04APR-1900/1800-PS│ BUY │ +$45.00 │ +150% │
├───────────────────────┼──────┼──────────┼─────────┤
│ TOTAL │ │ +$49.50 │ │
└───────────────────────┴──────┴──────────┴─────────┘
Win rate: 2/3 (66.7%)
Average P&L per trade: +$16.50
Best trade: ETH PUT_SPREAD (+$45.00, +150% ROI)
Worst trade: ETH CALL (-$8.00, expired worthless)
Insight: Your MEDIUM risk spread trades outperformed the HIGH risk
vanilla call. Consider focusing on spreads for better risk-adjusted returns.
OPEN POSITIONS
┌───────────────────────┬──────┬───────────┬──────────┐
│ Trade │ Side │ Contracts │ Expiry │
├───────────────────────┼──────┼───────────┼──────────┤
│ ETH-18APR-2000-P │ BUY │ 0.5 │ Apr 18 │
└───────────────────────┴──────┴───────────┴──────────┘
Want me to analyze what your open position might settle at?
"Show me ETH puts"
User: Show me ETH puts
Agent:
- Fetches MM pricing:
npx tsx scripts/get-mm-pricing.ts ETH --type PUT - Shows available strikes, expiries, bid/ask prices
- Asks which one interests you
"I want to sell a put at $1900 with 10 USDC"
Agent's Decision Process:
- Check orderbook for $1900 PUT bids
- Found liquidity? → "Use Orderbook - there's a BID at $1900"
- No liquidity? → "Use RFQ - I'll request a quote for you"
If Orderbook:
Agent: There's a BID at $1900. Let me fill it for you.
1. Previews: "You'll sell ~0.005 contracts, receive ~$X premium"
2. Asks: "Proceed?"
3. Executes fill
If RFQ:
Agent: No orderbook liquidity at $1900. I'll submit an RFQ.
1. Builds RFQ for ETH-1900-PUT sell
2. Approves USDC if needed
3. Submits RFQ
4. "RFQ submitted. MMs have 45 seconds to respond."
"Check my positions"
Agent:
- Gets wallet address from discover
- Runs:
npx tsx scripts/get-positions.ts 0xYourAddress - Shows open positions with current P&L
"I have $50 and I'm bearish on ETH, recommend a strategy"
Agent's Decision Process:
- WebSearch for market news → confirms bearish sentiment
- User has small collateral ($50) → Consider spreads for efficiency
- User is bearish → PUT direction
- Ask risk level → user says "medium"
- Recommendation: PUT_SPREAD (MEDIUM RISK)
Agent:
Market Context:
• ETH down 3% on regulatory concerns → BEARISH
• Overall market sentiment weak → confirms bearish view
With $50 and a bearish view at MEDIUM risk, I recommend a PUT SPREAD:
┌────────────────────────────────────────────────────┐
│ Strategy: PUT_SPREAD │
│ Risk: MEDIUM │
│ Direction: BEARISH │
├────────────────────────────────────────────────────┤
│ Max Loss: $50 (your full collateral) │
│ Max Gain: $50 (spread width - premium) │
│ Break-even: ~$1892 │
│ Win Zone: ETH below $1900 at expiry │
│ Collateral: $50 USDC -> 0.5 contracts │
│ (20x more than vanilla PUT at 0.026) │
└────────────────────────────────────────────────────┘
Available strikes for tomorrow's expiry:
• PUT_SPREAD $1950/$1850 - Bid: $12/contract
• PUT_SPREAD $1900/$1800 - Bid: $8/contract
Which spread would you like?
Want HIGH risk instead? Vanilla PUT gives unlimited profit but only 0.026 contracts.
Want LOW risk instead? Put Butterfly costs ~$3 with $47 max gain.
"I think ETH will stay around $2000 this week"
Agent's Decision Process:
- WebSearch for news → no major catalysts, range-bound expected
- User expects neutral/range-bound market
- Ask risk level → user says "low"
- Check collateral: $30 → butterfly is most efficient
- Recommendation: CALL_FLY (LOW RISK)
Agent:
Market Context:
• No major catalysts this week → NEUTRAL
• Options expiry Friday with large OI at $2000 → pin risk → NEUTRAL
With $30 and a neutral view, here's my LOW RISK recommendation:
┌────────────────────────────────────────────────────┐
│ Strategy: CALL_FLY (Butterfly) │
│ Risk: LOW │
│ Direction: NEUTRAL (target $2,050) │
├────────────────────────────────────────────────────┤
│ Strikes: 2000 / 2050 / 2100 │
│ Max Loss: ~$5/contract (premium paid) │
│ Max Gain: ~$45/contract (at exactly $2050) │
│ Break-even: $2005 - $2095 │
│ Collateral: $30 -> ~6 contracts │
└────────────────────────────────────────────────────┘
Why butterfly (LOW risk)?
• Cheapest way to play range-bound (more contracts per dollar)
• Max loss is tiny (~$5/contract)
• If ETH pins near $2050, each contract pays ~$45
Want MEDIUM risk? A Condor ($100+) gives a wider profit zone.
Build this RFQ? (y/n)
"I want to sell premium and collect income"
Agent's Decision Process:
- WebSearch for news → range-bound market expected
- User wants to sell premium → Income strategy
- Risk level: LOW (income/premium selling = defined risk)
- If neutral → IRON_CONDOR
Agent:
Market Context:
• Market consolidating after rally → NEUTRAL
• No major events this week → supports range-bound play
For premium selling, I recommend an IRON CONDOR (LOW RISK):
┌────────────────────────────────────────────────────┐
│ Strategy: IRON_CONDOR (sell) │
│ Risk: LOW │
│ Direction: NEUTRAL (profit if range-bound) │
├────────────────────────────────────────────────────┤
│ Strikes: $1800 / $1900 / $2100 / $2200 │
│ Max Loss: $85/contract (if ETH exits range) │
│ Max Gain: $15/contract (premium collected) │
│ Win Zone: ETH stays between $1900-$2100 │
│ Current: ETH $2,000 (right in the middle!) │
│ Collateral: ~$100 USDC per contract │
└────────────────────────────────────────────────────┘
This strategy profits ~60-70% of the time in range-bound markets.
You collect $15 premium and risk $85 if wrong.
Is ETH likely to stay between $1900-$2100? Let me build this RFQ.
Output Formatting & UX Guidelines
Progressive Disclosure
Tailor detail level to the user's experience:
| User Type | How to Detect | What to Show |
|---|---|---|
| New user | First session, no wallet, asks basic questions | Simplified info, no SDK details, guide step-by-step |
| Returning user | Has wallet, has traded before | Relevant data for their question, skip basics |
| Advanced user | Asks about multi-strike, SDK, specific params | Full technical detail, contract addresses, raw data |
Formatting Standards
ASCII Table Alignment Rules (CRITICAL):
- The
┌───┐top border,├───┤divider, and└───┘bottom border MUST all be the exact same width - Every content line
│ ... │MUST have the right│aligned exactly under the┐corner - Count characters carefully: if the border is 54
─chars wide (56 total with┌┐), every content line must be exactly 54 chars between the│borders - Pad content lines with spaces on the right so the closing
│always aligns - If content is too long for the box, shorten it or wrap to a new line (indented, still inside the
│borders) - Use light box-drawing only:
┌ ┐ └ ┘ ─ │ ├ ┤ ┼ ┬ ┴(never heavy/double:═ ║ ╔)
For strategy recommendations — Always include the Risk Summary Card (see "Risk-Categorized Strategy Recommendations" section for the full template with all fields: Strategy, Risk Level, Direction, Max Loss, Max Gain, Break-even, Win Zone, Collateral).
For price/market data — Use compact tables:
ETH Options (PUT) — Current Price: $2,050
┌────────────┬─────────┬────────┬────────┐
│ Strike │ Expiry │ Bid │ Ask │
├────────────┼─────────┼────────┼────────┤
│ $1,900 │ Apr 25 │ $8.50 │ $12.00 │
│ $2,000 │ Apr 25 │ $25.00 │ $30.00 │
└────────────┴─────────┴────────┴────────┘
For trade confirmations — Clear summary before execution:
Trade Summary
═════════════
Action: Buy PUT_SPREAD
Risk Level: MEDIUM
Strikes: $1900 / $1800
Expiry: Apr 25, 2026
Contracts: 1.0
Cost: ~$15 USDC
Max Loss: $15 | Max Gain: $85
Confirm? (yes/no)
For errors — Never show raw JSON to the user:
- Translate error codes to plain English
- Always suggest a corrective action
- Example: Instead of
{"error": "INSUFFICIENT_ALLOWANCE"}→ "You need to approve USDC spending first. Want me to do that?"
MCP Tool Usage for Enhanced UX
| Tool | When to Use | Example |
|---|---|---|
| WebSearch | Before every strategy recommendation (fetch market news) | "crypto market news today" |
| WebFetch | Drill into a specific article or data source from WebSearch results | Fetch full article content |
| context7 | When user asks about SDK features or encounters an unfamiliar error | Resolve thetanuts-finance/thetanuts-client, then query docs |
Context7 for SDK Documentation
When a user asks a technical question about the Thetanuts SDK:
- Use context7
resolve-library-idto findthetanuts-finance/thetanuts-client - Use context7
query-docswith the specific topic (e.g., "fillOrder", "WebSocket", "error handling") - Provide accurate, up-to-date answers from official documentation
Commands Reference
Wallet Commands
| Command | Description |
|---|---|
node scripts/wallet-discover.js | Check if wallet configured, show addresses |
node scripts/wallet-create.js | Generate new wallet |
node scripts/wallet-import.js --seed-file /path | Import existing seed |
node scripts/wallet-balance.js --chain base-mainnet | Check balance |
node scripts/wallet-select.js --family evm --chain base-mainnet | Set active chain |
Market Data Commands
| Command | Description |
|---|---|
npx tsx scripts/get-prices.ts | Get BTC/ETH prices |
npx tsx scripts/get-mm-pricing.ts ETH --type PUT | Get MM option quotes |
npx tsx scripts/fetch-orders.ts --type PUT | Fetch orderbook |
npx tsx scripts/check-orderbook.ts --underlying ETH --type PUT --strike 1900 --expiry <ts> --direction sell | Check orderbook liquidity (run FIRST before trading) |
Trading Commands
| Command | Description |
|---|---|
npx tsx scripts/fill-order.ts --order-index 0 --collateral 10 --seed "..." --execute | Fill orderbook order |
npx tsx scripts/build-rfq.ts --underlying ETH --type PUT --strike 1900 --expiry <ts> --contracts 0.1 --direction buy | Build vanilla RFQ (45s deadline) |
npx tsx scripts/build-rfq.ts --underlying ETH --type CALL --strikes 2000,2100 --expiry <ts> --contracts 1 --direction buy | Build multi-strike RFQ (spread/fly/condor) |
npx tsx scripts/approve-token.ts --token <addr> --spender <addr> --max --seed "..." | Approve tokens |
npx tsx scripts/send-transaction.ts --to <addr> --data <hex> --seed "..." | Send transaction |
npx tsx scripts/check-rfq-fill.ts --address <wallet> --ticker <expected> --since <ts> | Verify RFQ fill status |
Position Commands
| Command | Description |
|---|---|
npx tsx scripts/get-positions.ts <address> | Get open positions |
npx tsx scripts/calculate-payout.ts --type PUT --strike 1900 --settlement 1800 --contracts 1 | Calculate payoff |
Contract Addresses (Base Mainnet)
Core Contracts
| Contract | Address |
|---|---|
| OptionBook | 0xd58b814C7Ce700f251722b5555e25aE0fa8169A1 |
| OptionFactory | 0x1aDcD391CF15Fb699Ed29B1D394F4A64106886e5 |
Tokens
| Token | Address | Decimals |
|---|---|---|
| USDC | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | 6 |
| WETH | 0x4200000000000000000000000000000000000006 | 18 |
| cbBTC | 0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf | 8 |
| cbDOGE | 0x73c7A9C372F31c1b1C7f8E5A7D12B8735c817C79 | 8 |
| cbXRP | 0x7B2Cd9EA5566c345C9cdbcF58f5E211a0dB47444 | 6 |
| aBasWETH | 0xD4a0e0b9149BCee3C920d2E00b5dE09138fd8bb7 | 18 |
| aBascbBTC | 0xBdb9300b7CDE636d9cD4AFF00f6F009fFBBc8EE6 | 8 |
| aBasUSDC | 0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB | 6 |
Option Implementations
| Type | Address |
|---|---|
| PUT | 0xF480F636301d50Ed570D026254dC5728b746A90F |
| INVERSE_CALL | 0x3CeB524cBA83D2D4579F5a9F8C0D1f5701dd16FE |
| CALL_SPREAD | 0x4D75654bC616F64F6010d512C3B277891FB52540 |
| PUT_SPREAD | 0xC9767F9a2f1eADC7Fdcb7f0057E829D9d760E086 |
| CALL_FLY | 0xD8EA785ab2A63a8a94C38f42932a54A3E45501c3 |
| PUT_FLY | 0x1fE24872Ab7c83BbA26Dc761ce2EA735c9b96175 |
| CALL_CONDOR | 0xbb5d2EB2D354D930899DaBad01e032C76CC3c28f |
| PUT_CONDOR | 0xbdAcC00Dc3F6e1928D9380c17684344e947aa3Ec |
| IRON_CONDOR | 0x494Cd61b866D076c45564e236D6Cb9e011a72978 |
| PHYSICAL_CALL | 0x07032ffb1df85eC006Be7c76249B9e6f39b60F32 |
| PHYSICAL_PUT | 0xAC5eCA7129909dE8c12e1a41102414B5a5f340AA |
Price Feeds (Chainlink)
| Asset | Address |
|---|---|
| ETH/USD | 0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70 |
| BTC/USD | 0x64c911996D3c6aC71f9b455B1E8E7266BcbD848F |
| SOL | 0x975043adBb80fc32276CbF9Bbcfd4A601a12462D |
| DOGE | 0x8422f3d3CAFf15Ca682939310d6A5e619AE08e57 |
| XRP | 0x9f0C1dD78C4CBdF5b9cf923a549A201EdC676D34 |
Updates
Updates are checked automatically at the start of every conversation (see First-Time Setup). The agent runs update.sh silently and only notifies the user if a new version is installed.
To manually check or force dependency updates:
bash scripts/update.sh
Optional flags:
REFRESH_WDK_DEPS=1- Refresh dependencies from lockfileUPGRADE_WDK_DEPS=1- Upgrade dependency versionsRESTART_WDK_RUNTIME=1- Best-effort restart of WDK runtime
Updates NEVER modify wallet secrets (.env, WDK_SEED).
Ticker Format
Options use: {UNDERLYING}-{EXPIRY}-{STRIKE}-{TYPE}
Examples:
ETH-28MAR26-2500-P= ETH Put, $2500 strike, March 28 2026 expiryBTC-28MAR26-95000-C= BTC Call, $95000 strike, March 28 2026 expiry
Network
- Chain: Base Mainnet (Chain ID 8453)
- Collateral: USDC (6 decimals), WETH (18 decimals), cbBTC (8 decimals)
- Strikes: 8 decimals internally
- Expiry: 8:00 UTC on expiry date