MiniApp API

API Reference

The TypeScript client provides a strongly-typed interface for building MiniApps that talk to the Based Terminal via postMessage. This guide documents the SDK surface: setup, connection lifecycle, permissions, commands, responses, subscriptions, events, and message envelopes—with concise examples.

Core API

Creating an instance

import { createMiniApp, getMiniApp } from "@based-one/miniapp-sdk";

const app = createMiniApp(config); // If called again, a new instance replaces the old singleton

const same = getMiniApp(); // returns current instance or null

MiniAppConfig

  • appId: string (required) max 12 letters, alphanumeric lowercased

  • appSecret?: string (optional; enables signature signing/verification)

  • targetOrigin?: string (default "*")

  • autoConnect?: boolean (default true)

  • version?: string (default "1.0.0")

  • permissions?: AppPermission[] (requested on connect)

  • tenantId?: string (used by theming)

  • debug?: boolean (controls debug logs)

Lifecycle

  • connect(): Promise

  • disconnect(): void

  • destroy(): void

  • getConnectionState(): { connected: boolean; sessionId?: string; permissions: Set; lastHeartbeat?: number }

Example:

await app.connect();
const state = app.getConnectionState();

Events (emitted)

The SDK extends EventEmitter and emits:

  • "connected" | "disconnected" | "error"

  • All terminal events from Event Registry (see Events section), e.g. "market.prices", "order.status", "theme.change", etc.

app.on("permission.change", (payload) => {
  // payload.permissions: AppPermission[]
});

app.on("market.prices", ({ prices, timestamp }) => {
  console.log(prices, timestamp);
});

Permissions

  • requestPermissions(permissions: AppPermission[]): Promise<AppPermission[]>

  • hasPermission(p: AppPermission): boolean

import { AppPermission } from "@based-one/miniapp-sdk";

const approved = await app.requestPermissions([
  AppPermission.READ_MARKET_DATA,
  AppPermission.PLACE_ORDERS,
]);

if (app.hasPermission(AppPermission.PLACE_ORDERS)) {
  // safe to trade
}

AppPermission values

  • Market data: READ_MARKET_DATA, READ_ORDERBOOK, READ_CANDLES

  • Account: READ_ACCOUNT, READ_POSITIONS, READ_ORDERS, READ_BALANCE, READ_TRADES

  • Trading: PLACE_ORDERS, CANCEL_ORDERS, MODIFY_ORDERS, POPULATE_ORDERS

  • UI: READ_NAVIGATION, WRITE_NAVIGATION, WRITE_CHART, SEND_NOTIFICATIONS

  • User settings: READ_USER_SETTINGS, MODIFY_USER_SETTINGS

  • Advanced: EXECUTE_STRATEGIES, ACCESS_ANALYTICS

Permission categories

  • trading (high risk)

  • account_read (medium)

  • market_data (low)

  • navigation (medium)

  • notifications (low)

  • user_settings (medium)

Subscriptions

  • subscribe(type: MiniAppSubscriptionType, payload?: SubcriptionPayload): Promise<{ subscriptionId: string; unsubscribe: () => Promise }>

  • unsubscribe(subscriptionId: string): Promise

  • subscribeToMarkets(symbols: string[]): Promise<{ subscriptionId: string; unsubscribe: () => Promise }>

const { subscriptionId, unsubscribe } = await app.subscribeToMarkets(["BTC-USDT", "ETH-USDT"]);
app.on("market.prices", ({ prices }) => console.log(prices));
await unsubscribe();

Commands

Commands are validated with zod schemas before being sent. Use high-level helpers or sendCommand directly.

High-level helpers

  • placeOrder(order: SubmitOrderCommand): Promise<CommandResponse<"order.submit">>

  • placeOrders(orders: SubmitOrderCommand[]): Promise<CommandResponse<"order.submitMultiple">>

  • populateOrder(order: SubmitOrderCommand): Promise<CommandResponse<"order.populate">>

  • cancelOrder(orderId: string, symbol?: string): Promise<CommandResponse<"order.cancel">>

  • cancelOrders(orders: CancelOrderCommand[]): Promise<CommandResponse<"order.cancelMultiple">>

  • modifyOrder(orderId: string, modifications: { price?: number; quantity?: number; stopPrice?: number; trailingPercent?: number }): Promise<CommandResponse<"order.modify">>

  • getAccount(): Promise<CommandResponse<"wallet.balance">>

  • getPositions(symbol?: string): Promise<CommandResponse<"position.query">>

  • getOrders(symbol?: string, status?: "open" | "filled" | "cancelled" | "partial" | "all"): Promise<CommandResponse<"orders.query">>

  • getWalletAddress(): Promise

sendCommand

// Generic form
const resp = await app.sendCommand("order.submit", {
  symbol: "BTC",
  side: "buy",
  orderType: "limit",
  size: 0.1,
  price: 100000,
});

Command Registry

Supported command names and payload shapes (abbrev.):

  • order.populate: SubmitOrderCommand

  • order.submit: SubmitOrderCommand

  • order.submitMultiple: { orders: SubmitOrderCommand[] }

  • order.cancel: { orderId: string; symbol?: string }

  • order.cancelMultiple: { orders: { orderId: string; symbol?: string }[] }

  • order.modify: { orderId: string; modifications: { price?: number; quantity?: number; stopPrice?: number; trailingPercent?: number } }

  • orders.query: { symbol?: string; status?: "open" | "filled" | "cancelled" | "partial" | "all" }

  • leverage.query: { symbol: string } // note: registry uses update schema for validation

  • leverage.update: { symbol: string; leverage: number; isCross: boolean }

  • position.query: { symbol?: string }

  • position.close: { symbol: string; size?: number; percentage?: number; marketOrder?: boolean; limitPrice?: number }

  • wallet.balance: { assets?: string[]; includeSmallBalances?: boolean }

  • wallet.address: {}

  • notification.success | .error | .warning | .info: { title: string; message: string }

  • navigation.navigate: { symbol: string }

  • navigation.status: undefined

  • chart.draw: { symbol: string; drawings: Array<...> }

  • analytics.query: { metric: "pnl" | "volume" | "win_rate" | "sharpe" | "roi"; period: "1d" | "7d" | "30d" | "90d" | "all"; groupBy?: "day" | "week" | "month" | "symbol" }

SubmitOrderCommand

type SubmitOrderCommand = {
  symbol: string;
  side: "buy" | "sell";
  orderType: "market" | "limit" | "stop" | "stop_limit";
  size?: number;
  price?: number;
  stopPrice?: number;
  reduceOnly?: boolean;
  postOnly?: boolean;
  tpsl?: { tpPrice?: number | null; tpGainPercent?: number | null; slPrice?: number | null; slLossPercent?: number | null };
  tif?: "Gtc" | "Ioc" | "Alo";
};

ModifyOrderCommand

type ModifyOrderCommand = {
  orderId: string;
  modifications: {
    price?: number;
    quantity?: number;
    stopPrice?: number;
    trailingPercent?: number; // 0-100
  };
};

Orders/Positions/Wallet Query Types

type QueryOrdersCommand = { symbol?: string; status?: "open" | "filled" | "cancelled" | "partial" | "all" };
type PositionQueryCommand = { symbol?: string };
type WalletBalanceCommand = { assets?: string[]; includeSmallBalances?: boolean };

Command Responses

Every command resolves to CommandResponse<T>:

type CommandResponse<T extends string = string> = {
  success: boolean;
  data?: any; // typed per command below
  error?: string;
  timestamp?: number;
};

Per-command response payloads:

  • order.submit → { cloid: string; statuses: [OrderStatus] }

  • order.submitMultiple → { results: BatchOrderResult[]; successCount: number; failureCount: number }

  • order.cancel → undefined

  • order.cancelMultiple → { results: BatchCancelResult[]; successCount: number; failureCount: number }

  • order.modify → undefined

  • order.populate → undefined

  • orders.query → { orders: Order[]; totalCount: number }

  • leverage.update → { symbol: string; leverage: number; isCross: boolean }

  • leverage.query → { symbol: string; leverage: number; isCross: boolean }

  • position.query → { positions: Position[]; totalUnrealizedPnl: number; totalMargin: number }

  • position.close → { cloid: string; statuses: [OrderStatus] }

  • wallet.balance → { assets: string[]; positions: Position[] }

  • wallet.address → { address: string }

  • notification.* → undefined

  • navigation.navigate → undefined

  • navigation.status → { symbol?: string }

  • chart.draw → undefined

  • analytics.query → same shape as request (metric/period/groupBy)

Important types

type OrderStatus = { resting: { oid: string } } | { filled: { oid: string; totalSz: string; avgPx: string } } | { error: string };
type BatchOrderResult =
  | { index: number; success: true; status: { resting: { oid: string } } | { filled: { oid: string; totalSz: string; avgPx: string } } }
  | { index: number; success: false; status: { error: string } };
type CancelStatus = { success: { oid: string } } | { error: string };

Type guards (helpers)

import { isRestingStatus, isFilledStatus, isErrorStatus, isCancelSuccess, isCancelError } from "@based-one/miniapp-sdk";

if (isFilledStatus(status)) {
  console.log(status.filled.avgPx);
}

Events

The terminal emits events; the SDK relays them via app.on(eventType, handler).

Event types and payloads:

  • market.ticker: { symbol, price, volume24h, high24h, low24h, change24h, changePercent24h, timestamp }

  • market.bbo: { symbol, price, bid, ask, spread, timestamp }

  • market.prices: { prices: Record<string, string>; timestamp }

  • market.trades: { symbol, trades: Array<{ price, size, side, id, timestamp }> }

  • order.status: { orderId, symbol, status, side, orderType, price?, quantity, filledQuantity, remainingQuantity, averagePrice?, timestamp, message? }

  • order.update: { orderId, symbol, side, price, quantity, fee, feeAsset, timestamp, tradeId }

  • position.update: Array<{ symbol, size, entryPrice, leverage, positionValue, liquidationPx, pnl, timestamp }>

  • wallet.update: { asset, free, locked, total, usdValue, timestamp }

  • wallet.switch: { address }

  • theme.change: { colors, typography, mode }

  • navigation.update: { symbol }

  • auth.status: { authenticated, address?, permissions?, sessionExpiry? }

  • permission.change: { permissions: AppPermission[] }

  • permission.granted: { permissions: AppPermission[] }

  • connection.status: { connected, sessionId, appId, timestamp }

  • notification: { id, type: "info" | "success" | "warning" | "error", title, message, timestamp, actions? }

Example:

app.on("order.status", (e) => {
  if (e.status === "filled") console.log("Filled!", e.orderId);
});
``;

## Message Envelopes (advanced)
Low-level shapes used over postMessage. Most users do not need this.

### MiniAppMessage → Terminal
```ts
type MiniAppMessage = {
  id: string;
  timestamp: number;
  appId: string;
  sessionId?: string;
  type: "connect" | "disconnect" | "command" | "subscribe" | "unsubscribe" | "heartbeat" | "requestPermission" | "getPermissions";
  command?: string; // command or subscription type
  payload?: any;
  signature?: string;
  nonce?: string;
  source: "based-miniapp";
};

MiniAppResponse ← Terminal

type MiniAppResponse<T = any> = {
  id: string;
  type: "response" | "event" | "connected" | "disconnected" | "error";
  correlationId: string | undefined;
  timestamp: number;
  success: boolean;
  data?: T;
  error?: string;
};

Examples

Batch submit and cancel

// Submit multiple
const submit = await app.placeOrders([
  { symbol: "BTC-USDT", side: "buy", orderType: "limit", size: 0.1, price: 60000 },
  { symbol: "ETH-USDT", side: "sell", orderType: "limit", size: 1, price: 3500 },
]);

// Cancel by specific ids
await app.cancelOrders([
  { orderId: "abc123" },
  { orderId: "def456", symbol: "ETH-USDT" },
]);

Querying account state

const { data: orders } = await app.getOrders(undefined, "open");
const { data: positions } = await app.getPositions();
const walletAddress = await app.getWalletAddress();

Notes

  • All commands time out after ~30s if no response is received.

  • When appSecret is provided, messages are signed and verified.

  • Heartbeats are sent every 30s while connected.

Last updated