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 nullMiniAppConfig
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
appSecretis provided, messages are signed and verified.Heartbeats are sent every 30s while connected.
Last updated