Project Templates
Basic TELA App

Basic TELA App Template

Production-ready starter template using the XSWD connection patterns proven in tela_tests/app1. While the core WebSocket handling follows tested patterns, this template provides a modern dashboard UI designed for real applications, not API testing.

What's from tela_tests: XSWD connection, handshake, response handling
What's new: Modern UI, production features, dashboard components

πŸ“ Template Structure

basic-tela-app/
β”œβ”€β”€ index.html     # Main application (8.2KB)
β”œβ”€β”€ styles.css     # TELA design system (6.8KB)  
β”œβ”€β”€ app.js         # Core functionality (9.1KB)
└── README.md      # Instructions

Total size: ~24KB across 3 files (each under 18KB limit)

Proven XSWD Patterns from tela_tests/app1

  • βœ… Global socket variable and WebSocket connection
  • βœ… applicationData handshake structure
  • βœ… Traditional function declarations (tested and working)
  • βœ… addEventListener event handling
  • βœ… response.accepted / response.result / response.error logic

New Production Features

  • πŸ†• Modern card-based dashboard UI
  • πŸ†• Wallet and network statistics display
  • πŸ†• Professional error handling components
  • πŸ†• Responsive design system

πŸ“„ index.html

File size: ~8.2KB

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My TELA Application</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="app-container">
        <!-- Header Section -->
        <div class="enhanced-card">
            <div class="card-header">
                <div style="display:flex;justify-content:space-between;align-items:center;">
                    <div>
                        <h1 style="color:#fff;font-size:1.8rem;font-weight:700;margin:0;">πŸš€ My TELA App</h1>
                        <div class="section-info">A decentralized application on DERO blockchain</div>
                    </div>
                    <div id="connection-status" class="status-indicator status-info">
                        <div class="status-dot"></div>
                        <span>Connecting...</span>
                    </div>
                </div>
            </div>
        </div>
 
        <!-- Wallet Information -->
        <div class="enhanced-card" id="wallet-section" style="display:none;">
            <div class="card-header">
                <h2 style="color:#52c8db;font-size:1.6rem;font-weight:700;margin:0;">πŸ’° Wallet Information</h2>
                <div class="section-info">Connected wallet details and balance</div>
            </div>
            <div class="card-content">
                <div class="stats-grid">
                    <div class="enhanced-stat-card">
                        <div id="wallet-balance" style="font-size:1.8rem;font-weight:700;color:#52c8db;margin-bottom:0.5rem;">
                            Loading...
                        </div>
                        <div style="color:#b3b3b3;font-size:0.9rem;text-transform:uppercase;letter-spacing:0.5px;">
                            Balance (DERO)
                        </div>
                    </div>
                    <div class="enhanced-stat-card">
                        <div id="wallet-address" style="font-size:1rem;font-weight:600;color:#52c8db;margin-bottom:0.5rem;word-break:break-all;">
                            Loading...
                        </div>
                        <div style="color:#b3b3b3;font-size:0.9rem;text-transform:uppercase;letter-spacing:0.5px;">
                            Wallet Address
                        </div>
                    </div>
                </div>
            </div>
        </div>
 
        <!-- Network Information -->
        <div class="enhanced-card" id="network-section" style="display:none;">
            <div class="card-header">
                <h2 style="color:#b959b6;font-size:1.6rem;font-weight:700;margin:0;">🌐 Network Information</h2>
                <div class="section-info">Real-time DERO blockchain statistics</div>
            </div>
            <div class="card-content">
                <div class="stats-grid">
                    <div class="enhanced-stat-card" style="border-color:rgba(185,89,182,0.3);">
                        <div id="network-height" style="font-size:1.8rem;font-weight:700;color:#b959b6;margin-bottom:0.5rem;">
                            Loading...
                        </div>
                        <div style="color:#b3b3b3;font-size:0.9rem;text-transform:uppercase;letter-spacing:0.5px;">
                            Block Height
                        </div>
                    </div>
                    <div class="enhanced-stat-card" style="border-color:rgba(185,89,182,0.3);">
                        <div id="network-difficulty" style="font-size:1.4rem;font-weight:700;color:#b959b6;margin-bottom:0.5rem;">
                            Loading...
                        </div>
                        <div style="color:#b3b3b3;font-size:0.9rem;text-transform:uppercase;letter-spacing:0.5px;">
                            Difficulty
                        </div>
                    </div>
                    <div class="enhanced-stat-card" style="border-color:rgba(185,89,182,0.3);">
                        <div id="network-hashrate" style="font-size:1.4rem;font-weight:700;color:#b959b6;margin-bottom:0.5rem;">
                            Loading...
                        </div>
                        <div style="color:#b3b3b3;font-size:0.9rem;text-transform:uppercase;letter-spacing:0.5px;">
                            Est. Hash Rate
                        </div>
                    </div>
                </div>
            </div>
        </div>
 
        <!-- Actions Section -->
        <div class="enhanced-card" id="actions-section" style="display:none;">
            <div class="card-header">
                <h2 style="color:#4ade80;font-size:1.6rem;font-weight:700;margin:0;">⚑ Quick Actions</h2>
                <div class="section-info">Common operations and utilities</div>
            </div>
            <div class="card-content">
                <div class="actions">
                    <button class="btn-primary" onclick="refreshData()">πŸ”„ Refresh Data</button>
                    <button class="btn-success" onclick="showTransactionForm()">πŸ’Έ Send Transaction</button>
                    <button class="btn-warning" onclick="exportData()">πŸ“Š Export Data</button>
                </div>
            </div>
        </div>
 
        <!-- Error Display -->
        <div id="error-display" style="display:none;" class="status-indicator status-error">
            <div class="status-dot"></div>
            <span id="error-message">Error message will appear here</span>
        </div>
 
        <!-- Loading Overlay -->
        <div id="loading-overlay" style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);display:flex;align-items:center;justify-content:center;z-index:1000;">
            <div style="text-align:center;color:#52c8db;">
                <div style="font-size:2rem;margin-bottom:1rem;">πŸ”„</div>
                <div>Connecting to DERO wallet...</div>
            </div>
        </div>
    </div>
 
    <script src="app.js"></script>
</body>
</html>

app.js

File size: ~9.1KB - Based on proven tela_tests patterns

// Basic TELA Application - Based on working tela_tests/app1 patterns
// Uses proven JavaScript patterns that successfully deploy to TELA
 
// Global WebSocket connection (proven pattern from tela_tests)
let socket;
 
// XSWD application data (matches tela_tests structure)
const applicationData = {
    "id": "basic-tela-app-" + Date.now(),
    "name": "Basic TELA Application", 
    "description": "Simple TELA app with wallet integration",
    "url": "http://localhost:" + location.port
};
 
// Global variables for app state
let connected = false;
let walletData = null;
let networkData = null;
 
// Handle WebSocket connection (proven tela_tests pattern)
function connectWebSocket() {
    console.log('πŸ”— Connecting to DERO wallet via XSWD...');
    
    socket = new WebSocket("ws://localhost:44326/xswd");
 
    socket.addEventListener("open", function(event) {
        console.log("βœ… WebSocket connection established:", event);
        updateConnectionStatus("info", "πŸ”„ Requesting approval...");
        sendData(applicationData);
    });
 
    let address = "";
    let connecting = true;
 
    socket.addEventListener("message", function(event) {
        const response = JSON.parse(event.data);
        console.log("πŸ“¨ Response received:", response);
        
        if (response.accepted) {
            console.log("πŸŽ‰ Connection accepted:", response.message);
            connected = true;
            updateConnectionStatus("success", "βœ… Connected");
            
            // Request initial data
            sendData({
                jsonrpc: "2.0",
                id: "1",
                method: "GetAddress"
            });
        } else if (response.result) {
            const res = response.result;
            
            if (res.address) {
                address = res.address;
                console.log("πŸ’° Connected address:", address);
                walletData = { address: address };
                
                // Get balance
                sendData({
                    jsonrpc: "2.0", 
                    id: "2",
                    method: "GetBalance"
                });
                
                // Get network info
                sendData({
                    jsonrpc: "2.0",
                    id: "3", 
                    method: "GetInfo"
                });
                
            } else if (res.unlocked_balance !== undefined) {
                const balance = res.unlocked_balance / 100000;
                console.log("πŸ’° Balance:", balance.toFixed(5), "DERO");
                if (walletData) {
                    walletData.balance = balance;
                    walletData.unlocked = res.balance / 100000;
                }
                updateWalletDisplay();
                
            } else if (res.height) {
                console.log("πŸ“Š Network height:", res.height);
                networkData = {
                    height: res.height,
                    stableheight: res.stableheight,
                    difficulty: res.difficulty,
                    hashrate: Math.round(res.difficulty / 16)
                };
                updateNetworkDisplay();
                showAllSections();
                hideLoading();
                connecting = false;
            }
        } else if (response.error) {
            console.error("❌ Error:", response.error.message);
            showError("Error: " + response.error.message);
            if (connecting) updateConnectionStatus("error", "❌ Connection failed");
        }
    });
 
    socket.addEventListener("error", function(event) {
        console.error("🚨 WebSocket error:", event);
        showError("Connection error");
        updateConnectionStatus("error", "❌ Connection failed");
    });
 
    socket.addEventListener("close", function(event) {
        console.log("πŸ”Œ WebSocket closed:", event.code, event.reason);
        connected = false;
        updateConnectionStatus("error", "❌ Disconnected");
    });
}
 
// Send data through WebSocket (proven tela_tests pattern)
function sendData(data) {
    if (socket && socket.readyState === WebSocket.OPEN) {
        try {
            socket.send(JSON.stringify(data));
            if (data.method) {
                console.log(data.method, "request sent");
            }
        } catch (error) {
            console.error("Failed to send data:", error);
            showError("Failed to send request");
        }
    } else {
        console.log("WebSocket not open. State:", socket ? socket.readyState : "N/A");
        showError("Wallet not connected");
    }
}
 
// Update UI functions
function updateWalletDisplay() {
    if (walletData) {
        document.getElementById('wallet-balance').textContent = walletData.balance.toFixed(5);
        
        const addr = walletData.address;
        const shortAddr = addr.substring(0, 12) + '...' + addr.substring(addr.length - 12);
        document.getElementById('wallet-address').textContent = shortAddr;
    }
}
 
function updateNetworkDisplay() {
    if (networkData) {
        document.getElementById('network-height').textContent = networkData.height.toLocaleString();
        document.getElementById('network-difficulty').textContent = networkData.difficulty.toLocaleString();
        document.getElementById('network-hashrate').textContent = (networkData.hashrate / 1000000).toFixed(2) + ' MH/s';
    }
}
 
function showAllSections() {
    document.getElementById('wallet-section').style.display = 'block';
    document.getElementById('network-section').style.display = 'block';
    document.getElementById('actions-section').style.display = 'block';
}
 
function updateConnectionStatus(type, message) {
    const statusEl = document.getElementById('connection-status');
    statusEl.className = `status-indicator status-${type}`;
    statusEl.querySelector('span').textContent = message;
}
 
function showError(message) {
    console.error('🚨 Application error:', message);
    
    const errorEl = document.getElementById('error-display');
    document.getElementById('error-message').textContent = message;
    errorEl.style.display = 'flex';
    
    // Auto-hide after 8 seconds
    setTimeout(function() {
        errorEl.style.display = 'none';
    }, 8000);
}
 
function hideLoading() {
    document.getElementById('loading-overlay').style.display = 'none';
}
 
// Global functions for button actions (proven tela_tests pattern)
function refreshData() {
    if (!connected) {
        showError('Wallet not connected');
        return;
    }
 
    console.log('πŸ”„ Refreshing data...');
    
    // Refresh balance
    sendData({
        jsonrpc: "2.0",
        id: "refresh-balance",
        method: "GetBalance"
    });
    
    // Refresh network info
    sendData({
        jsonrpc: "2.0",
        id: "refresh-info",
        method: "GetInfo"
    });
}
 
function showTransactionForm() {
    alert('πŸ’‘ Transaction Demo\n\nThis would open a transaction form where users could:\nβ€’ Enter recipient address\nβ€’ Specify amount\nβ€’ Set transaction parameters\nβ€’ Confirm and send via XSWD\n\nImplement your transaction UI here!');
}
 
function exportData() {
    if (networkData && walletData) {
        const data = {
            timestamp: new Date().toISOString(),
            network: networkData,
            wallet: {
                address: walletData.address,
                balance: walletData.balance
            }
        };
        
        const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'tela-app-data.json';
        a.click();
        URL.revokeObjectURL(url);
    } else {
        showError('No data to export');
    }
}
 
// Initialize application when page loads (proven tela_tests pattern)
document.addEventListener('DOMContentLoaded', function() {
    console.log('πŸš€ Initializing TELA application...');
    connectWebSocket();
});
 
// Cleanup when page unloads
window.addEventListener('beforeunload', function() {
    if (socket) {
        socket.close();
    }
});

styles.css

File size: ~6.8KB - Same as before (CSS is not affected by JavaScript patterns)

/* TELA Basic App Styles - Complete CSS Framework */
 
/* Reset and Base */
* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}
 
html {
    scroll-behavior: smooth;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
 
body {
    background: linear-gradient(135deg, #0a0c0e, #15171a, #1d2024);
    min-height: 100vh;
    color: #ffffff;
    overflow-x: hidden;
}
 
/* Layout */
.app-container {
    width: 100%;
    max-width: 1200px;
    margin: 0 auto;
    padding: 1rem;
    min-height: 100vh;
}
 
/* Enhanced Card System */
.enhanced-card {
    background: rgba(0,0,0,0.4);
    border: 1px solid rgba(82,200,219,0.2);
    border-radius: 16px;
    backdrop-filter: blur(10px);
    margin: 1rem 0 2rem 0;
    overflow: hidden;
}
 
.card-header {
    padding: 1.5rem 1.5rem 1rem 1.5rem;
    border-bottom: 1px solid rgba(82,200,219,0.1);
}
 
.card-content {
    padding: 1.5rem;
}
 
.section-info {
    color: #b3b3b3;
    font-size: 0.9rem;
    margin-top: 0.5rem;
    line-height: 1.4;
}
 
/* Statistics Grid */
.stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 1rem;
    margin-bottom: 1rem;
}
 
.enhanced-stat-card {
    background: rgba(0,0,0,0.2);
    border: 1px solid rgba(82,200,219,0.3);
    border-radius: 8px;
    padding: 1.5rem;
    text-align: center;
    transition: all 0.2s ease;
}
 
.enhanced-stat-card:hover {
    border-color: rgba(82,200,219,0.5);
    transform: translateY(-2px);
}
 
/* Button System */
.btn-primary, .btn-success, .btn-warning, .btn-danger {
    padding: 0.6rem 1.2rem;
    border-radius: 6px;
    cursor: pointer;
    transition: all 0.2s ease;
    font-weight: 500;
    text-decoration: none;
    display: inline-block;
    font-size: 0.9rem;
    border: 1px solid;
}
 
.btn-primary {
    background: rgba(82,200,219,0.1);
    border-color: #52c8db;
    color: #52c8db;
}
 
.btn-primary:hover {
    background: rgba(82,200,219,0.2);
}
 
.btn-success {
    background: rgba(74,222,128,0.1);
    border-color: #4ade80;
    color: #4ade80;
}
 
.btn-success:hover {
    background: rgba(74,222,128,0.2);
}
 
.btn-warning {
    background: rgba(251,191,36,0.1);
    border-color: #fbbf24;
    color: #fbbf24;
}
 
.btn-warning:hover {
    background: rgba(251,191,36,0.2);
}
 
/* Status Indicators */
.status-indicator {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.6rem 1rem;
    border-radius: 20px;
    font-size: 0.8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
 
.status-success {
    background: rgba(74,222,128,0.1);
    border: 1px solid #4ade80;
    color: #4ade80;
}
 
.status-info {
    background: rgba(82,200,219,0.1);
    border: 1px solid #52c8db;
    color: #52c8db;
}
 
.status-warning {
    background: rgba(251,191,36,0.1);
    border: 1px solid #fbbf24;
    color: #fbbf24;
}
 
.status-error {
    background: rgba(239,68,68,0.1);
    border: 1px solid #ef4444;
    color: #ef4444;
}
 
.status-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
}
 
/* Actions Layout */
.actions {
    display: flex;
    gap: 0.75rem;
    flex-wrap: wrap;
    margin-top: 1rem;
}
 
/* Responsive Design */
@media (max-width: 768px) {
    .app-container {
        padding: 0.5rem;
    }
    
    .stats-grid {
        grid-template-columns: 1fr;
        gap: 0.75rem;
    }
    
    .actions {
        flex-direction: column;
    }
    
    .card-header, .card-content {
        padding: 1rem;
    }
    
    .enhanced-stat-card {
        padding: 1rem;
    }
}
 
/* Utility Classes */
.text-center { text-align: center; }
.mb-1 { margin-bottom: 1rem; }
.hidden { display: none; }

πŸ“‹ Template Architecture

βœ… XSWD Connection (from tela_tests/app1)

The core WebSocket connection code follows the proven patterns from working tela_tests:

  1. Connection Pattern:

    // Same approach as tela_tests/app1 lines 71-80
    socket = new WebSocket("ws://localhost:44326/xswd");
    socket.addEventListener("open", function(event) {
        sendData(applicationData);
    });
  2. Response Handling:

    // Same logic structure as tela_tests/app1 lines 86-145
    if (response.accepted) { /* connection approved */ }
    else if (response.result) { /* API response data */ }
    else if (response.error) { /* handle errors */ }
  3. Traditional JavaScript:

    • Global variables (let socket, const applicationData)
    • Function declarations (function connectWebSocket(), function sendData())
    • Standard event listeners (addEventListener)