npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@basedone/miniapp-sdk

v1.2.2

Published

SDK for building mini-apps for the Based One trading terminal

Downloads

179

Readme

Based.One MiniApp SDK

Official SDK for building mini-apps that integrate with the Based.One trading terminal. This SDK provides a robust postMessage-based communication layer with full TypeScript support and React integration.

✨ Features

  • 🚀 Core SDK - Powerful client for terminal communication via postMessage
  • ⚛️ React Integration - Custom hooks and context provider for React apps
  • 💪 TypeScript First - Full type safety with comprehensive definitions
  • 🔒 Permission Management - Request and manage permissions for terminal features
  • 📡 Real-time Data - EventEmitter-based updates for market data, positions, and balances
  • 🎨 Theme Support - Automatic theme synchronization with the terminal
  • 📚 Command System - Type-safe command execution with response handling

🚀 Quick Start

Installation

npm install @basedone/miniapp-sdk
# or
yarn add @basedone/miniapp-sdk

Core API (Vanilla JavaScript)

import { MiniAppSDK } from '@basedone/miniapp-sdk';

// Initialize client
const client = new MiniAppSDK({
  appId: 'my-trading-app',
  debug: true
});

// Wait for connection
client.on('connected', ({ sessionId, permissions }) => {
  console.log('Connected!', sessionId);
});

// Place an order with automatic permission handling
try {
  const response = await client.placeOrder({
    symbol: 'ETH',
    side: 'buy',
    orderType: 'market',
    size: 0.1,
    // Optional: Add TP/SL (Take Profit/Stop Loss)
    tpsl: {
      tpPrice: 2500,    // Take profit at $2500
      slPrice: 1800     // Stop loss at $1800
    }
  });
  console.log('Order placed:', response.data.orderId);
} catch (error) {
  console.error('Order failed:', error.message);
}

// Subscribe to market data
await client.subscribeToMarkets(['ETH', 'BTC', 'SOL']);

client.on('market.price', (data) => {
  console.log(`${data.symbol}: $${data.price}`);
});

React Hooks

import React from 'react';
import {
  MiniAppProvider,
  useMiniApp,
  useCommand,
  useMarketData
} from '@basedone/miniapp-sdk/react';

function App() {
  return (
    <MiniAppProvider config={{
      appId: 'my-trading-app',
      debug: true
    }}>
      <TradingWidget />
    </MiniAppProvider>
  );
}

function TradingWidget() {
  const { client, connected, sessionId } = useMiniApp();
  const { execute: placeOrder, loading } = useCommand('order.submit');
  const marketData = useMarketData(['ETH', 'BTC', 'SOL']);

  const handleQuickBuy = async (symbol: string) => {
    try {
      const result = await placeOrder({
        symbol,
        side: 'buy',
        orderType: 'market',
        size: 0.1
      });
      alert(`Order placed: ${result.orderId}`);
    } catch (err) {
      alert(`Order failed: ${err.message}`);
    }
  };

  if (!connected) return <div>Connecting...</div>;

  return (
    <div>
      <h2>🚀 Trading Widget</h2>
      <p>Session: {sessionId}</p>

      {Object.entries(marketData).map(([symbol, data]) => (
        <div key={symbol} style={{ margin: '10px 0' }}>
          <strong>{symbol}:</strong> ${data.price?.toFixed(2)}
          <button
            onClick={() => handleQuickBuy(symbol)}
            disabled={loading}
            style={{ marginLeft: '10px' }}
          >
            {loading ? 'Buying...' : `Buy 0.1 ${symbol}`}
          </button>
        </div>
      ))}
    </div>
  );
}

React Context Provider

import React from 'react';
import { MiniAppProvider, useMiniApp } from '@basedone/miniapp-sdk/react';

function App() {
  const config = {
    appId: 'my-trading-app',
    name: 'Trading Dashboard',
    url: window.location.origin,
    permissions: ['read_market_data', 'place_orders'],
    autoConnect: true,
    debug: true
  };

  return (
    <MiniAppProvider config={config}>
      <TradingDashboard />
    </MiniAppProvider>
  );
}

function TradingDashboard() {
  const { client, connected, sessionId, permissions } = useMiniApp();
  const [marketData, setMarketData] = React.useState<Record<string, any>>({});

  React.useEffect(() => {
    if (!client || !connected) return;

    // Subscribe to market data (market.price)
    client.subscribeToMarkets(['ETH', 'BTC', 'SOL']);

    // Listen for market updates
    const handleMarketUpdate = (data: any) => {
      setMarketData(prev => ({ ...prev, [data.symbol]: data }));
    };

    client.on('market.price', handleMarketUpdate);

    return () => {
      client.off('market.price', handleMarketUpdate);
    };
  }, [client, connected]);

  const handleQuickBuy = async (symbol: string) => {
    if (!client?.hasPermission('place_orders')) {
      alert('Trading permission required');
      return;
    }

    try {
      const result = await client.placeOrder({
        symbol,
        side: 'buy',
        orderType: 'market',
        size: 0.1
      });
      alert(`Order placed successfully`);
    } catch (err) {
      alert(`Order failed: ${err.message}`);
    }
  };

  if (!connected) return <div>Connecting...</div>;

  return (
    <div>
      <h1>🏗️ Trading Dashboard</h1>
      <p>Session: {sessionId}</p>
      <p>Permissions: {Array.from(permissions).join(', ')}</p>

      <div>
        {Object.entries(marketData).map(([symbol, data]) => (
          <div key={symbol}>
            <strong>{symbol}:</strong> ${data.price?.toFixed(2)}
            {client?.hasPermission('place_orders') && (
              <button onClick={() => handleQuickBuy(symbol)}>
                Quick Buy
              </button>
            )}
          </div>
        ))}
      </div>
    </div>
  );
}

📚 API Reference

Core SDK Client

import { MiniAppSDK } from '@basedone/miniapp-sdk';

const client = new MiniAppSDK({
  appId: string;           // Your app identifier
  name?: string;           // App display name
  url?: string;            // App URL
  debug?: boolean;         // Enable console logging
  autoConnect?: boolean;   // Auto-connect on initialization (default: true)
  targetOrigin?: string;   // PostMessage target origin (default: '*')
  permissions?: AppPermission[]; // Initial permissions to request
  version?: string;        // App version
  appSecret?: string;      // Optional app secret for message signing
});

// Connection Management
client.connect(): Promise<void>
client.disconnect(): void
client.getConnectionState(): ConnectionState

// Permission Management
client.hasPermission(permission: AppPermission): boolean
client.requestPermissions(permissions: AppPermission[]): Promise<AppPermission[]>

// Command Execution
client.sendCommand<T extends CommandType>(
  commandName: T,
  data: CommandPayload<T>
): Promise<CommandResponse<T>>

// Subscriptions
client.subscribe(
  dataType: MiniAppSubscriptionType,
  payload?: SubcriptionPayload
): Promise<{ subscriptionId: string; unsubscribe: () => Promise<void> }>
client.unsubscribe(subscriptionId: string): Promise<void>

// Trading Operations (convenience methods)
client.placeOrder(order: SubmitOrderCommand): Promise<CommandResponse<"order.submit">>
client.placeOrders(orders: SubmitOrderCommand[]): Promise<CommandResponse<"order.submitMultiple">>
client.cancelOrder(orderId: string, symbol?: string): Promise<CommandResponse<"order.cancel">>
client.cancelOrders(orders: CancelOrderCommand[]): Promise<CommandResponse<"order.cancelMultiple">>
client.getAccount(): Promise<CommandResponse<"wallet.balance">>
client.getPositions(symbol?: string): Promise<CommandResponse<"position.query">>
client.getOrders(symbol?: string, status?: "open" | "filled" | "cancelled" | "partial" | "all"): Promise<CommandResponse<"orders.query">>
client.getWalletAddress(): Promise<string>

// Market Data
client.subscribeToMarkets(symbols: string[]): Promise<{
  subscriptionId: string;
  unsubscribe: () => Promise<void>
}>

// Theme Management
client.applyTheme(): Promise<void>
client.getThemeManager(): ThemeManager

// Event Handling
client.on(event: ExtendedEventType, handler: Function): void
client.off(event: ExtendedEventType, handler: Function): void
client.emit(event: ExtendedEventType, data: any): void

// Cleanup
client.destroy(): void

React Hooks

// Core Hooks
useMiniApp()                        // Access client and connection state
useCommand<T>(commandType: T)       // Execute typed commands
useTheme()                          // Theme management
useMarketData(symbols: string[])    // Subscribe to market data
usePositions(sdk: MiniAppSDK)       // Track positions
useEvent(sdk, eventType, handler)   // Subscribe to events
useWalletAddress()                  // Get connected wallet address

// Hook Return Types
useMiniApp(): {
  client: MiniAppSDK | null;
  connected: boolean;
  sessionId: string | undefined;
  permissions: Set<AppPermission>;
  error: Error | null;
}

useCommand<T>(): {
  execute: (data: CommandPayload<T>) => Promise<CommandResponse>;
  loading: boolean;
  error: Error | null;
  response: CommandResponse | null;
}

useTheme(): {
  theme: Theme | null;
  applyTheme: () => Promise<void>;
  loading: boolean;
}

useMarketData(): Record<string, any>

usePositions(): {
  positions: any[];
  refresh: () => Promise<void>;
  loading: boolean;
}

useWalletAddress(): {
  address: string | null;
  loading: boolean;
  error: Error | null;
  refresh: () => Promise<void>;
}

React Provider

// Context Provider
<MiniAppProvider config={MiniAppConfig}>
  {children}
</MiniAppProvider>

// Provider Configuration
interface MiniAppConfig {
  appId: string;                      // Unique app identifier
  name?: string;                      // App display name
  url?: string;                       // App URL
  targetOrigin?: string;              // PostMessage target (default: '*')
  permissions?: AppPermission[];      // Initial permissions
  autoConnect?: boolean;              // Auto-connect (default: true)
  version?: string;                   // App version
  debug?: boolean;                    // Debug mode
  appSecret?: string;                 // Optional app secret
  sessionId?: string;                 // Session ID (managed internally)
  tenantId?: string;                  // Tenant ID for theming
}

// Context Value
interface MiniAppContextValue {
  client: MiniAppSDK | null;
  connected: boolean;
  sessionId: string | undefined;
  permissions: Set<AppPermission>;
  error: Error | null;
}

🔒 Permission Types

type AppPermission =
  // Market data permissions
  | 'read_market_data'      // Access market prices and data
  | 'read_orderbook'         // Access order book data
  | 'read_candles'           // Access candlestick data

  // Account permissions
  | 'read_account'           // Access account information
  | 'read_positions'         // Access position data
  | 'read_orders'            // Access order data
  | 'read_balance'           // Access balance information
  | 'read_trades'            // Access trade history

  // Trading permissions
  | 'place_orders'           // Place new orders
  | 'cancel_orders'          // Cancel existing orders
  | 'modify_orders'          // Modify existing orders
  | 'populate_orders'        // Populate order form inputs

  // UI permissions
  | 'read_navigation'        // Read current navigation state
  | 'write_navigation'       // Change navigation/symbol
  | 'write_chart'            // Draw on charts
  | 'send_notifications'     // Send notifications to user

  // User settings
  | 'read_user_settings'     // Access user settings
  | 'modify_user_settings'   // Modify user settings

  // Advanced permissions
  | 'execute_strategies'     // Execute trading strategies
  | 'access_analytics';      // Access analytics data

📊 Type Definitions

// Connection State
interface ConnectionState {
  connected: boolean;
  sessionId?: string;
  permissions: Set<AppPermission>;
  lastHeartbeat?: number;
}

// Order Types
interface SubmitOrderCommand {
  symbol: string;
  side: 'buy' | 'sell';
  orderType: 'market' | 'limit' | 'stop' | 'stop_limit';
  size?: number;
  price?: number;
  stopPrice?: number;
  leverage?: number;
  reduceOnly?: boolean;
  postOnly?: boolean;
  // TP/SL (Take Profit/Stop Loss) options
  tpsl?: {
    tpPrice?: number | null;        // Take profit price
    tpGainPercent?: number | null;  // TP as percentage gain
    slPrice?: number | null;        // Stop loss price
    slLossPercent?: number | null;  // SL as percentage loss
  };
}

// Order (returned from getOrders)
interface Order {
  orderId: string;
  clientOrderId?: string;
  symbol: string;
  side: 'buy' | 'sell';
  orderType: 'market' | 'limit' | 'stop' | 'stop_limit';
  price?: number;
  stopPrice?: number;
  size: number;
  filledSize: number;
  remainingSize: number;
  status: 'open' | 'partial' | 'cancelled' | 'filled';
  createdAt: number;
  updatedAt: number;
  tpsl?: {
    tpPrice?: number;
    slPrice?: number;
  };
}

// Query Orders Response
interface QueryOrdersResponse {
  orders: Order[];
  totalCount: number;
}

// Event Types
type ExtendedEventType =
  | EventType              // All terminal events
  | 'connected'            // Connection established
  | 'disconnected'         // Connection lost
  | 'error';               // Error occurred

type EventType =
  // Market events
  | 'market.ticker'        // Market ticker update
  | 'market.bbo'           // Best bid/offer update
  | 'market.prices'        // Price updates for multiple symbols
  | 'market.trades'        // Trade executed

  // Order & Position events
  | 'order.status'         // Order status change
  | 'order.update'         // Order update
  | 'position.update'      // Position changed

  // Wallet events
  | 'wallet.update'        // Balance changed
  | 'wallet.switch'        // Wallet switched

  // UI events
  | 'navigation.update'    // Navigation/symbol changed
  | 'theme.change'         // Theme changed

  // Auth & Permission events
  | 'auth.status'          // Auth status changed
  | 'permission.change'    // Permissions changed
  | 'permission.granted'   // New permission granted

  // System events
  | 'connection.status'    // Connection status changed
  | 'notification';        // Notification received

// Command Types
type CommandType =
  // Order commands
  | 'order.populate'       // Populate order form inputs
  | 'order.submit'         // Submit new order
  | 'order.cancel'         // Cancel existing order
  | 'order.modify'         // Modify existing order

  // Leverage commands
  | 'leverage.query'       // Get current leverage settings for a user and symbol
  | 'leverage.update'      // Update leverage and margin modes

  // Position commands
  | 'position.query'       // Query positions
  | 'position.close'       // Close position

  // Wallet commands
  | 'wallet.balance'       // Get wallet balance
  | 'wallet.address'       // Get connected wallet address

  // Notification commands
  | 'notification.success' // Show success notification
  | 'notification.error'   // Show error notification
  | 'notification.warning' // Show warning notification
  | 'notification.info'    // Show info notification

  // Navigation commands
  | 'navigation.navigate'  // Navigate to symbol
  | 'navigation.status'    // Get current navigation

  // Chart commands
  | 'chart.draw'           // Draw on chart

  // Analytics commands
  | 'analytics.query';     // Query analytics data

// Subscription Types
enum MiniAppSubscriptionType {
  MARKET_PRICES = 'market.prices',      // Price updates
  BEST_BID_OFFER = 'market.bbo',        // Best bid/offer
  ORDER_UPDATES = 'order.update',       // Order updates
  POSITION_UPDATES = 'position.update', // Position updates
  TRADE_UPDATES = 'trade.update',       // Trade updates
  ACCOUNT_UPDATES = 'account.update',   // Account updates
  USER_SETTINGS = 'user.settings',      // User settings
  NAVIGATION_UPDATES = 'navigation.update' // Navigation updates
}

// Order Response Types with Discriminated Unions
interface CommandResponse<T extends CommandType> {
  success: boolean;
  data?: CommandResponsePayload<T>;
  error?: string;
  timestamp?: number;
}

// Order Status Types (Discriminated Union)
type OrderStatus =
  | { resting: { oid: string } }           // Order in orderbook
  | { filled: { oid: string; totalSz: string; avgPx: string } }  // Order filled
  | { error: string };                     // Order failed

// Single Order Response
interface SubmitOrderResponse {
  cloid: string;                           // Client order ID
  statuses: [OrderStatus];                 // Always array with 1 element
}

// Batch Order Result (Discriminated Union)
type BatchOrderResult =
  | {
      index: number;
      success: true;
      status: { resting: { oid: string } } | { filled: { oid: string; totalSz: string; avgPx: string } };
    }
  | {
      index: number;
      success: false;
      status: { error: string };
    };

// Batch Orders Response
interface SubmitMultipleOrdersResponse {
  results: BatchOrderResult[];
  successCount: number;
  failureCount: number;
}

// Cancel Order Types
interface CancelOrderCommand {
  orderId: string;
  symbol?: string;
}

// Cancel Status Types (Discriminated Union)
type CancelStatus =
  | { success: { oid: string } }       // Cancellation succeeded
  | { error: string };                 // Cancellation failed

// Batch Cancel Result (Discriminated Union)
type BatchCancelResult =
  | {
      index: number;
      orderId: string;
      success: true;
      status: { success: { oid: string } };
    }
  | {
      index: number;
      orderId: string;
      success: false;
      status: { error: string };
    };

// Batch Cancel Response
interface CancelMultipleOrdersResponse {
  results: BatchCancelResult[];
  successCount: number;
  failureCount: number;
}

// Type Guards for Order Status
function isRestingStatus(status: OrderStatus): status is { resting: { oid: string } } {
  return 'resting' in status;
}

function isFilledStatus(status: OrderStatus): status is { filled: { oid: string; totalSz: string; avgPx: string } } {
  return 'filled' in status;
}

function isErrorStatus(status: OrderStatus): status is { error: string } {
  return 'error' in status;
}

// Type Guards for Cancel Status
function isCancelSuccess(status: CancelStatus): status is { success: { oid: string } } {
  return 'success' in status;
}

function isCancelError(status: CancelStatus): status is { error: string } {
  return 'error' in status;
}

🎯 Type-Safe Response Handling

The SDK provides discriminated unions for type-safe response handling. TypeScript automatically narrows types based on runtime checks.

Single Order Response

import { MiniAppSDK, isRestingStatus, isFilledStatus, isErrorStatus } from '@basedone/miniapp-sdk';

const client = new MiniAppSDK({ appId: 'my-app' });

const response = await client.placeOrder({
  symbol: 'ETH',
  side: 'buy',
  orderType: 'limit',
  price: 2000,
  size: 0.1
});

if (response.success && response.data) {
  const status = response.data.statuses[0];

  // TypeScript knows the shape based on runtime check
  if (isRestingStatus(status)) {
    console.log(`Order resting in orderbook: ${status.resting.oid}`);
    // TypeScript knows: status.resting.oid exists
  } else if (isFilledStatus(status)) {
    console.log(`Order filled: ${status.filled.totalSz} @ ${status.filled.avgPx}`);
    // TypeScript knows: status.filled.totalSz, status.filled.avgPx exist
  } else if (isErrorStatus(status)) {
    console.error(`Order failed: ${status.error}`);
    // TypeScript knows: status.error exists
  }
}

Batch Order Response with Type Safety

const orders = [
  { symbol: 'ETH', side: 'buy', orderType: 'market', size: 0.1 },
  { symbol: 'BTC', side: 'buy', orderType: 'limit', price: 50000, size: 0.01 },
  { symbol: 'SOL', side: 'buy', orderType: 'market', size: 5 }
];

const response = await client.placeOrders(orders);

if (response.success && response.data) {
  const { successCount, failureCount, results } = response.data;
  console.log(`Batch: ${successCount} succeeded, ${failureCount} failed`);

  // Process each result with type safety
  results.forEach((result, idx) => {
    if (result.success) {
      // TypeScript knows: result.status can be 'resting' or 'filled'
      if ('resting' in result.status) {
        console.log(`Order ${idx}: Resting (${result.status.resting.oid})`);
      } else {
        // TypeScript knows this is 'filled'
        const { oid, totalSz, avgPx } = result.status.filled;
        console.log(`Order ${idx}: Filled ${totalSz} @ ${avgPx} (${oid})`);
      }
    } else {
      // TypeScript knows: result.status.error exists
      console.error(`Order ${idx}: Failed - ${result.status.error}`);
    }
  });
}

React Example with Type Safety

import React from 'react';
import { useMiniApp, isFilledStatus } from '@basedone/miniapp-sdk/react';

function TradingComponent() {
  const { client } = useMiniApp();
  const [orderStatus, setOrderStatus] = React.useState<string>('');

  const handleOrder = async () => {
    if (!client) return;

    const response = await client.placeOrder({
      symbol: 'ETH',
      side: 'buy',
      orderType: 'market',
      size: 0.1
    });

    if (response.success && response.data) {
      const status = response.data.statuses[0];

      if (isFilledStatus(status)) {
        // Full type safety - TypeScript knows all these fields exist
        setOrderStatus(
          `Filled ${status.filled.totalSz} @ $${status.filled.avgPx}`
        );
      } else if ('resting' in status) {
        setOrderStatus(`Order placed: ${status.resting.oid}`);
      } else {
        setOrderStatus(`Error: ${status.error}`);
      }
    }
  };

  return (
    <div>
      <button onClick={handleOrder}>Place Order</button>
      {orderStatus && <p>{orderStatus}</p>}
    </div>
  );
}

📚 Documentation

💡 Examples

Get Wallet Address (Vanilla JavaScript)

// Retrieve the connected user's wallet address
const client = new MiniAppSDK({
  appId: 'my-app',
  debug: true
});

client.on('connected', async () => {
  try {
    const address = await client.getWalletAddress();
    console.log('Connected wallet:', address);
  } catch (error) {
    console.error('Failed to get wallet address:', error);
  }
});

// Listen for wallet switch events
client.on('wallet.switch', (event) => {
  console.log('Wallet switched to:', event.address);
});

Get Wallet Address (React Hook)

import React from 'react';
import { MiniAppProvider, useWalletAddress } from '@basedone/miniapp-sdk/react';

function WalletDisplay() {
  const { address, loading, error, refresh } = useWalletAddress();

  if (loading) return <div>Loading wallet...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h3>Connected Wallet</h3>
      <p>{address || 'No wallet connected'}</p>
      <button onClick={refresh}>Refresh</button>
    </div>
  );
}

function App() {
  return (
    <MiniAppProvider config={{ appId: 'my-app' }}>
      <WalletDisplay />
    </MiniAppProvider>
  );
}

Populate Order Form

// Populate the terminal's order form with pre-filled values
const client = new MiniAppSDK({
  appId: 'trading-assistant',
  debug: true
});

// Populate order form with calculated values
await client.sendCommand('order.populate', {
  symbol: 'ETH',
  side: 'buy',
  orderType: 'limit',
  size: 0.5,
  price: 2000,
  // Add TP/SL levels
  tpsl: {
    tpPrice: 2200,      // Take profit at $2200
    slPrice: 1900,      // Stop loss at $1900
    // Or use percentage-based TP/SL
    // tpGainPercent: 10,  // 10% profit
    // slLossPercent: 5    // 5% loss
  }
});

Batch Order Placement

// Place multiple orders simultaneously for efficient portfolio management
const client = new MiniAppSDK({
  appId: 'batch-trader',
  debug: true,
  autoConnect: true
});

client.on('connected', async () => {
  // Portfolio rebalancing with batch orders
  const orders = [
    {
      symbol: 'ETH',
      side: 'buy',
      orderType: 'market',
      size: 0.1
    },
    {
      symbol: 'BTC',
      side: 'buy',
      orderType: 'limit',
      size: 0.01,
      price: 50000
    },
    {
      symbol: 'SOL',
      side: 'buy',
      orderType: 'market',
      size: 5,
      tpsl: {
        tpGainPercent: 10,  // Take profit at 10%
        slLossPercent: 5     // Stop loss at 5%
      }
    }
  ];

  try {
    const result = await client.placeOrders(orders);

    if (result.success && result.data) {
      const { successCount, failureCount, results } = result.data;
      console.log(`Batch complete: ${successCount} succeeded, ${failureCount} failed`);

      // Check individual results
      results.forEach((res, idx) => {
        if (res.success) {
          console.log(`Order ${idx}: Placed successfully (${res.cloid})`);
        } else {
          console.error(`Order ${idx}: Failed - ${res.error}`);
        }
      });
    }
  } catch (error) {
    console.error('Batch order failed:', error);
  }
});

// DCA strategy using batch orders
async function executeDCA(symbols, amountEach) {
  const orders = symbols.map(symbol => ({
    symbol,
    side: 'buy',
    orderType: 'market',
    size: amountEach,
    tpsl: {
      tpGainPercent: 15,
      slLossPercent: 5
    }
  }));

  return await client.placeOrders(orders);
}

// Execute DCA for multiple assets
await executeDCA(['ETH', 'BTC', 'SOL', 'AVAX'], 0.1);

Batch Order Cancellation

import { MiniAppSDK, isCancelSuccess, isCancelError } from '@basedone/miniapp-sdk';

const client = new MiniAppSDK({
  appId: 'order-manager',
  debug: true,
  autoConnect: true
});

client.on('connected', async () => {
  // Cancel multiple orders simultaneously
  const ordersToCancel = [
    { orderId: 'order-123', symbol: 'ETH' },
    { orderId: 'order-456', symbol: 'BTC' },
    { orderId: 'order-789', symbol: 'SOL' }
  ];

  try {
    const response = await client.cancelOrders(ordersToCancel);

    if (response.success && response.data) {
      const { successCount, failureCount, results } = response.data;
      console.log(`Batch cancel: ${successCount} succeeded, ${failureCount} failed`);

      // Process each result with type safety
      results.forEach((result, idx) => {
        if (result.success) {
          // TypeScript knows: result.status.success exists
          console.log(`Cancel ${idx}: Success (${result.status.success.oid})`);
        } else {
          // TypeScript knows: result.status.error exists
          console.error(`Cancel ${idx}: Failed - ${result.status.error}`);
        }
      });
    }
  } catch (error) {
    console.error('Batch cancel failed:', error);
  }
});

// Example: Cancel all open orders for a symbol
async function cancelAllOrdersForSymbol(symbol: string) {
  // First, get all open orders for the symbol
  const positions = await client.getPositions(symbol);

  // Extract order IDs (this depends on your data structure)
  const orderIds = positions.data?.positions
    .flatMap(p => p.orders || [])
    .map(order => ({ orderId: order.id, symbol }));

  if (orderIds && orderIds.length > 0) {
    return await client.cancelOrders(orderIds);
  }
}

// Cancel all orders for ETH
await cancelAllOrdersForSymbol('ETH');

Retrieve Open Orders

import { MiniAppSDK, Order } from '@basedone/miniapp-sdk';

const client = new MiniAppSDK({
  appId: 'order-monitor',
  permissions: ['read_orders'],
  debug: true,
  autoConnect: true
});

client.on('connected', async () => {
  // Get all open orders
  const response = await client.getOrders();

  if (response.success && response.data) {
    const { orders, totalCount } = response.data;
    console.log(`Found ${totalCount} open orders`);

    orders.forEach((order: Order) => {
      console.log(`
        Order ID: ${order.orderId}
        Symbol: ${order.symbol}
        Side: ${order.side}
        Type: ${order.orderType}
        Size: ${order.filledSize}/${order.size}
        Price: ${order.price || 'Market'}
        Status: ${order.status}
        ${order.tpsl ? `TP: ${order.tpsl.tpPrice}, SL: ${order.tpsl.slPrice}` : ''}
      `);
    });
  }

  // Get open orders for specific symbol
  const ethOrders = await client.getOrders('ETH', 'open');
  if (ethOrders.success && ethOrders.data) {
    console.log(`ETH open orders: ${ethOrders.data.totalCount}`);
  }

  // Get all orders (including filled/cancelled)
  const allOrders = await client.getOrders(undefined, 'all');
  if (allOrders.success && allOrders.data) {
    console.log(`Total orders: ${allOrders.data.totalCount}`);

    // Filter by status
    const filledOrders = allOrders.data.orders.filter(o => o.status === 'filled');
    const cancelledOrders = allOrders.data.orders.filter(o => o.status === 'cancelled');
    console.log(`Filled: ${filledOrders.length}, Cancelled: ${cancelledOrders.length}`);
  }

  // Server-side filtering by status (more efficient)
  const filled = await client.getOrders(undefined, 'filled');
  console.log(`Filled orders: ${filled.data?.totalCount || 0}`);

  const cancelled = await client.getOrders(undefined, 'cancelled');
  console.log(`Cancelled orders: ${cancelled.data?.totalCount || 0}`);

  const partial = await client.getOrders(undefined, 'partial');
  console.log(`Partially filled orders: ${partial.data?.totalCount || 0}`);
});

// Listen for order updates in real-time
client.on('order.update', (order: Order) => {
  console.log(`Order ${order.orderId} updated: ${order.status}`);

  if (order.status === 'filled') {
    console.log(`Order filled: ${order.filledSize} ${order.symbol} @ ${order.price}`);
  }
});

React Filled Orders Tracker

import React from 'react';
import { MiniAppProvider, useBasedOrders } from '@basedone/miniapp-sdk/react';
import type { Order } from '@basedone/miniapp-sdk';

function App() {
  return (
    <MiniAppProvider config={{
      appId: 'trade-history',
      permissions: ['read_orders'],
      debug: true
    }}>
      <FilledOrdersTracker />
    </MiniAppProvider>
  );
}

function FilledOrdersTracker() {
  const { orders, getFilledOrders, loading } = useBasedOrders();
  const [selectedSymbol, setSelectedSymbol] = React.useState<string>('');

  React.useEffect(() => {
    // Load filled orders on mount
    getFilledOrders();
  }, []);

  const handleSymbolFilter = async (symbol: string) => {
    setSelectedSymbol(symbol);
    if (symbol) {
      await getFilledOrders(symbol);
    } else {
      await getFilledOrders();
    }
  };

  if (loading) return <div>Loading trade history...</div>;

  // Calculate statistics
  const totalVolume = orders.reduce((sum, order) => sum + (order.filledSize * (order.price || 0)), 0);
  const buyOrders = orders.filter(o => o.side === 'buy');
  const sellOrders = orders.filter(o => o.side === 'sell');

  return (
    <div style={{ padding: '20px' }}>
      <h2>📈 Trade History</h2>

      <div style={{ marginBottom: '20px' }}>
        <label>Filter by symbol: </label>
        <select
          value={selectedSymbol}
          onChange={(e) => handleSymbolFilter(e.target.value)}
        >
          <option value="">All Symbols</option>
          {Array.from(new Set(orders.map(o => o.symbol))).map(symbol => (
            <option key={symbol} value={symbol}>{symbol}</option>
          ))}
        </select>
      </div>

      {/* Statistics */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '20px', marginBottom: '30px' }}>
        <div style={{ padding: '15px', background: '#f5f5f5', borderRadius: '8px' }}>
          <strong>Total Trades</strong>
          <div style={{ fontSize: '24px', marginTop: '10px' }}>{orders.length}</div>
        </div>
        <div style={{ padding: '15px', background: '#e8f5e9', borderRadius: '8px' }}>
          <strong>Buy Orders</strong>
          <div style={{ fontSize: '24px', marginTop: '10px', color: 'green' }}>{buyOrders.length}</div>
        </div>
        <div style={{ padding: '15px', background: '#ffebee', borderRadius: '8px' }}>
          <strong>Sell Orders</strong>
          <div style={{ fontSize: '24px', marginTop: '10px', color: 'red' }}>{sellOrders.length}</div>
        </div>
      </div>

      {/* Orders List */}
      <h3>Filled Orders ({orders.length})</h3>
      {orders.length === 0 ? (
        <p>No filled orders found</p>
      ) : (
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead>
            <tr style={{ borderBottom: '2px solid #ccc' }}>
              <th>Time</th>
              <th>Symbol</th>
              <th>Side</th>
              <th>Type</th>
              <th>Size</th>
              <th>Price</th>
              <th>Total</th>
            </tr>
          </thead>
          <tbody>
            {orders.map((order) => (
              <tr key={order.orderId} style={{ borderBottom: '1px solid #eee' }}>
                <td>{new Date(order.updatedAt).toLocaleString()}</td>
                <td>{order.symbol}</td>
                <td style={{ color: order.side === 'buy' ? 'green' : 'red' }}>
                  {order.side.toUpperCase()}
                </td>
                <td>{order.orderType}</td>
                <td>{order.filledSize}</td>
                <td>${order.price?.toFixed(2) || 'Market'}</td>
                <td>${((order.filledSize * (order.price || 0))).toFixed(2)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
}

React Orders Manager

import React from 'react';
import { MiniAppProvider, useMiniApp, useBasedOrders } from '@basedone/miniapp-sdk/react';
import type { Order } from '@basedone/miniapp-sdk';

function App() {
  return (
    <MiniAppProvider config={{
      appId: 'orders-manager',
      permissions: ['read_orders', 'cancel_orders'],
      debug: true
    }}>
      <OrdersManager />
    </MiniAppProvider>
  );
}

function OrdersManager() {
  const { client, connected } = useMiniApp();
  const { orders, getOpenOrders, refreshOrders, loading } = useBasedOrders();
  const [selectedSymbol, setSelectedSymbol] = React.useState<string>('');

  React.useEffect(() => {
    if (connected) {
      // Refresh orders on mount
      refreshOrders();

      // Set up auto-refresh every 5 seconds
      const interval = setInterval(refreshOrders, 5000);
      return () => clearInterval(interval);
    }
  }, [connected, refreshOrders]);

  const handleSymbolFilter = async (symbol: string) => {
    setSelectedSymbol(symbol);
    if (symbol) {
      await getOpenOrders(symbol);
    } else {
      await refreshOrders();
    }
  };

  const handleCancelOrder = async (orderId: string, symbol: string) => {
    if (!client) return;

    try {
      await client.cancelOrder(orderId, symbol);
      await refreshOrders(); // Refresh after cancellation
    } catch (error) {
      console.error('Failed to cancel order:', error);
    }
  };

  if (!connected) return <div>Connecting...</div>;
  if (loading) return <div>Loading orders...</div>;

  // Group orders by symbol
  const ordersBySymbol = orders.reduce((acc, order) => {
    if (!acc[order.symbol]) acc[order.symbol] = [];
    acc[order.symbol].push(order);
    return acc;
  }, {} as Record<string, Order[]>);

  return (
    <div style={{ padding: '20px' }}>
      <h2>📋 Orders Manager</h2>

      <div style={{ marginBottom: '20px' }}>
        <label>Filter by symbol: </label>
        <select
          value={selectedSymbol}
          onChange={(e) => handleSymbolFilter(e.target.value)}
        >
          <option value="">All Symbols</option>
          {Object.keys(ordersBySymbol).map(symbol => (
            <option key={symbol} value={symbol}>{symbol}</option>
          ))}
        </select>
        <button onClick={() => refreshOrders()} style={{ marginLeft: '10px' }}>
          Refresh
        </button>
      </div>

      <div>
        <h3>Open Orders ({orders.length})</h3>
        {orders.length === 0 ? (
          <p>No open orders</p>
        ) : (
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead>
              <tr style={{ borderBottom: '2px solid #ccc' }}>
                <th>Symbol</th>
                <th>Side</th>
                <th>Type</th>
                <th>Size</th>
                <th>Filled</th>
                <th>Price</th>
                <th>Status</th>
                <th>Actions</th>
              </tr>
            </thead>
            <tbody>
              {orders.map((order) => (
                <tr key={order.orderId} style={{ borderBottom: '1px solid #eee' }}>
                  <td>{order.symbol}</td>
                  <td style={{ color: order.side === 'buy' ? 'green' : 'red' }}>
                    {order.side.toUpperCase()}
                  </td>
                  <td>{order.orderType}</td>
                  <td>{order.size}</td>
                  <td>{order.filledSize}</td>
                  <td>{order.price || 'Market'}</td>
                  <td>
                    <span style={{
                      padding: '2px 8px',
                      borderRadius: '4px',
                      background: order.status === 'open' ? '#e3f2fd' : '#fff3e0'
                    }}>
                      {order.status}
                    </span>
                  </td>
                  <td>
                    <button
                      onClick={() => handleCancelOrder(order.orderId, order.symbol)}
                      style={{
                        padding: '4px 12px',
                        background: '#f44336',
                        color: 'white',
                        border: 'none',
                        borderRadius: '4px',
                        cursor: 'pointer'
                      }}
                    >
                      Cancel
                    </button>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        )}
      </div>

      {/* Summary by symbol */}
      <div style={{ marginTop: '30px' }}>
        <h3>Summary by Symbol</h3>
        {Object.entries(ordersBySymbol).map(([symbol, symbolOrders]) => (
          <div key={symbol} style={{ marginBottom: '10px' }}>
            <strong>{symbol}:</strong> {symbolOrders.length} orders
            {' '}({symbolOrders.filter(o => o.side === 'buy').length} buy,
            {symbolOrders.filter(o => o.side === 'sell').length} sell)
          </div>
        ))}
      </div>
    </div>
  );
}

Basic Trading Bot

// Simple DCA (Dollar Cost Averaging) bot
const client = new MiniAppSDK({
  appId: 'dca-bot',
  debug: true,
  autoConnect: true
});

client.on('connected', async () => {
  // Subscribe to ETH price updates
  await client.subscribeToMarkets(['ETH']);

  client.on('market.prices', async (data) => {
    if (data.symbol === 'ETH' && shouldBuy(data.price)) {
      try {
        const result = await client.placeOrder({
          symbol: 'ETH',
          side: 'buy',
          orderType: 'market',
          size: 0.01, // Buy 0.01 ETH
          // Add TP/SL for risk management
          tpsl: {
            tpGainPercent: 5,   // Take profit at 5% gain
            slLossPercent: 2    // Stop loss at 2% loss
          }
        });
        console.log(`DCA: Bought ETH at $${data.price}`);
      } catch (error) {
        console.error('DCA order failed:', error);
      }
    }
  });
});

function shouldBuy(currentPrice) {
  // Your DCA logic here
  return Math.random() > 0.95; // 5% chance for demo
}

React Batch Order Component

import React, { useState } from 'react';
import { MiniAppProvider, useMiniApp } from '@basedone/miniapp-sdk/react';

function App() {
  return (
    <MiniAppProvider config={{
      appId: 'batch-trader',
      permissions: ['place_orders'],
      debug: true
    }}>
      <BatchOrderWidget />
    </MiniAppProvider>
  );
}

function BatchOrderWidget() {
  const { placeOrders, loading, connected } = useMiniApp();
  const [status, setStatus] = useState('');

  const handleBatchOrder = async () => {
    if (!placeOrders) return;

    const orders = [
      { symbol: 'ETH', side: 'buy', orderType: 'market', size: 0.1 },
      { symbol: 'BTC', side: 'buy', orderType: 'market', size: 0.01 },
      { symbol: 'SOL', side: 'buy', orderType: 'market', size: 5 }
    ];

    try {
      const result = await placeOrders(orders);
      if (result.success && result.data) {
        setStatus(`Success: ${result.data.successCount}/${orders.length} orders placed`);
      }
    } catch (err) {
      setStatus(`Error: ${err.message}`);
    }
  };

  if (!connected) return <div>Connecting...</div>;

  return (
    <div>
      <h2>Batch Order Placement</h2>
      <button onClick={handleBatchOrder} disabled={loading}>
        {loading ? 'Placing...' : 'Place 3 Orders'}
      </button>
      {status && <div>{status}</div>}
    </div>
  );
}

React Portfolio Tracker

import React from 'react';
import { MiniAppProvider, useMiniApp, usePositions } from '@basedone/miniapp-sdk/react';

function App() {
  return (
    <MiniAppProvider config={{
      appId: 'portfolio-tracker',
      permissions: ['read_positions', 'read_balance'],
      debug: true
    }}>
      <PortfolioTracker />
    </MiniAppProvider>
  );
}

function PortfolioTracker() {
  const { client, connected } = useMiniApp();
  const { positions, refresh, loading } = usePositions(client);
  const [balance, setBalance] = React.useState<any>(null);

  React.useEffect(() => {
    if (connected && client) {
      // Fetch initial balance
      client.getAccount().then(setBalance);

      // Refresh positions
      refresh();

      // Set up auto-refresh
      const interval = setInterval(() => {
        refresh();
        client.getAccount().then(setBalance);
      }, 5000);

      return () => clearInterval(interval);
    }
  }, [connected, client]);

  if (!connected) return <div>Connecting...</div>;
  if (loading) return <div>Loading...</div>;

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
      <div>
        <h3>Positions</h3>
        {positions.map(pos => (
          <div key={pos.symbol}>
            {pos.symbol}: {pos.size} @ ${pos.entryPrice}
            <br />PnL: ${pos.pnl?.toFixed(2)}
          </div>
        ))}
      </div>
      <div>
        <h3>Balance</h3>
        {balance && (
          <div>Total: ${balance.totalUsdValue?.toFixed(2)}</div>
        )}
      </div>
    </div>
  );
}

🛠️ Development

# Install dependencies
npm install

# Run development mode
npm run dev

# Build for production
npm run build

# Run tests
npm test

# Type check
npm run type-check

🔗 Links

📄 License

MIT License - see LICENSE for details.


Made with ❤️ for Based.One developers