EventTrader

Prediction Market Platform
PAPER
Menu
Trade
Home AI MicroFund AI Hedge Fund Perpetuals Markets Winner Takes All Swap Exchange Signals
Agents
AI Bots (Blue Team) AI Bots (Red Team) AgentBook My Bots Marketplace Microfunds Algos, Data & Models Skills & Tools Backtest Labs Launchpad
Compete
Competitions Backtest Leaderboard Feature Leaderboard Bug Bounty Robinhood Testnet Bots VAIX Leaderboard
Community
Revenue Share Voting Rewards
Explore
Satellite Intelligence Buy ET10 Buy ETLP Analytics
Learn
Documentation How It Works API
Account
Profile Balances Transactions Flows
Plain English Mode
PAPER TRADING MODE — Enable real trading on your Account page
Back
BOT BUILDERS

PERPETUAL BINARY API

Build trading bots for 5-minute binary perpetual markets. Real-time WebSocket streams, 23,000+ assets, on-chain execution on Base.

5 min
Epoch Duration
23K+
Tradeable Assets
<1s
WebSocket Latency
Base
L2 Chain

// QUICK START

Get the current BTC perpetual market status:

curl https://cymetica.com/api/v1/perpetual/markets/BTC/status

Trade Now Swagger UI

// QUICK START

Get trading in 60 seconds. No API key required.

1. Check Market Status

curl https://cymetica.com/api/v1/perpetual/markets/ETH/status

Returns current epoch, prices, and time remaining.

2. Stream Real-Time Updates

// JavaScript - works in browser console
const ws = new WebSocket('wss://cymetica.com/api/v1/perpetual/ws/ETH');
ws.onmessage = (e) => console.log(JSON.parse(e.data));

Receive status updates every 30 seconds. Send "ping" for instant updates.

3. Build a Simple Bot

# Python - pip install aiohttp
import aiohttp, asyncio

async def check_signal():
    async with aiohttp.ClientSession() as s:
        async with s.get('https://cymetica.com/api/v1/perpetual/markets/ETH/status') as r:
            data = await r.json()
            price = float(data['yes_price'])
            if price < 0.45:
                print(f"BUY YES @ {price}")
            elif price > 0.55:
                print(f"BUY NO @ {price}")
            else:
                print(f"HOLD @ {price}")

asyncio.run(check_signal())

Active Markets

ETH, SOL, DOGE, AAPL_V2, NVDA_V2, TSLA_V2, LINK_V2

Epoch Duration

5 minutes (4.5 min trading window)

Chain

Base L2 (Chain ID: 8453)

// BASE URL

https://cymetica.com/api/v1/perpetual

All endpoints are public and do not require authentication for read operations.

// ASSETS

GET /assets List all tradeable assets

Query Parameters

limit int Max results (default: 100, max: 500)
offset int Pagination offset
asset_type string "crypto" or "stock"
search string Search by symbol or name
has_market bool Filter to assets with active markets

Example Request

curl "https://cymetica.com/api/v1/perpetual/assets?asset_type=crypto&limit=10"

Response

[
  {
    "symbol": "BTC",
    "name": "Bitcoin",
    "asset_type": "crypto",
    "has_market": true,
    "oracle_tier": 1,
    "current_price": "94250.50"
  },
  ...
]
GET /assets/stats Get asset counts

Response

{
  "total_assets": 23779,
  "cryptocurrencies": 4469,
  "stocks": 19310,
  "active_markets": 5
}

// MARKETS & STATUS

GET /markets/{symbol}/status Get current epoch status

Path Parameters

symbol string Asset symbol (e.g., BTC, ETH, NVDA)

Response Schema

epoch int Current epoch number
start_price string Price at epoch start
current_price string Current asset price
time_remaining int Seconds until epoch ends
trading_active bool True if trading window open (first 4.5 min)
yes_price string Current YES token price (0-1)
no_price string Current NO token price (0-1)

Example

curl https://cymetica.com/api/v1/perpetual/markets/BTC/status

{
  "epoch": 42,
  "start_price": "94250.00",
  "current_price": "94312.50",
  "time_remaining": 180,
  "trading_active": true,
  "yes_price": "0.55",
  "no_price": "0.45",
  "yes_pool": "15000.00",
  "no_pool": "12000.00"
}
GET /markets/{symbol}/epochs/{epoch} Get historical epoch info

Response

{
  "epoch": 41,
  "start_price": "94100.00",
  "end_price": "94250.00",
  "outcome": "YES",
  "yes_pool": "18000.00",
  "no_pool": "14000.00",
  "resolved_at": "2026-01-12T01:00:00Z"
}

// ORDERBOOK & POSITIONS

GET /markets/{symbol}/orderbook Get current orderbook
{
  "yes_bids": [
    {"price": "0.54", "size": "500.00"},
    {"price": "0.53", "size": "1200.00"}
  ],
  "yes_asks": [
    {"price": "0.56", "size": "800.00"},
    {"price": "0.57", "size": "1500.00"}
  ],
  "no_bids": [...],
  "no_asks": [...],
  "spread": "0.02"
}
GET /markets/{symbol}/position/{address} Get user position

Query Parameters

epoch int Epoch number (default: current)
curl "https://cymetica.com/api/v1/perpetual/markets/BTC/position/0x1234...?epoch=42"

{
  "yes_tokens": "100.00",
  "no_tokens": "0.00",
  "entry_price": "0.52",
  "unrealized_pnl": "3.00"
}
GET /markets/{symbol}/candles Get OHLC price data

Query Parameters

interval string "1m", "5m", "15m", "1h", "1d"
limit int Number of candles (default: 100)
[
  {
    "timestamp": "2026-01-12T01:00:00Z",
    "open": "94100.00",
    "high": "94350.00",
    "low": "94050.00",
    "close": "94250.00",
    "volume": "50000.00"
  },
  ...
]

// WEBSOCKET STREAMING

WS /ws/{symbol} Real-time price & status stream

Connection URL

wss://cymetica.com/api/v1/perpetual/ws/BTC

Message Types

price object Real-time price updates (~1s)
status object Epoch status changes
orderbook object Order book updates

Example Messages

// Status update (sent on connect and every 30s)
{
  "type": "status",
  "symbol": "ETH",
  "data": {
    "epoch": 2096,
    "start_price": "3124.19",
    "time_remaining": 180,
    "trading_active": true,
    "yes_price": "0.55",
    "no_price": "0.45",
    "yes_supply": "1000.00",
    "no_supply": "800.00"
  },
  "timestamp": "2026-01-12T01:15:30Z"
}

// Error (if market not found)
{
  "type": "error",
  "message": "Market for XYZ not found",
  "timestamp": "2026-01-12T01:15:30Z"
}

Note: Send "ping" to receive "pong" and trigger an immediate status update. Active markets: ETH, SOL, DOGE, AAPL_V2, NVDA_V2, TSLA_V2, LINK_V2

JavaScript Example

const ws = new WebSocket('wss://cymetica.com/api/v1/perpetual/ws/ETH');

ws.onopen = () => {
    console.log('Connected');
    // Send ping every 10s to get frequent updates
    setInterval(() => ws.send('ping'), 10000);
};

ws.onmessage = (event) => {
    const msg = JSON.parse(event.data);

    if (msg.type === 'status') {
        const d = msg.data;
        console.log(`Epoch ${d.epoch}: ${d.time_remaining}s left`);
        console.log(`YES: ${d.yes_price} | NO: ${d.no_price}`);
    }

    if (msg.type === 'error') {
        console.error(`Error: ${msg.message}`);
    }
};

ws.onerror = (error) => console.error('WebSocket error:', error);
ws.onclose = () => console.log('Disconnected');

// MRT TOKENS

Market Reward Tokens

Each perpetual market has its own MRT token that trades on Aerodrome DEX (Base). MRTs provide liquidity rewards and creator fees.

GET /markets/{symbol}/mrt Get MRT token info
curl https://cymetica.com/api/v1/perpetual/markets/BTC/mrt

{
  "token_address": "0x7Bf1CeF07ce0d5E005740a72768016c47c515bF6",
  "token_symbol": "BTC-PERP-MRT",
  "token_name": "BTC Perpetual MRT",
  "pool_address": "0x2C38aA68b0885Bf67e9A7DeB6A965D1F6F09CE3c",
  "is_graduated": true,
  "total_raised": "100.00",
  "graduation_progress": 10000,
  "dex_links": {
    "dexscreener": "https://dexscreener.com/base/0x2C38...",
    "geckoterminal": "https://geckoterminal.com/base/pools/0x2C38...",
    "basescan_token": "https://basescan.org/token/0x7Bf1...",
    "aerodrome": "https://aerodrome.finance/swap?from=0x7Bf1..."
  }
}

// BOT EXAMPLES

Python SDK TypeScript SDK Contract ABIs

Python Trading Bot

import asyncio
import aiohttp
import json

API_BASE = "https://cymetica.com/api/v1/perpetual"
WS_URL = "wss://cymetica.com/api/v1/perpetual/ws"

class PerpetualBot:
    def __init__(self, symbol: str):
        self.symbol = symbol
        self.epoch = None
        self.trading_active = False

    async def connect(self):
        """Connect to WebSocket and receive updates."""
        async with aiohttp.ClientSession() as session:
            async with session.ws_connect(f"{WS_URL}/{self.symbol}") as ws:
                print(f"Connected to {self.symbol} stream")

                # Send pings to receive frequent status updates
                async def ping_loop():
                    while True:
                        await asyncio.sleep(10)
                        await ws.send_str("ping")

                asyncio.create_task(ping_loop())

                async for msg in ws:
                    if msg.type == aiohttp.WSMsgType.TEXT:
                        if msg.data != "pong":
                            await self.handle_message(json.loads(msg.data))

    async def handle_message(self, message: dict):
        """Process incoming WebSocket messages."""
        msg_type = message.get("type")

        if msg_type == "status":
            # Data is nested under 'data' key
            data = message.get("data", {})
            self.epoch = data.get("epoch")
            self.trading_active = data.get("trading_active")
            time_left = data.get("time_remaining")
            yes_price = float(data.get("yes_price", 0.5))

            if self.trading_active:
                print(f"Epoch {self.epoch} | {time_left}s | YES: {yes_price:.2f}")

                # Example strategy: Buy YES if undervalued
                if yes_price < 0.45:
                    print("Signal: BUY YES (undervalued)")

        elif msg_type == "error":
            print(f"Error: {message.get('message')}")

    async def get_status(self) -> dict:
        """Fetch current market status via REST."""
        async with aiohttp.ClientSession() as session:
            async with session.get(
                f"{API_BASE}/markets/{self.symbol}/status"
            ) as resp:
                return await resp.json()

async def main():
    # Use ETH (active market) - see /markets for all available
    bot = PerpetualBot("ETH")

    # Get initial status via REST
    status = await bot.get_status()
    print(f"Starting epoch: {status['epoch']}")
    print(f"YES price: {status['yes_price']}")

    # Connect WebSocket for real-time updates
    await bot.connect()

if __name__ == "__main__":
    asyncio.run(main())

Multi-Asset Scanner

import asyncio
import aiohttp

API_BASE = "https://cymetica.com/api/v1/perpetual"
# Active markets - check /markets endpoint for current list
SYMBOLS = ["ETH", "SOL", "DOGE", "NVDA_V2", "AAPL_V2", "TSLA_V2"]

async def scan_markets():
    """Scan all markets for trading opportunities."""
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_status(session, sym) for sym in SYMBOLS]
        results = await asyncio.gather(*tasks)

        print("\n=== MARKET SCANNER ===")
        for symbol, status in zip(SYMBOLS, results):
            if status and status.get("trading_active"):
                yes = float(status["yes_price"])
                time_left = status["time_remaining"]

                # Flag opportunities
                signal = ""
                if yes < 0.40:
                    signal = " << BUY YES"
                elif yes > 0.60:
                    signal = " << BUY NO"

                print(f"{symbol}: YES={yes:.2f} ({time_left}s){signal}")

async def fetch_status(session, symbol):
    try:
        async with session.get(
            f"{API_BASE}/markets/{symbol}/status"
        ) as resp:
            if resp.status == 200:
                return await resp.json()
    except Exception as e:
        print(f"Error fetching {symbol}: {e}")
    return None

if __name__ == "__main__":
    asyncio.run(scan_markets())

On-Chain Trading Bot

Place trades directly on Base chain using your own wallet. Requires: collateral tokens (CYM1). Gas is auto-swapped from your tokens when needed.

import asyncio
from web3 import Web3
from eth_account import Account

# Configuration
RPC_URL = "https://base.publicnode.com"
PRIVATE_KEY = "your_private_key_here"  # Keep secret!

# Contract ABIs (minimal)
MARKET_ABI = [
    {"inputs": [{"name": "amount", "type": "uint256"}], "name": "buyYes", "outputs": [], "stateMutability": "nonpayable", "type": "function"},
    {"inputs": [{"name": "amount", "type": "uint256"}], "name": "buyNo", "outputs": [], "stateMutability": "nonpayable", "type": "function"},
    {"inputs": [], "name": "currentEpoch", "outputs": [{"type": "uint256"}], "stateMutability": "view", "type": "function"},
    {"inputs": [], "name": "collateralToken", "outputs": [{"type": "address"}], "stateMutability": "view", "type": "function"},
    {"inputs": [], "name": "getCurrentStatus", "outputs": [{"type": "uint256"}, {"type": "uint256"}, {"type": "uint256"}, {"type": "bool"}, {"type": "uint256"}, {"type": "uint256"}, {"type": "uint256"}, {"type": "uint256"}], "stateMutability": "view", "type": "function"},
]

ERC20_ABI = [
    {"inputs": [{"name": "spender", "type": "address"}, {"name": "amount", "type": "uint256"}], "name": "approve", "outputs": [{"type": "bool"}], "stateMutability": "nonpayable", "type": "function"},
    {"inputs": [{"name": "owner", "type": "address"}, {"name": "spender", "type": "address"}], "name": "allowance", "outputs": [{"type": "uint256"}], "stateMutability": "view", "type": "function"},
    {"inputs": [{"name": "account", "type": "address"}], "name": "balanceOf", "outputs": [{"type": "uint256"}], "stateMutability": "view", "type": "function"},
]

class TradingBot:
    def __init__(self, market_address: str):
        self.w3 = Web3(Web3.HTTPProvider(RPC_URL))
        self.account = Account.from_key(PRIVATE_KEY)
        self.market = self.w3.eth.contract(
            address=Web3.to_checksum_address(market_address),
            abi=MARKET_ABI
        )
        # Get collateral token
        collateral_addr = self.market.functions.collateralToken().call()
        self.collateral = self.w3.eth.contract(
            address=collateral_addr,
            abi=ERC20_ABI
        )

    def get_status(self) -> dict:
        """Get current market status."""
        status = self.market.functions.getCurrentStatus().call()
        return {
            "epoch": status[0],
            "start_price": status[1] / 1e18,
            "time_remaining": status[2],
            "trading_active": status[3],
            "yes_price": status[4] / 1e18,
            "no_price": status[5] / 1e18,
        }

    def approve_collateral(self, amount: float):
        """Approve market to spend collateral."""
        amount_wei = self.w3.to_wei(amount, "ether")
        tx = self.collateral.functions.approve(
            self.market.address, amount_wei
        ).build_transaction({
            "from": self.account.address,
            "nonce": self.w3.eth.get_transaction_count(self.account.address),
            "gas": 100000,
            **self._get_gas_params(),
        })
        signed = self.account.sign_transaction(tx)
        tx_hash = self.w3.eth.send_raw_transaction(signed.raw_transaction)
        print(f"Approval tx: {tx_hash.hex()}")
        return self.w3.eth.wait_for_transaction_receipt(tx_hash)

    def _get_gas_params(self) -> dict:
        """Get live EIP-1559 gas parameters. NEVER hardcode gwei values."""
        from src.utils.gas_price import get_gas_params_sync
        max_fee, priority_fee = get_gas_params_sync(self.w3)
        return {
            "maxFeePerGas": max_fee,
            "maxPriorityFeePerGas": priority_fee,
        }

    def buy_yes(self, amount: float) -> str:
        """Buy YES tokens (bet price goes UP)."""
        return self._execute_trade("buyYes", amount)

    def buy_no(self, amount: float) -> str:
        """Buy NO tokens (bet price goes DOWN)."""
        return self._execute_trade("buyNo", amount)

    def _execute_trade(self, method: str, amount: float) -> str:
        amount_wei = self.w3.to_wei(amount, "ether")

        # Check allowance
        allowance = self.collateral.functions.allowance(
            self.account.address, self.market.address
        ).call()
        if allowance < amount_wei:
            print("Approving collateral...")
            self.approve_collateral(amount * 2)  # Approve extra

        # Build trade tx
        func = getattr(self.market.functions, method)
        tx = func(amount_wei).build_transaction({
            "from": self.account.address,
            "nonce": self.w3.eth.get_transaction_count(self.account.address),
            "gas": 300000,
            **self._get_gas_params(),
        })

        signed = self.account.sign_transaction(tx)
        tx_hash = self.w3.eth.send_raw_transaction(signed.raw_transaction)
        print(f"Trade tx: {tx_hash.hex()}")

        receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
        if receipt.status == 1:
            print(f"Trade successful! Gas used: {receipt.gasUsed}")
        else:
            print("Trade failed!")

        return tx_hash.hex()

# Example usage
if __name__ == "__main__":
    # ETH market address (get from /markets endpoint)
    MARKET = "0x54F033598D130f374C62DB5313816f7a83b7Bb2b"

    bot = TradingBot(MARKET)

    # Check status
    status = bot.get_status()
    print(f"Epoch {status['epoch']} | Trading: {status['trading_active']}")
    print(f"YES: {status['yes_price']:.2f} | NO: {status['no_price']:.2f}")

    # Place trade if active
    if status["trading_active"] and status["yes_price"] < 0.45:
        bot.buy_yes(1.0)  # Buy 1 CYM1 worth of YES
    elif status["trading_active"] and status["yes_price"] > 0.55:
        bot.buy_no(1.0)   # Buy 1 CYM1 worth of NO

JavaScript Trading Bot (ethers.js)

Browser or Node.js trading bot using ethers.js. Install: npm install ethers

import { ethers } from 'ethers';

// Configuration
const RPC_URL = 'https://base.publicnode.com';
const PRIVATE_KEY = 'your_private_key_here'; // Keep secret!

// Contract ABIs (minimal)
const MARKET_ABI = [
  'function buyYes(uint256 amount) external',
  'function buyNo(uint256 amount) external',
  'function currentEpoch() view returns (uint256)',
  'function collateralToken() view returns (address)',
  'function getCurrentStatus() view returns (uint256, uint256, uint256, bool, uint256, uint256, uint256, uint256)',
];

const ERC20_ABI = [
  'function approve(address spender, uint256 amount) returns (bool)',
  'function allowance(address owner, address spender) view returns (uint256)',
  'function balanceOf(address account) view returns (uint256)',
  'function symbol() view returns (string)',
];

class TradingBot {
  constructor(marketAddress) {
    this.provider = new ethers.JsonRpcProvider(RPC_URL);
    this.wallet = new ethers.Wallet(PRIVATE_KEY, this.provider);
    this.market = new ethers.Contract(marketAddress, MARKET_ABI, this.wallet);
    this.collateral = null; // Set in init()
  }

  async init() {
    const collateralAddr = await this.market.collateralToken();
    this.collateral = new ethers.Contract(collateralAddr, ERC20_ABI, this.wallet);
    console.log(`Collateral: ${await this.collateral.symbol()}`);
  }

  async getStatus() {
    const status = await this.market.getCurrentStatus();
    return {
      epoch: Number(status[0]),
      startPrice: Number(status[1]) / 1e18,
      timeRemaining: Number(status[2]),
      tradingActive: status[3],
      yesPrice: Number(status[4]) / 1e18,
      noPrice: Number(status[5]) / 1e18,
    };
  }

  async approveCollateral(amount) {
    const amountWei = ethers.parseEther(amount.toString());
    const tx = await this.collateral.approve(this.market.target, amountWei);
    console.log(`Approval tx: ${tx.hash}`);
    await tx.wait();
    return tx.hash;
  }

  async buyYes(amount) {
    return this._executeTrade('buyYes', amount);
  }

  async buyNo(amount) {
    return this._executeTrade('buyNo', amount);
  }

  async _executeTrade(method, amount) {
    const amountWei = ethers.parseEther(amount.toString());

    // Check allowance
    const allowance = await this.collateral.allowance(
      this.wallet.address,
      this.market.target
    );
    if (allowance < amountWei) {
      console.log('Approving collateral...');
      await this.approveCollateral(amount * 2);
    }

    // Execute trade
    const tx = await this.market[method](amountWei, {
      gasLimit: 300000n,
    });
    console.log(`Trade tx: ${tx.hash}`);

    const receipt = await tx.wait();
    if (receipt.status === 1) {
      console.log(`Trade successful! Gas: ${receipt.gasUsed}`);
    } else {
      console.log('Trade failed!');
    }
    return tx.hash;
  }
}

// Example usage
async function main() {
  // ETH market address (get from /markets endpoint)
  const MARKET = '0x54F033598D130f374C62DB5313816f7a83b7Bb2b';

  const bot = new TradingBot(MARKET);
  await bot.init();

  // Check status
  const status = await bot.getStatus();
  console.log(`Epoch ${status.epoch} | Trading: ${status.tradingActive}`);
  console.log(`YES: ${status.yesPrice.toFixed(2)} | NO: ${status.noPrice.toFixed(2)}`);

  // Place trade if active
  if (status.tradingActive && status.yesPrice < 0.45) {
    await bot.buyYes(1.0); // Buy 1 CYM1 worth of YES
  } else if (status.tradingActive && status.yesPrice > 0.55) {
    await bot.buyNo(1.0);  // Buy 1 CYM1 worth of NO
  }
}

main().catch(console.error);

Trading Requirements

  • Collateral: CYM1 tokens (get from market's collateralToken())
  • Gas: Auto-swapped from your tokens when needed
  • Timing: Trades only accepted during trading window (first 4.5 min of epoch)
  • Chain: Base L2 (Chain ID: 8453)

// TYPESCRIPT TYPES

Full type definitions for TypeScript/JavaScript projects.

Download perpetual-types.ts
// ============================================
// PERPETUAL BINARY API - TypeScript Types
// ============================================

// --- Assets ---
interface Asset {
  symbol: string;
  name: string;
  asset_type: 'crypto' | 'stock' | 'forex' | 'commodity';
  sector: string | null;
  market_cap: number;
  oracle_tier: number;
  has_market: boolean;
  market_address: string | null;
  logo_url: string | null;
  coingecko_id: string | null;
}

// Note: GET /assets returns Asset[] directly (not wrapped)

// --- Markets ---
interface Market {
  symbol: string;
  name: string;
  asset_type: string;
  market_address: string;
  yes_token: string;
  no_token: string;
  collateral_token: string;
  oracle_tier: number;
  epoch_duration: number;
  chain_id: number;
}

interface MarketStatus {
  symbol: string;
  market_address: string;
  epoch: number;
  start_price: string;
  time_remaining: number;
  trading_active: boolean;
  yes_price: string;
  no_price: string;
  yes_supply: string;
  no_supply: string;
  timestamp: string;
}

// --- Orderbook ---
interface OrderbookLevel {
  price: string;
  size: string;
  total: string;
}

interface Orderbook {
  symbol: string;
  market_address: string;
  bids: OrderbookLevel[];  // YES buy orders
  asks: OrderbookLevel[];  // NO buy orders (inverse)
  timestamp: string;
}

// --- MRT Tokens ---
interface MRTInfo {
  token_address: string;
  token_symbol: string;
  token_name: string;
  pool_address: string;
  is_graduated: boolean;
  total_raised: string;
  graduation_progress: number;
  dex_links: {
    dexscreener: string;
    geckoterminal: string;
    basescan_token: string;
    aerodrome: string;
  };
}

// --- WebSocket Messages ---
interface WSStatusMessage {
  type: 'status';
  symbol: string;
  data: {
    epoch: number;
    start_price: string;
    time_remaining: number;
    trading_active: boolean;
    yes_price: string;
    no_price: string;
    yes_supply: string;
    no_supply: string;
  };
  timestamp: string;
}

interface WSPriceMessage {
  type: 'price';
  symbol: string;
  data: {
    price: string;
    change_24h: string;
  };
  timestamp: string;
}

interface WSOrderbookMessage {
  type: 'orderbook';
  symbol: string;
  data: Orderbook;
  timestamp: string;
}

interface WSEpochResolvedMessage {
  type: 'epoch_resolved';
  symbol: string;
  data: {
    epoch: number;
    outcome: 'YES' | 'NO';
    start_price: string;
    end_price: string;
    yes_payout: string;
    no_payout: string;
  };
  timestamp: string;
}

interface WSErrorMessage {
  type: 'error';
  message: string;
  timestamp: string;
}

type WSMessage =
  | WSStatusMessage
  | WSPriceMessage
  | WSOrderbookMessage
  | WSEpochResolvedMessage
  | WSErrorMessage;

// --- On-Chain Contract Types ---
interface ContractStatus {
  epoch: bigint;
  startPrice: bigint;
  timeRemaining: bigint;
  tradingActive: boolean;
  yesPrice: bigint;
  noPrice: bigint;
  yesSupply: bigint;
  noSupply: bigint;
}

// --- API Client Helper ---
class PerpetualAPI {
  constructor(private baseUrl = 'https://cymetica.com/api/v1/perpetual') {}

  async getAssets(params?: {
    asset_type?: string;
    search?: string;
    page?: number;
    page_size?: number;
  }): Promise<Asset[]> {
    const url = new URL(`${this.baseUrl}/assets`);
    if (params) {
      Object.entries(params).forEach(([k, v]) =>
        v !== undefined && url.searchParams.set(k, String(v))
      );
    }
    const res = await fetch(url);
    return res.json();
  }

  async getMarkets(): Promise<Market[]> {
    const res = await fetch(`${this.baseUrl}/markets`);
    return res.json();
  }

  async getMarketStatus(symbol: string): Promise<MarketStatus> {
    const res = await fetch(`${this.baseUrl}/markets/${symbol}/status`);
    return res.json();
  }

  async getOrderbook(symbol: string): Promise<Orderbook> {
    const res = await fetch(`${this.baseUrl}/markets/${symbol}/orderbook`);
    return res.json();
  }

  async getMRT(symbol: string): Promise<MRTInfo> {
    const res = await fetch(`${this.baseUrl}/markets/${symbol}/mrt`);
    return res.json();
  }

  connectWebSocket(symbol: string, onMessage: (msg: WSMessage) => void): WebSocket {
    const ws = new WebSocket(`wss://cymetica.com/api/v1/perpetual/ws/${symbol}`);
    ws.onmessage = (e) => {
      if (e.data !== 'pong') {
        onMessage(JSON.parse(e.data));
      }
    };
    // Ping every 10s for frequent updates
    ws.onopen = () => setInterval(() => ws.send('ping'), 10000);
    return ws;
  }
}

// --- Usage Example ---
const api = new PerpetualAPI();

// Fetch market status
const status = await api.getMarketStatus('ETH');
console.log(`Epoch ${status.epoch}: YES=${status.yes_price}`);

// Stream updates
api.connectWebSocket('ETH', (msg) => {
  if (msg.type === 'status') {
    console.log(`Time left: ${msg.data.time_remaining}s`);
  }
});

Installation

# Download and add to your project
curl -O https://cymetica.com/static/sdk/perpetual-types.ts

# Or copy the download URL
wget https://cymetica.com/static/sdk/perpetual-types.ts

Then import in your code: import { PerpetualAPI, MarketStatus } from './perpetual-types';

// ERRORS & RATE LIMITS

Rate Limits

Public endpoints are rate limited by IP address. Authenticated endpoints use API key limits.

Endpoint Type Limit Window
Public REST endpoints 100 requests per minute
WebSocket connections 10 connections per IP
WebSocket messages 60 messages per minute

Rate limit headers are included in all responses:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1700000060

HTTP Status Codes

Code Meaning
200 Success
400 Bad Request - Invalid parameters
404 Not Found - Market or resource doesn't exist
429 Rate Limit Exceeded - Wait and retry
500 Server Error - Try again later

Error Response Format

{
  "success": false,
  "error": {
    "code": "MARKET_NOT_FOUND",
    "message": "Market 'XYZ' not found",
    "details": {
      "symbol": "XYZ"
    }
  }
}

Error Codes

Code HTTP Description
MARKET_NOT_FOUND 404 Market symbol doesn't exist or has no active market
MARKET_CLOSED 400 Trading window has ended for current epoch
INVALID_PARAMETER 400 Request parameter is invalid or out of range
INVALID_AMOUNT 400 Trade amount is invalid (must be > 0)
INSUFFICIENT_BALANCE 400 Not enough collateral tokens for trade
RATE_LIMIT_EXCEEDED 429 Too many requests - check Retry-After header
INTERNAL_SERVER_ERROR 500 Server error - try again later

Handling Rate Limits

async function fetchWithRetry(url, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const res = await fetch(url);

    if (res.status === 429) {
      const retryAfter = res.headers.get('Retry-After') || 60;
      console.log(`Rate limited. Waiting ${retryAfter}s...`);
      await new Promise(r => setTimeout(r, retryAfter * 1000));
      continue;
    }

    return res.json();
  }
  throw new Error('Max retries exceeded');
}

Best Practices

  • Use WebSocket for real-time data instead of polling REST endpoints
  • Cache responses when possible (market list changes rarely)
  • Implement exponential backoff for retries
  • Check X-RateLimit-Remaining header before making requests
  • Send "ping" on WebSocket every 10s for frequent status updates

Need help? Join our Telegram or Discord

Swagger UIPython SDKFull API Docs