HTML Sample MiniApp
The following represents a sample pure HTML MiniApp:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Trader MiniApp</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0a0a0a;
color: #ffffff;
padding: 16px;
min-height: 100vh;
}
.container {
max-width: 600px;
margin: 0 auto;
}
.header {
text-align: center;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #333;
}
.status {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 12px;
background: #1a1a1a;
border-radius: 8px;
font-size: 14px;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 8px;
}
.status-dot.connected { background: #00ff88; }
.status-dot.disconnected { background: #ff4444; }
.card {
background: #1a1a1a;
border-radius: 12px;
padding: 20px;
margin-bottom: 16px;
border: 1px solid #333;
}
.card h3 {
margin-bottom: 16px;
color: #00ff88;
font-size: 18px;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 4px;
font-size: 14px;
color: #ccc;
}
.form-group input, .form-group select {
width: 100%;
padding: 12px;
background: #0a0a0a;
border: 1px solid #444;
border-radius: 6px;
color: #fff;
font-size: 14px;
}
.form-group input:focus, .form-group select:focus {
outline: none;
border-color: #00ff88;
}
.button {
background: #00ff88;
color: #000;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
.button:hover {
background: #00dd77;
transform: translateY(-1px);
}
.button:disabled {
background: #444;
color: #888;
cursor: not-allowed;
transform: none;
}
.button.secondary {
background: #333;
color: #fff;
}
.button.secondary:hover {
background: #444;
}
.button.danger {
background: #ff4444;
color: #fff;
}
.button.danger:hover {
background: #dd3333;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.log {
max-height: 200px;
overflow-y: auto;
background: #0a0a0a;
border: 1px solid #333;
border-radius: 6px;
padding: 12px;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 12px;
line-height: 1.4;
}
.log-entry {
margin-bottom: 4px;
padding: 4px 0;
}
.log-entry.info { color: #00ff88; }
.log-entry.error { color: #ff4444; }
.log-entry.warning { color: #ffaa00; }
.log-entry.debug { color: #888; }
.permissions-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 8px;
margin-top: 12px;
}
.permission-item {
background: #0a0a0a;
border: 1px solid #333;
border-radius: 6px;
padding: 8px 12px;
font-size: 12px;
text-align: center;
}
.permission-item.granted {
border-color: #00ff88;
color: #00ff88;
}
.market-data {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 12px;
margin-top: 12px;
}
.market-item {
background: #0a0a0a;
border: 1px solid #333;
border-radius: 6px;
padding: 12px;
text-align: center;
}
.market-item .symbol {
font-size: 16px;
font-weight: 600;
margin-bottom: 4px;
}
.market-item .price {
font-size: 14px;
color: #00ff88;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 Simple Trader</h1>
<p>MiniApp for Based.One Terminal</p>
</div>
<div class="status">
<div style="display: flex; align-items: center;">
<div class="status-dot" id="connectionStatus"></div>
<span id="connectionText">Disconnected</span>
</div>
<div>
<span>Session: </span>
<code id="sessionId">-</code>
</div>
</div>
<!-- Trading Form -->
<div class="card">
<h3>📈 Place Order</h3>
<div class="form-group">
<label for="symbol">Symbol</label>
<select id="symbol">
<option value="ETH">ETH</option>
<option value="BTC">BTC</option>
<option value="SOL">SOL</option>
</select>
</div>
<div class="grid">
<div class="form-group">
<label for="side">Side</label>
<select id="side">
<option value="buy">Buy</option>
<option value="sell">Sell</option>
</select>
</div>
<div class="form-group">
<label for="orderType">Order Type</label>
<select id="orderType">
<option value="market">Market</option>
<option value="limit">Limit</option>
</select>
</div>
</div>
<div class="grid">
<div class="form-group">
<label for="size">Size</label>
<input type="number" id="size" step="0.001" placeholder="0.1">
</div>
<div class="form-group">
<label for="price">Price (if limit)</label>
<input type="number" id="price" step="0.01" placeholder="2500.00">
</div>
</div>
<div class="grid">
<button class="button" id="placeOrderBtn" onclick="placeOrder()">
Place Order
</button>
<button class="button danger" id="cancelAllBtn" onclick="cancelAllOrders()">
Cancel All
</button>
</div>
</div>
<!-- Market Data -->
<div class="card">
<h3>📊 Market Data</h3>
<button class="button secondary" onclick="subscribeToMarketData()">
Subscribe to Market Data
</button>
<div class="market-data" id="marketData">
<!-- Market data will be populated here -->
</div>
</div>
<!-- Permissions -->
<div class="card">
<h3>🔐 Permissions</h3>
<button class="button secondary" onclick="requestAllPermissions()">
Request Permissions
</button>
<div class="permissions-list" id="permissionsList">
<!-- Permissions will be populated here -->
</div>
</div>
<!-- Debug Log -->
<div class="card">
<h3>🐛 Debug Log</h3>
<button class="button secondary" onclick="clearLog()" style="margin-bottom: 12px;">
Clear Log
</button>
<div class="log" id="debugLog">
<!-- Debug messages will appear here -->
</div>
</div>
</div>
<script>
// MiniApp SDK Integration
let isConnected = false;
let sessionId = null;
let permissions = new Set();
// Initialize connection when page loads
window.addEventListener('load', () => {
initializeMiniApp();
});
// Initialize the MiniApp connection
function initializeMiniApp() {
log('🚀 Initializing Simple Trader MiniApp...', 'info');
// Set as connecting initially
updateConnectionStatus(false);
// Request connection to the parent terminal
sendToParent({
type: 'CONNECT',
appId: 'simple-trader',
appName: 'Simple Trader',
version: '1.0.0'
});
// Also check if we're already in a connected state by trying to communicate
// Sometimes the parent connection happens before the iframe fully loads
setTimeout(() => {
if (!isConnected) {
log('🔄 Retrying connection...', 'info');
sendToParent({
type: 'CONNECT',
appId: 'simple-trader',
appName: 'Simple Trader',
version: '1.0.0'
});
}
}, 2000);
}
// Send message to parent terminal
function sendToParent(data) {
if (window.parent && window.parent !== window) {
const message = {
id: generateId(),
timestamp: Date.now(),
appId: 'simple-trader',
sessionId: sessionId,
...data
};
log(`📤 Sending: ${data.type}`, 'debug');
window.parent.postMessage(message, '*');
} else {
log('❌ No parent window found', 'error');
}
}
// Listen for messages from parent terminal
window.addEventListener('message', (event) => {
try {
const message = event.data;
if (!message || typeof message !== 'object') return;
log(`📥 Received: ${message.type || 'unknown'}`, 'debug');
handleParentMessage(message);
} catch (error) {
log(`❌ Error handling message: ${error.message}`, 'error');
}
});
// Handle messages from parent terminal
function handleParentMessage(message) {
log(`📥 Handling message type: ${message.type}`, 'debug');
switch (message.type) {
case 'connected':
case 'CONNECTED':
handleConnection(message);
break;
case 'disconnected':
case 'DISCONNECTED':
handleDisconnection();
break;
case 'permissionGranted':
case 'PERMISSION_GRANTED':
handlePermissionGranted(message);
break;
case 'permissionsLoaded':
case 'PERMISSIONS_LOADED':
handlePermissionsLoaded(message);
break;
case 'permissionDenied':
case 'PERMISSION_DENIED':
handlePermissionDenied(message);
break;
case 'marketData':
case 'MARKET_DATA':
handleMarketData(message);
break;
case 'orderResponse':
case 'ORDER_RESPONSE':
handleOrderResponse(message);
break;
case 'error':
case 'ERROR':
handleError(message);
break;
case 'PERMISSION_DENIED':
handlePermissionDeniedError(message);
break;
// Handle PostMessage responses
case undefined:
// Check for response structure
if (message.success !== undefined && message.correlationId) {
log('📨 Received response message', 'debug');
// This could be a response to our CONNECT message
if (message.success && message.data && message.data.connected) {
handleConnection({
type: 'connected',
sessionId: message.data.sessionId,
success: true
});
}
} else {
log(`🤔 Unknown message structure:`, 'warning');
log(JSON.stringify(message, null, 2), 'debug');
}
break;
default:
log(`🤔 Unknown message type: ${message.type}`, 'warning');
log('Full message:', 'debug');
log(JSON.stringify(message, null, 2), 'debug');
}
}
// Handle connection establishment
function handleConnection(message) {
isConnected = true;
sessionId = message.sessionId || 'connected';
updateConnectionStatus(true);
document.getElementById('sessionId').textContent = sessionId;
log('✅ Connected to Based.One Terminal', 'info');
log(`🔗 Session ID: ${sessionId}`, 'debug');
// Query existing permissions first, then request basic permissions after connection
setTimeout(() => {
queryExistingPermissions();
}, 500);
// Request basic permissions as fallback after a delay
setTimeout(() => {
requestBasicPermissions();
}, 1500);
}
// Handle disconnection
function handleDisconnection() {
isConnected = false;
sessionId = null;
permissions.clear();
updateConnectionStatus(false);
document.getElementById('sessionId').textContent = '-';
log('❌ Disconnected from terminal', 'warning');
}
// Handle permission granted
function handlePermissionGranted(message) {
const permission = message.permission;
permissions.add(permission);
log(`✅ Permission granted: ${permission}`, 'info');
updatePermissionsDisplay();
}
// Handle permissions loaded (bulk permission status)
function handlePermissionsLoaded(message) {
const loadedPermissions = message.permissions || [];
log(`📋 Loaded ${loadedPermissions.length} existing permissions`, 'info');
loadedPermissions.forEach(permission => {
permissions.add(permission);
log(`✅ Existing permission: ${permission}`, 'debug');
});
updatePermissionsDisplay();
}
// Handle permission denied
function handlePermissionDenied(message) {
const permission = message.permission;
log(`❌ Permission denied: ${permission}`, 'warning');
}
// Handle market data updates
function handleMarketData(message) {
const data = message.data;
log(`📊 Market data: ${data.symbol} = $${data.price}`, 'info');
updateMarketDataDisplay(data);
}
// Handle order responses
function handleOrderResponse(message) {
if (message.success) {
log(`✅ Order placed: ${message.orderId}`, 'info');
} else {
log(`❌ Order failed: ${message.error}`, 'error');
}
}
// Handle errors
function handleError(message) {
const error = message.error || 'Unknown error';
log(`❌ Error: ${error}`, 'error');
// Check if this is a permission-related error
if (error.includes('Permission denied') || error.includes('PERMISSION_DENIED')) {
handlePermissionError(error);
}
}
// Handle permission denied errors by automatically requesting permissions
function handlePermissionDeniedError(message) {
const error = message.error || 'Permission denied';
log(`🔐 Permission denied: ${error}`, 'warning');
handlePermissionError(error);
}
// Handle permission errors by requesting the missing permissions
function handlePermissionError(errorMessage) {
let requiredPermissions = [];
let actionDescription = 'perform this action';
// Parse error message to determine required permissions
if (errorMessage.includes('place_orders') || errorMessage.includes('placing orders')) {
requiredPermissions = ['place_orders', 'read_balance', 'read_positions'];
actionDescription = 'place orders';
} else if (errorMessage.includes('cancel_orders') || errorMessage.includes('canceling orders')) {
requiredPermissions = ['cancel_orders'];
actionDescription = 'cancel orders';
} else if (errorMessage.includes('read_market_data') || errorMessage.includes('market data')) {
requiredPermissions = ['read_market_data'];
actionDescription = 'access market data';
} else if (errorMessage.includes('read_positions') || errorMessage.includes('positions')) {
requiredPermissions = ['read_positions'];
actionDescription = 'read positions';
} else if (errorMessage.includes('read_orders') || errorMessage.includes('orders')) {
requiredPermissions = ['read_orders'];
actionDescription = 'read orders';
} else if (errorMessage.includes('read_account') || errorMessage.includes('account')) {
requiredPermissions = ['read_account', 'read_balance'];
actionDescription = 'access account information';
} else {
// Default: request common trading permissions
requiredPermissions = ['place_orders', 'cancel_orders', 'read_market_data', 'read_positions'];
actionDescription = 'perform trading operations';
}
if (requiredPermissions.length > 0) {
log(`🔄 Auto-requesting permissions for: ${actionDescription}`, 'info');
requestSpecificPermissions(requiredPermissions, actionDescription);
}
}
// Query existing permissions on first load
function queryExistingPermissions() {
log('🔍 Querying existing permissions...', 'info');
sendToParent({
type: 'GET_PERMISSIONS',
payload: {}
});
}
// Request basic permissions
function requestBasicPermissions() {
const basicPermissions = [
'read_market_data',
'place_orders',
'cancel_orders',
'read_positions'
];
basicPermissions.forEach(permission => {
sendToParent({
type: 'REQUEST_PERMISSION',
payload: {
permissions: [permission]
}
});
});
}
// Request specific permissions with context
function requestSpecificPermissions(requiredPermissions, actionDescription) {
log(`🔐 Requesting permissions for: ${actionDescription}`, 'info');
sendToParent({
type: 'REQUEST_PERMISSION',
payload: {
permissions: requiredPermissions
}
});
}
// Request all permissions
function requestAllPermissions() {
const allPermissions = [
'read_market_data',
'read_orderbook',
'read_trades',
'read_candles',
'read_account',
'read_positions',
'read_orders',
'read_balance',
'place_orders',
'cancel_orders',
'modify_orders',
'read_user_settings'
];
sendToParent({
type: 'REQUEST_PERMISSION',
payload: {
permissions: allPermissions
}
});
}
// Place order
function placeOrder() {
if (!isConnected) {
log('❌ Not connected to terminal', 'error');
return;
}
if (!permissions.has('place_orders')) {
log('❌ Missing place_orders permission - requesting...', 'warning');
requestSpecificPermissions(['place_orders'], 'place an order');
return;
}
const symbol = document.getElementById('symbol').value;
const side = document.getElementById('side').value;
const orderType = document.getElementById('orderType').value;
const size = parseFloat(document.getElementById('size').value);
const price = parseFloat(document.getElementById('price').value);
if (!size || size <= 0) {
log('❌ Invalid order size', 'error');
return;
}
if (orderType === 'limit' && (!price || price <= 0)) {
log('❌ Invalid price for limit order', 'error');
return;
}
const orderData = {
symbol,
side,
orderType,
size,
...(orderType === 'limit' && { price })
};
sendToParent({
type: 'COMMAND',
action: 'placeOrder',
payload: orderData
});
log(`📝 Placing ${side} order: ${size} ${symbol} @ ${orderType}`, 'info');
}
// Cancel all orders
function cancelAllOrders() {
if (!isConnected) {
log('❌ Not connected to terminal', 'error');
return;
}
if (!permissions.has('cancel_orders')) {
log('❌ Missing cancel_orders permission - requesting...', 'warning');
requestSpecificPermissions(['cancel_orders'], 'cancel orders');
return;
}
sendToParent({
type: 'COMMAND',
action: 'cancelAllOrders',
payload: {}
});
log('🗑️ Cancelling all orders...', 'info');
}
// Subscribe to market data
function subscribeToMarketData() {
if (!isConnected) {
log('❌ Not connected to terminal', 'error');
return;
}
if (!permissions.has('read_market_data')) {
log('❌ Missing read_market_data permission - requesting...', 'warning');
requestSpecificPermissions(['read_market_data'], 'subscribe to market data');
return;
}
const symbols = ['ETH', 'BTC', 'SOL'];
symbols.forEach(symbol => {
sendToParent({
type: 'SUBSCRIBE',
payload: {
dataType: 'MARKET_DATA',
symbol: symbol
}
});
});
log('📡 Subscribed to market data', 'info');
}
// Update connection status display
function updateConnectionStatus(connected) {
const statusDot = document.getElementById('connectionStatus');
const statusText = document.getElementById('connectionText');
if (connected) {
statusDot.className = 'status-dot connected';
statusText.textContent = 'Connected';
} else {
statusDot.className = 'status-dot disconnected';
statusText.textContent = 'Disconnected';
}
}
// Update permissions display
function updatePermissionsDisplay() {
const container = document.getElementById('permissionsList');
container.innerHTML = '';
const allPermissions = [
'read_market_data',
'place_orders',
'cancel_orders',
'read_positions',
'read_orders',
'read_balance'
];
allPermissions.forEach(permission => {
const item = document.createElement('div');
item.className = `permission-item ${permissions.has(permission) ? 'granted' : ''}`;
item.textContent = permission.replace(/_/g, ' ');
container.appendChild(item);
});
}
// Update market data display
function updateMarketDataDisplay(data) {
const container = document.getElementById('marketData');
// Only update display if we have valid price data
const price = (data.price != null && !isNaN(data.price) && data.price > 0) ? data.price : null;
if (price === null) {
log(`⚠️ Ignoring invalid price data for ${data.symbol}: ${data.price}`, 'debug');
return; // Don't update display with invalid data
}
let item = container.querySelector(`[data-symbol="${data.symbol}"]`);
if (!item) {
item = document.createElement('div');
item.className = 'market-item';
item.setAttribute('data-symbol', data.symbol);
container.appendChild(item);
}
item.innerHTML = `
<div class="symbol">${data.symbol}</div>
<div class="price">$${price.toFixed(2)}</div>
`;
}
// Utility functions
function generateId() {
return 'msg_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now();
}
function log(message, level = 'info') {
const logContainer = document.getElementById('debugLog');
const entry = document.createElement('div');
entry.className = `log-entry ${level}`;
const timestamp = new Date().toLocaleTimeString();
entry.textContent = `[${timestamp}] ${message}`;
logContainer.appendChild(entry);
logContainer.scrollTop = logContainer.scrollHeight;
// Limit log entries
while (logContainer.children.length > 100) {
logContainer.removeChild(logContainer.firstChild);
}
}
function clearLog() {
document.getElementById('debugLog').innerHTML = '';
}
// Handle order type change
document.getElementById('orderType').addEventListener('change', (e) => {
const priceField = document.getElementById('price');
if (e.target.value === 'market') {
priceField.disabled = true;
priceField.style.opacity = '0.5';
} else {
priceField.disabled = false;
priceField.style.opacity = '1';
}
});
// Initialize price field state
document.getElementById('orderType').dispatchEvent(new Event('change'));
</script>
</body>
</html>
Last updated