MiniApp Architecture
MiniApp Architecture Guide
This guide provides a comprehensive overview of how Based.One MiniApps work under the hood, including communication patterns, security models, and architectural decisions.
High-Level Architecture
System Overview
┌─────────────────────────────────────────────────────────────────────┐
│ Based.One Terminal │
├─────────────────────────────────────────────────────────────────────┤
│ ┌───────────────┐ ┌─────────────────────────────────────────────┐ │
│ │ MiniApp │ │ Core Systems │ │
│ │ Container │ │ │ │
│ │ │ │ ┌─────────────┐ ┌─────────────────────────┐ │ │
│ │ ┌─────────┐ │◄─┤ │ Permission │ │ Trading Engine │ │ │
│ │ │Your App │ │ │ │ Manager │ │ │ │ │
│ │ │ │ │─►│ │ │ │ • Order Management │ │ │
│ │ │ │ │ │ └─────────────┘ │ • Risk Controls │ │ │
│ │ └─────────┘ │ │ │ • Portfolio Tracking │ │ │
│ │ │ │ ┌─────────────┐ │ │ │ │
│ │ iframe │ │ │ Market Data │ └─────────────────────────┘ │ │
│ │ sandbox │ │◄─┤ Service │ │ │
│ │ │ │ │ │ ┌─────────────────────────┐ │ │
│ └───────────────┘ │ │ • WebSocket │ │ Account Service │ │ │
│ │ │ • REST API │ │ │ │ │
│ │ │ • Caching │ │ • Balance Management │ │ │
│ │ │ │ │ • Position Tracking │ │ │
│ │ └─────────────┘ │ • Transaction History │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────┘ │ │
└─────────────────────────────────────────────────────────────────────┘
Communication Architecture
1. PostMessage Communication
MiniApps run in sandboxed iframes and communicate with the terminal via the PostMessage API:
// MiniApp to Terminal
window.parent.postMessage({
type: 'MINIAPP_REQUEST',
requestId: 'uuid-123',
payload: {
action: 'place_order',
data: {
symbol: 'ETH',
side: 'buy',
quantity: 0.1,
type: 'market'
}
}
}, '*');
// Terminal to MiniApp
window.addEventListener('message', (event) => {
if (event.data.type === 'MINIAPP_RESPONSE') {
const { requestId, success, data, error } = event.data;
// Handle response
}
});
2. Message Flow Diagram
MiniApp Terminal Trading Engine
│ │ │
│ 1. place_order │ │
├─────────────────────────►│ │
│ │ 2. validate_permissions │
│ ├──────────────┐ │
│ │◄─────────────┘ │
│ │ 3. execute_order │
│ ├─────────────────────────►│
│ │ 4. order_confirmation │
│ 5. order_response │◄─────────────────────────┤
│◄─────────────────────────┤ │
│ │ 6. broadcast_update │
│ 7. market_data_update │ │
│◄─────────────────────────┤ │
3. Event-Driven Architecture
The SDK uses an event-driven pattern for real-time updates:
class SimpleMiniAppClient extends EventEmitter {
constructor(config: SimpleConfig) {
super();
this.setupMessageHandlers();
}
private setupMessageHandlers() {
window.addEventListener('message', (event) => {
const { type, data } = event.data;
switch (type) {
case 'MARKET_DATA_UPDATE':
this.emit('marketData', data);
break;
case 'ORDER_CONFIRMATION':
this.emit('orderConfirmation', data);
break;
case 'ACCOUNT_UPDATE':
this.emit('accountUpdate', data);
break;
}
});
}
}
Security Architecture
1. Permission Model
MiniApps operate under a strict permission-based security model:
interface PermissionScope {
// Market data access
market_data_read: boolean;
// Trading permissions
trading_read: boolean; // View orders, positions
trading_write: boolean; // Place orders
trading_cancel: boolean; // Cancel orders
// Account permissions
account_read: boolean; // View balances, history
// Advanced permissions
api_access: boolean; // External API calls
}
2. Permission Request Flow
// Simple API handles permissions automatically
const miniApp = createSimpleMiniApp({
appId: 'my-app',
requestedPermissions: ['trading_read', 'trading_write', 'market_data_read']
});
// Permission request is automatic
await miniApp.requestTradingPermissions();
// Manual permission checking
if (await miniApp.hasPermission('trading_write')) {
await miniApp.placeOrder(orderData);
} else {
console.log('Trading permission required');
}
3. Sandboxing
MiniApps run in isolated iframe sandboxes with restricted capabilities:
<iframe
src="miniapp-url"
sandbox="allow-scripts allow-same-origin allow-forms"
style="width: 100%; height: 100%; border: none;">
</iframe>
Sandbox Restrictions:
No direct DOM access to parent window
No access to terminal's localStorage/cookies
Limited network access (proxied through terminal)
No popup windows or modal dialogs
Communication only via PostMessage API
Data Flow Architecture
1. Real-Time Data Pipeline
External Data Sources → Terminal → MiniApp SDK → Your Application
↓
┌─────────────┐
│ WebSocket │ ← Market feeds, order updates
│ Manager │
└─────────────┘
↓
┌─────────────┐
│ Data │ ← Normalization, validation
│ Processor │
└─────────────┘
↓
┌─────────────┐
│ Event │ ← Broadcast to subscribed MiniApps
│ Dispatcher │
└─────────────┘
↓
┌─────────────┐
│ MiniApp │ ← Your event handlers
│ SDK │
└─────────────┘
2. State Management
The SDK provides different state management patterns:
Simple API - Event-Based:
const miniApp = createSimpleMiniApp({ appId: 'my-app' });
// State is managed through events
let currentPrice = 0;
miniApp.on('marketData', (data) => {
if (data.symbol === 'ETH') {
currentPrice = data.price;
}
});
React API - Hook-Based:
function TradingComponent() {
// State is managed by hooks
const { marketData, positions, balance } = useSimpleMiniApp({
appId: 'trading-ui',
symbols: ['ETH', 'BTC']
});
// Automatic re-renders on state changes
return <div>ETH: ${marketData.ETH?.price}</div>;
}
SDK Architecture Layers
1. Layered Architecture
┌─────────────────────────────────────────────────────┐
│ React Components │ ← SimpleOrderForm, etc.
├─────────────────────────────────────────────────────┤
│ React Hooks │ ← useSimpleTrade, etc.
├─────────────────────────────────────────────────────┤
│ Simple API │ ← createSimpleMiniApp
├─────────────────────────────────────────────────────┤
│ Core SDK │ ← MiniAppSDK class
├─────────────────────────────────────────────────────┤
│ Communication Layer │ ← PostMessage handling
├─────────────────────────────────────────────────────┤
│ Browser APIs │ ← iframe, postMessage
└─────────────────────────────────────────────────────┘
2. Simple API Architecture
// High-level Simple API
class SimpleMiniAppClient {
private coreSDK: MiniAppSDK;
private eventEmitter: EventEmitter;
private permissionManager: PermissionManager;
constructor(config: SimpleConfig) {
this.coreSDK = new MiniAppSDK(config);
this.setupAutomaticPermissions();
this.setupEventBridge();
}
// Convenience methods wrap core SDK
async quickBuy(symbol: string, quantity: number) {
await this.ensureTradingPermissions();
return this.coreSDK.commands.trading.placeOrder({
symbol,
side: 'buy',
type: 'market',
quantity
});
}
}
Performance Architecture
1. Connection Management
class ConnectionManager {
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
private reconnectDelay = 1000;
async connect() {
try {
await this.establishConnection();
this.reconnectAttempts = 0;
} catch (error) {
await this.handleConnectionFailure();
}
}
private async handleConnectionFailure() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts);
setTimeout(() => this.connect(), delay);
}
}
}
2. Message Queuing
class MessageQueue {
private queue: Message[] = [];
private processing = false;
async sendMessage(message: Message) {
this.queue.push(message);
if (!this.processing) {
await this.processQueue();
}
}
private async processQueue() {
this.processing = true;
while (this.queue.length > 0) {
const message = this.queue.shift()!;
try {
await this.sendMessageToTerminal(message);
} catch (error) {
// Handle retry logic
if (message.retryCount < 3) {
message.retryCount++;
this.queue.unshift(message); // Retry first
}
}
}
this.processing = false;
}
}
3. Data Caching
class DataCache {
private cache = new Map<string, CacheEntry>();
private readonly TTL = 5000; // 5 seconds
get(key: string): any {
const entry = this.cache.get(key);
if (entry && Date.now() - entry.timestamp < this.TTL) {
return entry.data;
}
return null;
}
set(key: string, data: any) {
this.cache.set(key, {
data,
timestamp: Date.now()
});
}
}
Error Handling Architecture
1. Error Classification
enum ErrorType {
CONNECTION_ERROR = 'connection',
PERMISSION_ERROR = 'permission',
VALIDATION_ERROR = 'validation',
TRADING_ERROR = 'trading',
NETWORK_ERROR = 'network'
}
class MiniAppError extends Error {
constructor(
public type: ErrorType,
message: string,
public code?: string,
public recoverable = true
) {
super(message);
this.name = 'MiniAppError';
}
}
2. Error Recovery Strategies
class ErrorHandler {
async handleError(error: MiniAppError, context: any) {
switch (error.type) {
case ErrorType.CONNECTION_ERROR:
return this.handleConnectionError(error);
case ErrorType.PERMISSION_ERROR:
return this.handlePermissionError(error);
case ErrorType.TRADING_ERROR:
return this.handleTradingError(error);
default:
throw error; // Re-throw unhandleable errors
}
}
private async handleConnectionError(error: MiniAppError) {
// Implement reconnection logic
await this.reconnect();
}
private async handlePermissionError(error: MiniAppError) {
// Request missing permissions
await this.requestPermissions();
}
}
Key Architectural Decisions
1. Why iframe Sandboxing?
Security: Complete isolation from terminal code
Stability: MiniApp crashes don't affect terminal
Flexibility: Support for any web technology
Updates: Independent deployment and versioning
2. Why PostMessage Communication?
Security: Controlled communication channel
Standards: Built on web standards
Reliability: Browser-native implementation
Debugging: Easy to inspect and debug
3. Why Event-Driven Design?
Real-time: Immediate updates from markets
Scalability: Efficient one-to-many broadcasting
Flexibility: Easy to add new event types
Performance: Non-blocking operations
4. Why Multiple API Layers?
Progressive Enhancement: Start simple, add complexity as needed
Developer Experience: Choose the right abstraction level
Maintenance: Clear separation of concerns
Testing: Each layer can be tested independently
Next Steps
Now that you understand the architecture:
Simple API Reference - Detailed API documentation
React Integration - Building UIs with React
Next: Simple API Reference → Complete API documentation
Last updated