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
- Application Start: TELA app attempts XSWD connection
- Handshake: App sends identification data
- Wallet Approval: User approves/denies connection
- Secure Channel: Encrypted communication established
- 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
| Field | Requirement | Validation |
|---|---|---|
id | 64 hex characters | Must be valid hexadecimal. Any 64-char hex string works. |
name | 1-255 chars, ASCII only | No Unicode characters allowed. Will fail silently with Unicode. |
description | 1-255 chars, ASCII only | Clearly describe what permissions your app needs. |
url | Must match Origin | Must 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 checkOption 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:
| Permission | Value | Behavior |
|---|---|---|
Ask | 0 | Prompt user for each request (default) |
Allow | 1 | Allow this specific request |
Deny | 2 | Deny this specific request |
AlwaysAllow | 3 | Remember: always allow this method |
AlwaysDeny | 4 | Remember: 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 approveThis 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 subscriptionsSignData- Signing arbitrary dataCheckSignature- Signature verificationGetDaemon- Get daemon endpointQueryKey/query_key- Query wallet keys
Error Codes
| Code | Constant | Meaning |
|---|---|---|
| -32043 | PermissionDenied | User denied this request |
| -32044 | PermissionAlwaysDenied | User set "Always Deny" for this method |
| -32070 | RateLimitExceeded | Too 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 statisticsDERO.GetBlock- Block data by height or hashDERO.GetBlockHeaderByTopoHeight- Block header by topo heightDERO.GetBlockHeaderByHash- Block header by hashDERO.GetTransaction- Transaction detailsDERO.GetTxPool- Pending transactions (mempool)DERO.GetLastBlockHeader- Latest block headerDERO.GetHeight- Current block heightDERO.GetBlockCount- Total block countDERO.GetSC- Smart contract data and variablesDERO.GetEncryptedBalance- Encrypted balance for addressDERO.GetRandomAddress- Random registered addressDERO.NameToAddress- Resolve name serviceDERO.GetGasEstimate- Estimate transaction gasDERO.SendRawTransaction- Broadcast raw transaction
Wallet Methods (Permission Required)
These methods always prompt the user in default forceAsk mode:
GetAddress- Wallet addressGetBalance- Account balanceGetHeight- Wallet sync heightGetTransferbyTXID- Transaction by TXIDGetTransfers- Transaction historyGetTrackedAssets- Tracked token balancesTransfer- Send transactionsscinvoke- Invoke smart contractMakeIntegratedAddress- Create payment addressesSplitIntegratedAddress- Parse payment addressesQueryKey- Query wallet keys (mnemonic)
XSWD-Specific Methods
HasMethod- Check if method is availableSubscribe- Subscribe to events (new_balance, new_topoheight, new_entry)Unsubscribe- Unsubscribe from eventsSignData- Sign arbitrary data with walletCheckSignature- Verify a DERO signatureGetDaemon- Get connected daemon endpoint
Gnomon Indexer Methods (If Available)
Gnomon.GetLastIndexHeight- Indexer statusGnomon.GetAllOwnersAndSCIDs- All smart contractsGnomon.GetSCIDVariableDetails- Contract variablesGnomon.GetAllSCIDInvokeDetails- Contract interactions
EPOCH Mining Methods (If Available)
AttemptEPOCH- Submit mining hashesGetMaxHashesEPOCH- Mining limitsGetSessionEPOCH- 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
GetInforesults 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
| State | Value | Description | Action |
|---|---|---|---|
CONNECTING | 0 | Establishing connection | Wait |
OPEN | 1 | Connected, ready | Can send data |
CLOSING | 2 | Closing | Cannot send |
CLOSED | 3 | Closed | Reconnect needed |
WebSocket Close Codes
| Code | Meaning | Solution |
|---|---|---|
1000 | Normal closure | User closed wallet |
1006 | Abnormal closure | Wallet not running or XSWD disabled |
1001 | Going away | Wallet closed |
1002 | Protocol error | Check application data format |
Common Rejection Messages
| Message | Cause | Fix |
|---|---|---|
Invalid ID size | ID not exactly 64 chars | Use 64-character hex string |
Invalid hexadecimal ID | ID contains non-hex chars | Use only 0-9, a-f |
Invalid name | Empty, >255 chars, or Unicode | Use ASCII only, 1-255 chars |
Invalid description | Empty, >255 chars, or Unicode | Use ASCII only, 1-255 chars |
Invalid URL compared to origin | URL doesn't match page origin | Use window.location.origin |
Related Documentation
Getting Started:
- Official Demo (app1) - Complete working XSWD implementation
Ready-to-Use Solutions:
- XSWD Templates - Copy-paste XSWD implementations
- XSWD Basic - Lightweight (15KB)
- XSWD Advanced - Production-grade (25KB)
Troubleshooting:
- Error Troubleshooting Guide - Fix common XSWD connection errors
- API Reference - Complete DERO RPC methods
Understanding TELA:
- TELA Overview - Platform architecture
- Security Model - Privacy features
- Best Practices - Production development workflow
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.