XSWD Protocol

XSWD Protocol Overview

XSWD is the secure communication protocol that connects TELA applications to DERO wallets and the blockchain network.

🔐

Security First: XSWD provides secure, encrypted communication between your TELA application and the user's DERO wallet, ensuring private keys never leave the wallet environment.

What is XSWD?

XSWD enables TELA applications to:

  • Connect to DERO wallets (Engram, CLI wallet)
  • Access blockchain data through the user's node
  • Submit transactions securely through the wallet
  • Maintain privacy with encrypted communication
  • Work offline when wallet is local

Key Benefits

  • 🔒 Secure: Private keys never exposed to applications
  • Fast: Direct WebSocket connection for real-time data
  • 🛡️ Private: All communication through user's own node
  • 🔄 Reliable: Built-in retry and error handling
  • 📱 Cross-Platform: Works with all DERO wallet implementations

🔌 Connection Architecture

TELA Application ←→ XSWD Protocol ←→ DERO Wallet ←→ DERO Network
     (Your App)      (WebSocket)      (Engram/CLI)    (Blockchain)

Connection Flow

  1. Application Start: TELA app attempts XSWD connection
  2. Handshake: App sends identification data
  3. Wallet Approval: User approves/denies connection
  4. Secure Channel: Encrypted communication established
  5. API Access: Full DERO RPC methods available

Handshake Requirements

The first message sent to the XSWD server must be your application's identification data. The wallet validates this data strictly.

⚠️

Critical Requirements: The handshake data has strict validation rules. Failure to meet these requirements will result in connection rejection.

ApplicationData Structure

const applicationData = {
    "id": string,          // REQUIRED: 64-character hex string (any valid hex)
    "name": string,        // REQUIRED: 1-255 characters, ASCII only
    "description": string, // REQUIRED: 1-255 characters, ASCII only
    "url": string,         // REQUIRED: Must match Origin header, start with http:// or https://
    "permissions": object, // OPTIONAL: Pre-set permissions (requires signature)
    "signature": string    // OPTIONAL: DERO signed message (required for pre-set permissions)
};

Field Requirements

FieldRequirementValidation
id64 hex charactersMust be valid hexadecimal. Any 64-char hex string works.
name1-255 chars, ASCII onlyNo Unicode characters allowed. Will fail silently with Unicode.
description1-255 chars, ASCII onlyClearly describe what permissions your app needs.
urlMust match OriginMust start with http:// or https://. Max 255 chars.

Generating a Valid App ID

The id field must be a 64-character hexadecimal string. The ID is essentially arbitrary — the XSWD server only validates length and hex format.

From Source Code (walletapi/xswd/xswd.go):

// App ID validation (lines 420-431):
id := strings.TrimSpace(app.Id)
if len(id) != 64 {
    response = "Invalid ID size"
}
if _, err := hex.DecodeString(id); err != nil {
    response = "Invalid hexadecimal ID"
}
 
// Result: Any 64-char hex string is valid
// No derivation from app name, no uniqueness check

Option 1: Random generation (store for reuse)

const appId = Array.from({length: 64}, () => 
    Math.floor(Math.random() * 16).toString(16)
).join('');
localStorage.setItem('xswd_app_id', appId);

Option 2: SHA-256 of app name (deterministic)

async function generateAppId(appName) {
    const encoder = new TextEncoder();
    const data = encoder.encode(appName);
    const hashBuffer = await crypto.subtle.digest('SHA-256', data);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
 
const appId = await generateAppId('My TELA Application v1.0');

Option 3: Static/hardcoded

const appId = "71605a32e3b0c44298fc1c549afbf4c8496fb92427ae41e4649b934ca495991b";
ℹ️

Can App IDs be duplicated? Yes. Nothing prevents someone from using the same App ID as another app. The wallet only checks length, hex validity, and that the ID isn't already connected in the current session. For TELA applications, the SCID is the true identity, not the applicationData.

🚨

ASCII Only: The name and description fields must contain only ASCII characters (0-127). Unicode characters like emojis, smart quotes, or non-English characters will cause the connection to be rejected. This is validated server-side using Go's unicode.MaxASCII check.

Basic Implementation

Connection Setup

// Get or create App ID (stored for permission continuity)
function getOrCreateAppId() {
    let appId = localStorage.getItem('xswd_app_id');
    if (!appId) {
        appId = Array.from({length: 64}, () => 
            Math.floor(Math.random() * 16).toString(16)
        ).join('');
        localStorage.setItem('xswd_app_id', appId);
    }
    return appId;
}
 
// Basic XSWD connection
function connectToXSWD() {
    const applicationData = {
        "id": getOrCreateAppId(),  // 64-char hex string
        "name": "My TELA Application",  // ASCII only, max 255 chars
        "description": "Read-only blockchain explorer - no wallet access required",  // ASCII only
        "url": window.location.origin  // Must match Origin header
    };
    
    const socket = new WebSocket("ws://localhost:44326/xswd");
    
    socket.addEventListener("open", function(event) {
        console.log("XSWD connection opened");
        socket.send(JSON.stringify(applicationData));
    });
    
    socket.addEventListener("message", function(event) {
        const response = JSON.parse(event.data);
        
        if (response.accepted !== undefined) {
            if (response.accepted) {
                console.log("XSWD connection accepted:", response.message);
                // Connection established - can now make API calls
            } else {
                console.error("XSWD connection rejected:", response.message);
                // Common rejection reasons:
                // - "Invalid ID size" (not 64 chars)
                // - "Invalid hexadecimal ID"
                // - "Invalid name" (empty, >255 chars, or non-ASCII)
                // - "Invalid description" (empty, >255 chars, or non-ASCII)
                // - "Invalid URL compared to origin"
            }
        } else if (response.jsonrpc) {
            // Handle API response
            handleAPIResponse(response);
        }
    });
    
    socket.addEventListener("close", function(event) {
        console.log("XSWD connection closed");
    });
    
    socket.addEventListener("error", function(event) {
        console.error("XSWD connection error:", event);
    });
    
    return socket;
}

Making API Calls

// Make RPC calls through XSWD
function makeXSWDCall(socket, method, params = {}) {
    return new Promise((resolve, reject) => {
        const request = {
            "jsonrpc": "2.0",
            "id": Math.random().toString(36),
            "method": method,
            "params": params
        };
        
        // Store pending request for response handling
        pendingRequests.set(request.id, { resolve, reject });
        
        // Send request
        socket.send(JSON.stringify(request));
        
        // Set timeout
        setTimeout(() => {
            if (pendingRequests.has(request.id)) {
                pendingRequests.delete(request.id);
                reject(new Error('Request timeout'));
            }
        }, 30000);
    });
}
 
// Usage examples
const networkInfo = await makeXSWDCall(socket, 'DERO.GetInfo');
const balance = await makeXSWDCall(socket, 'GetBalance');
const block = await makeXSWDCall(socket, 'DERO.GetBlock', { height: 12345 });

Permission System

🔑

How Permissions Work: XSWD uses a per-method permission system. Understanding this is crucial for building user-friendly applications.

Permission Types

The XSWD server defines 5 permission levels:

PermissionValueBehavior
Ask0Prompt user for each request (default)
Allow1Allow this specific request
Deny2Deny this specific request
AlwaysAllow3Remember: always allow this method
AlwaysDeny4Remember: always deny this method

Default Behavior: forceAsk Mode

Important: The default XSWD server runs in forceAsk mode, meaning:

  • ALL wallet method requests will prompt the user regardless of any pre-set permissions
  • Pre-set permissions in the handshake are ignored unless the wallet disables forceAsk
  • This is a security feature to prevent apps from silently accessing wallet functions

Daemon Methods: Always Allowed

Methods prefixed with DERO. are automatically allowed without prompting:

// These NEVER require user permission - wallet proxies to daemon
await xswd.call('DERO.GetInfo');      // Always works
await xswd.call('DERO.GetBlock', { height: 100 });  // Always works
await xswd.call('DERO.GetSC', { scid: '...' });     // Always works
 
// These ALWAYS prompt the user (in forceAsk mode)
await xswd.call('GetAddress');   // User must approve
await xswd.call('GetBalance');   // User must approve
await xswd.call('Transfer', {}); // User must approve

This is why read-only explorers work seamlessly - they only use DERO.* methods which don't require permissions.

Pre-Set Permissions (Advanced)

To request pre-set permissions, you must provide a DERO signature where the signed message is your App ID:

const applicationData = {
    "id": "a1b2c3d4...",  // 64-char hex
    "name": "My App",
    "description": "Needs balance access",
    "url": "https://myapp.com",
    "permissions": {
        "GetBalance": 3,   // AlwaysAllow (3)
        "GetAddress": 3,   // AlwaysAllow (3)
        "Transfer": 4      // AlwaysDeny (4) - explicitly deny
    },
    "signature": "-----BEGIN DERO SIGNED MESSAGE-----\n..."  // Required!
};
⚠️

Signature Required: If you include permissions without a valid signature, the connection will be rejected with: "Application is requesting permissions without signature"

Methods That Cannot Store AlwaysAllow

These sensitive methods will always prompt the user, even with a valid signature:

  • Subscribe - Event subscriptions
  • SignData - Signing arbitrary data
  • CheckSignature - Signature verification
  • GetDaemon - Get daemon endpoint
  • QueryKey / query_key - Query wallet keys

Error Codes

CodeConstantMeaning
-32043PermissionDeniedUser denied this request
-32044PermissionAlwaysDeniedUser set "Always Deny" for this method
-32070RateLimitExceededToo many requests (>10/sec)

Rate Limiting

XSWD enforces rate limiting to prevent abuse:

  • Rate: 10 requests per second
  • Burst: Up to 20 requests in a burst
  • Penalty: Exceeding the limit disconnects the application
// Built-in rate limiter
class RateLimiter {
    constructor(rps = 10) {
        this.tokens = rps;
        this.maxTokens = rps * 2;  // Burst capacity
        this.lastRefill = Date.now();
        this.refillRate = 1000 / rps;  // ms per token
    }
    
    async acquire() {
        const now = Date.now();
        const elapsed = now - this.lastRefill;
        this.tokens = Math.min(this.maxTokens, this.tokens + elapsed / this.refillRate);
        this.lastRefill = now;
        
        if (this.tokens < 1) {
            const waitTime = (1 - this.tokens) * this.refillRate;
            await new Promise(r => setTimeout(r, waitTime));
            this.tokens = 0;
        } else {
            this.tokens -= 1;
        }
    }
}
 
const limiter = new RateLimiter(10);
 
async function rateLimitedCall(method, params) {
    await limiter.acquire();
    return xswd.call(method, params);
}

Available Methods

Daemon Methods (No Permission Required)

These methods are proxied to the daemon and never require user approval:

  • DERO.GetInfo - Network status and statistics
  • DERO.GetBlock - Block data by height or hash
  • DERO.GetBlockHeaderByTopoHeight - Block header by topo height
  • DERO.GetBlockHeaderByHash - Block header by hash
  • DERO.GetTransaction - Transaction details
  • DERO.GetTxPool - Pending transactions (mempool)
  • DERO.GetLastBlockHeader - Latest block header
  • DERO.GetHeight - Current block height
  • DERO.GetBlockCount - Total block count
  • DERO.GetSC - Smart contract data and variables
  • DERO.GetEncryptedBalance - Encrypted balance for address
  • DERO.GetRandomAddress - Random registered address
  • DERO.NameToAddress - Resolve name service
  • DERO.GetGasEstimate - Estimate transaction gas
  • DERO.SendRawTransaction - Broadcast raw transaction

Wallet Methods (Permission Required)

These methods always prompt the user in default forceAsk mode:

  • GetAddress - Wallet address
  • GetBalance - Account balance
  • GetHeight - Wallet sync height
  • GetTransferbyTXID - Transaction by TXID
  • GetTransfers - Transaction history
  • GetTrackedAssets - Tracked token balances
  • Transfer - Send transactions
  • scinvoke - Invoke smart contract
  • MakeIntegratedAddress - Create payment addresses
  • SplitIntegratedAddress - Parse payment addresses
  • QueryKey - Query wallet keys (mnemonic)

XSWD-Specific Methods

  • HasMethod - Check if method is available
  • Subscribe - Subscribe to events (new_balance, new_topoheight, new_entry)
  • Unsubscribe - Unsubscribe from events
  • SignData - Sign arbitrary data with wallet
  • CheckSignature - Verify a DERO signature
  • GetDaemon - Get connected daemon endpoint

Gnomon Indexer Methods (If Available)

  • Gnomon.GetLastIndexHeight - Indexer status
  • Gnomon.GetAllOwnersAndSCIDs - All smart contracts
  • Gnomon.GetSCIDVariableDetails - Contract variables
  • Gnomon.GetAllSCIDInvokeDetails - Contract interactions

EPOCH Mining Methods (If Available)

  • AttemptEPOCH - Submit mining hashes
  • GetMaxHashesEPOCH - Mining limits
  • GetSessionEPOCH - Mining session stats

Security Considerations

User Privacy Protection

🔐

Privacy by Design: XSWD ensures that private keys and sensitive wallet data never leave the user's local environment.

Best Practices for App Descriptions

Your description field is shown to users when they approve the connection. Be clear about what your app does:

// Generate proper App ID
const appId = await generateAppId('DERO Block Explorer v1.0');
 
// GOOD: Clear, honest description - ASCII only!
const applicationData = {
    "id": appId,  // Any valid 64-char hex string
    "name": "DERO Block Explorer",
    "description": "Read-only blockchain explorer - no wallet access required",
    "url": "https://explorer.example.com"
};
 
// BAD: Invalid ID format
const badApp1 = {
    "id": "block-explorer-v1",  // ERROR: Not 64 hex chars!
    "name": "Explorer",
    "description": "Explores blocks",
    "url": "https://example.com"
};
 
// BAD: Unicode characters (will be rejected!)
const badApp2 = {
    "id": appId,
    "name": "DERO Explorer",  // OK
    "description": "View blocks and transactions",  // ERROR: Smart quote!
    "url": "https://example.com"
};
 
// BAD: Vague description
const badApp3 = {
    "id": appId,
    "name": "App",
    "description": "Does stuff",  // Users won't trust this
    "url": "https://example.com"
};

URL Origin Validation

The XSWD server validates that your url matches the HTTP Origin header for security:

// The URL must match where your app is actually hosted
const applicationData = {
    "id": appId,
    "name": "My App",
    "description": "My application",
    "url": window.location.origin  // Use actual origin, not hardcoded
};
 
// If URL doesn't match Origin header, connection is rejected with:
// "Invalid URL compared to origin"

Performance Optimization

Connection Management

Performance Best Practices 👨‍💻

Rate Limiting: Never make more than 2-3 XSWD calls per second. I've seen connections drop when apps make 10+ simultaneous calls.

Caching Strategy: Cache GetInfo results for 15-30 seconds. Multiple modules requesting the same data will overwhelm the connection.

Timeout Handling: Different methods need different timeouts - Smart contract calls need 30s, basic calls need 10s.

// Rate limiting for XSWD calls
let lastCallTime = 0;
const MIN_CALL_INTERVAL = 500; // 500ms between calls
 
async function rateLimitedCall(socket, method, params) {
    const now = Date.now();
    const timeSinceLastCall = now - lastCallTime;
    
    if (timeSinceLastCall < MIN_CALL_INTERVAL) {
        await new Promise(resolve => 
            setTimeout(resolve, MIN_CALL_INTERVAL - timeSinceLastCall)
        );
    }
    
    lastCallTime = Date.now();
    return makeXSWDCall(socket, method, params);
}

Error Handling

Robust Error Management

// Comprehensive error handling
async function robustXSWDCall(socket, method, params, retries = 3) {
    for (let attempt = 1; attempt <= retries; attempt++) {
        try {
            return await makeXSWDCall(socket, method, params);
        } catch (error) {
            console.warn(`XSWD call attempt ${attempt} failed:`, error.message);
            
            if (attempt === retries) {
                throw new Error(`${method} failed after ${retries} attempts: ${error.message}`);
            }
            
            // Exponential backoff
            const delay = Math.pow(2, attempt) * 1000;
            await new Promise(resolve => setTimeout(resolve, delay));
        }
    }
}

Quick Reference

Connection States

StateValueDescriptionAction
CONNECTING0Establishing connectionWait
OPEN1Connected, readyCan send data
CLOSING2ClosingCannot send
CLOSED3ClosedReconnect needed

WebSocket Close Codes

CodeMeaningSolution
1000Normal closureUser closed wallet
1006Abnormal closureWallet not running or XSWD disabled
1001Going awayWallet closed
1002Protocol errorCheck application data format

Common Rejection Messages

MessageCauseFix
Invalid ID sizeID not exactly 64 charsUse 64-character hex string
Invalid hexadecimal IDID contains non-hex charsUse only 0-9, a-f
Invalid nameEmpty, >255 chars, or UnicodeUse ASCII only, 1-255 chars
Invalid descriptionEmpty, >255 chars, or UnicodeUse ASCII only, 1-255 chars
Invalid URL compared to originURL doesn't match page originUse window.location.origin

Related Documentation

Getting Started:

Ready-to-Use Solutions:

Troubleshooting:

Understanding TELA:


XSWD is the foundation of TELA application development, providing secure and efficient access to the DERO blockchain through the user's own wallet and node.