Skill: yoyoemily/crypto-swap (version 1.3.0)

--- name: crypto-swap description: Lightning-fast crypto swaps. 240+ coins, best rates, done in minutes. Chat, CLI, or web — however you prefer. metadata: { "openclaw": { "requires": { "bins": ["crypto-swap"] }, "install": [ { "id": "node", "kind": "node", "package": "crypto-swap", "bins": ["crypto-swap"], "label": "Install Crypto Swap CLI (npm)" } ] } } --- # Crypto Swap Skill (LightningEX) A versatile cryptocurrency swap service powered by LightningEX API with three interaction modes: - **Chat Mode**: Natural language conversation for swaps and queries - **CLI Mode**: Command-line interface for scripting and automation - **UI Mode**: Web-based DeFi interface for visual trading ## Quick Start ### Chat Mode (Default) Simply talk to perform exchanges: **Exchange & Rates:** - "Swap 100 USDT to ETH" - "What's the exchange rate for BTC to USDT?" - "Exchange rate for 100 USDT (TRC20) to USDT (BEP20)" **Explore:** - "Show me supported tokens" - "List all currencies" - "What networks does USDT support?" **Order Management:** - "Check order status I1Y0××××" - "Monitor order I1Y0××××" - "Where is my order?" **Cross-chain Swaps:** - "Swap USDT from Tron to BSC" - "Bridge ETH from Ethereum to Arbitrum" - "Convert BTC to SOL" ### CLI Mode **Prerequisite:** Install the CLI tool globally: ```bash npm install -g crypto-swap ``` **Run the CLI:** ```bash # Start interactive wizard (default) crypto-swap # Show all available commands crypto-swap --help # List supported currencies crypto-swap currencies # List supported currency-network pairs crypto-swap pair-list --send USDT --receive USDT crypto-swap pair-list --send USDT --receive USDT --send-network TRX # Get pair info crypto-swap pair --send USDT --receive USDT --send-network TRX --receive-network BSC # Check exchange rate crypto-swap rate --send USDT --receive USDT --send-network TRX --receive-network BSC --amount 100 # Check order status crypto-swap status --id I1Y0... # Monitor order until complete crypto-swap monitor --id I1Y0... ``` ### UI Mode ```bash # Launch web UI (default port 8080, auto-assign if occupied) crypto-swap ui ``` ## Files This skill contains the following files: - `swap.js` - Main CLI script (~1000 lines, open source) - `package.json` - Package metadata - `SKILL.md` - This documentation - `LICENSE` - MIT License - `README.md` - Package readme - `assets/` - Web UI assets (HTML/CSS/JS) --- **Author:** [@yoyoemily](https://github.com/yoyoemily) **Repository:** https://github.com/yoyoemily/crypto-swap **License:** MIT

path: README.md
content: # crypto-swap

---
name: crypto-swap
description: Lightning-fast crypto swaps. 240+ coins, best rates, done in minutes. Chat, CLI, or web — however you prefer.
---

# Crypto Swap Skill (LightningEX)

A versatile cryptocurrency swap service powered by LightningEX API with three interaction modes:
- **Chat Mode**: Natural language conversation for swaps and queries
- **CLI Mode**: Command-line interface for scripting and automation  
- **UI Mode**: Web-based DeFi interface for visual trading

## Quick Start

### Chat Mode (Default)
Simply talk to perform exchanges:

**Exchange & Rates:**
- "Swap 100 USDT to ETH"
- "What's the exchange rate for BTC to USDT?"
- "Exchange rate for 100 USDT (TRC20) to USDT (BEP20)"

**Explore:**
- "Show me supported tokens"
- "List all currencies"
- "What networks does USDT support?"

**Order Management:**
- "Check order status I1Y0××××"
- "Monitor order I1Y0××××"
- "Where is my order?"

**Cross-chain Swaps:**
- "Swap USDT from Tron to BSC"
- "Bridge ETH from Ethereum to Arbitrum"
- "Convert BTC to SOL"

### CLI Mode

**Run the CLI:**
```bash
# Navigate to skill directory
cd /path/to/crypto-swap

# Start interactive wizard (default)
node swap.js

# Show all available commands
node swap.js --help
```

### UI Mode
```bash
# Launch web UI (default port 8080, auto-assign if occupied)
node swap.js ui
```
Then open http://localhost:8080 (or the displayed port) in your browser for the DeFi-style trading interface.


path: assets/ui/css/base.css
content: :root {
  /* Cyberpunk Neon Dark Theme */
  --bg-primary: #0a0a0f;
  --bg-secondary: #12121a;
  --bg-tertiary: #1a1a25;
  --bg-card: #151520;
  
  /* Neon Colors */
  --neon-cyan: #00f5ff;
  --neon-pink: #ff00ff;
  --neon-purple: #b829dd;
  --neon-blue: #0080ff;
  --neon-green: #00ff88;
  --neon-yellow: #ffdd00;
  --neon-orange: #ff6600;
  
  /* Text Colors */
  --text-primary: #ffffff;
  --text-secondary: #a0a0b0;
  --text-muted: #606070;
  
  /* Gradients */
  --gradient-primary: linear-gradient(135deg, var(--neon-cyan) 0%, var(--neon-purple) 100%);
  --gradient-secondary: linear-gradient(135deg, var(--neon-pink) 0%, var(--neon-orange) 100%);
  --gradient-glow: linear-gradient(135deg, rgba(0, 245, 255, 0.1) 0%, rgba(184, 41, 221, 0.1) 100%);
  
  /* Spacing */
  --space-xs: 0.25rem;
  --space-sm: 0.5rem;
  --space-md: 1rem;
  --space-lg: 1.5rem;
  --space-xl: 2rem;
  --space-2xl: 3rem;
  --space-3xl: 4rem;
  
  /* Typography */
  --font-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-mono: 'JetBrains Mono', 'Fira Code', monospace;
  
  /* Border Radius */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --radius-full: 9999px;
  
  /* Shadows */
  --shadow-neon-cyan: 0 0 10px rgba(0, 245, 255, 0.5), 0 0 20px rgba(0, 245, 255, 0.3);
  --shadow-neon-pink: 0 0 10px rgba(255, 0, 255, 0.5), 0 0 20px rgba(255, 0, 255, 0.3);
  --shadow-card: 0 4px 20px rgba(0, 0, 0, 0.4);
  
  /* Transitions */
  --transition-fast: 0.15s ease;
  --transition-normal: 0.3s ease;
  --transition-slow: 0.5s ease;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  font-family: var(--font-primary);
  background: var(--bg-primary);
  color: var(--text-primary);
  line-height: 1.6;
  min-height: 100vh;
  overflow-x: hidden;
}

/* Background Grid Effect */
body::before {
  content: '';
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background-image: 
    linear-gradient(rgba(0, 245, 255, 0.03) 1px, transparent 1px),
    linear-gradient(90deg, rgba(0, 245, 255, 0.03) 1px, transparent 1px);
  background-size: 50px 50px;
  pointer-events: none;
  z-index: -1;
}

/* Scrollbar */
::-webkit-scrollbar {
  width: 8px;
}

::-webkit-scrollbar-track {
  background: var(--bg-secondary);
}

::-webkit-scrollbar-thumb {
  background: var(--neon-cyan);
  border-radius: var(--radius-full);
}

::-webkit-scrollbar-thumb:hover {
  background: var(--neon-purple);
}

/* Typography */
h1, h2, h3, h4, h5, h6 {
  font-weight: 700;
  line-height: 1.2;
}

h1 {
  font-size: 3rem;
  background: var(--gradient-primary);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

h2 {
  font-size: 2.25rem;
  color: var(--text-primary);
}

h3 {
  font-size: 1.5rem;
  color: var(--text-primary);
}

p {
  color: var(--text-secondary);
}

a {
  color: var(--neon-cyan);
  text-decoration: none;
  transition: var(--transition-fast);
}

a:hover {
  color: var(--neon-pink);
  text-shadow: 0 0 10px rgba(255, 0, 255, 0.5);
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  padding: var(--space-md) var(--space-xl);
  font-size: 1rem;
  font-weight: 600;
  border: none;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: var(--transition-normal);
  font-family: var(--font-primary);
}

.btn-primary {
  background: var(--gradient-primary);
  color: var(--bg-primary);
  box-shadow: var(--shadow-neon-cyan);
}

.btn-primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 0 20px rgba(0, 245, 255, 0.7), 0 0 40px rgba(0, 245, 255, 0.4);
}

.btn-secondary {
  background: transparent;
  color: var(--neon-cyan);
  border: 2px solid var(--neon-cyan);
}

.btn-secondary:hover {
  background: rgba(0, 245, 255, 0.1);
  box-shadow: var(--shadow-neon-cyan);
}

.btn-glow {
  position: relative;
  overflow: hidden;
}

.btn-glow::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
  transition: var(--transition-slow);
}

.btn-glow:hover::before {
  left: 100%;
}

/* Cards */
.card {
  background: var(--bg-card);
  border: 1px solid rgba(0, 245, 255, 0.1);
  border-radius: var(--radius-lg);
  padding: var(--space-xl);
  box-shadow: var(--shadow-card);
  transition: var(--transition-normal);
}

.card:hover {
  border-color: rgba(0, 245, 255, 0.3);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5), 0 0 20px rgba(0, 245, 255, 0.1);
  transform: translateY(-4px);
}

/* Form Elements */
.input {
  width: 100%;
  padding: var(--space-md) var(--space-lg);
  background: var(--bg-secondary);
  border: 1px solid rgba(0, 245, 255, 0.2);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  font-size: 1rem;
  font-family: var(--font-primary);
  transition: var(--transition-fast);
}

.input:focus {
  outline: none;
  border-color: var(--neon-cyan);
  box-shadow: 0 0 10px rgba(0, 245, 255, 0.3);
}

.input::placeholder {
  color: var(--text-muted);
}

.select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2300f5ff' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1rem center;
  padding-right: 2.5rem;
  cursor: pointer;
}

/* Neon Text Effects */
.text-neon-cyan {
  color: var(--neon-cyan);
  text-shadow: 0 0 10px rgba(0, 245, 255, 0.5);
}

.text-neon-pink {
  color: var(--neon-pink);
  text-shadow: 0 0 10px rgba(255, 0, 255, 0.5);
}

.text-neon-purple {
  color: var(--neon-purple);
  text-shadow: 0 0 10px rgba(184, 41, 221, 0.5);
}

.text-gradient {
  background: var(--gradient-primary);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

/* Animations */
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

@keyframes glow {
  0%, 100% { box-shadow: 0 0 5px var(--neon-cyan), 0 0 10px var(--neon-cyan); }
  50% { box-shadow: 0 0 20px var(--neon-cyan), 0 0 30px var(--neon-cyan); }
}

@keyframes float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-10px); }
}

@keyframes slideIn {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.animate-pulse {
  animation: pulse 2s infinite;
}

.animate-glow {
  animation: glow 2s infinite;
}

.animate-float {
  animation: float 3s ease-in-out infinite;
}

.animate-slide-in {
  animation: slideIn 0.5s ease forwards;
}

/* Utility Classes */
.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 var(--space-lg);
  width: 100%;
  box-sizing: border-box;
}

@media (max-width: 768px) {
  .container {
    padding: 0 var(--space-md);
  }
}

.section {
  padding: var(--space-3xl) 0;
}

.text-center {
  text-align: center;
}

.flex {
  display: flex;
}

.flex-col {
  flex-direction: column;
}

.items-center {
  align-items: center;
}

.justify-center {
  justify-content: center;
}

.justify-between {
  justify-content: space-between;
}

.gap-sm { gap: var(--space-sm); }
.gap-md { gap: var(--space-md); }
.gap-lg { gap: var(--space-lg); }
.gap-xl { gap: var(--space-xl); }

.grid {
  display: grid;
}

.grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
.grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid-cols-4 { grid-template-columns: repeat(4, 1fr); }

.hidden {
  display: none !important;
}

/* Responsive */
@media (max-width: 768px) {
  h1 { font-size: 2rem; }
  h2 { font-size: 1.75rem; }
  h3 { font-size: 1.25rem; }
  
  .grid-cols-2,
  .grid-cols-3,
  .grid-cols-4 {
    grid-template-columns: 1fr;
  }
  
  .container {
    padding: 0 var(--space-md);
  }
}


path: assets/ui/css/components.css
content: /* Modal Styles */
.modal {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.8);
  backdrop-filter: blur(10px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2000;
  opacity: 0;
  visibility: hidden;
  transition: var(--transition-normal);
}

.modal.active {
  opacity: 1;
  visibility: visible;
}

.modal-content {
  background: var(--bg-card);
  border: 1px solid rgba(0, 245, 255, 0.2);
  border-radius: var(--radius-xl);
  padding: var(--space-xl);
  max-width: 500px;
  width: 90%;
  max-height: 90vh;
  overflow-y: auto;
  transform: scale(0.9);
  transition: var(--transition-normal);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}

.modal.active .modal-content {
  transform: scale(1);
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--space-lg);
}

.modal-header h3 {
  font-size: 1.25rem;
}

.modal-close {
  width: 32px;
  height: 32px;
  background: var(--bg-tertiary);
  border: 1px solid rgba(0, 245, 255, 0.2);
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  font-size: 1.5rem;
  cursor: pointer;
  transition: var(--transition-fast);
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal-close:hover {
  background: rgba(255, 0, 255, 0.1);
  border-color: var(--neon-pink);
  color: var(--neon-pink);
}

/* Currency Modal */
.currency-modal-content {
  max-width: 400px;
}

.currency-list {
  max-height: 400px;
  overflow-y: auto;
  margin-top: var(--space-md);
}

.currency-list-item {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-md);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: var(--transition-fast);
}

.currency-list-item:hover {
  background: rgba(0, 245, 255, 0.05);
}

.currency-list-item.active {
  background: rgba(0, 245, 255, 0.1);
  border: 1px solid rgba(0, 245, 255, 0.3);
}

.currency-list-icon {
  width: 36px;
  height: 36px;
  background: var(--gradient-primary);
  border-radius: var(--radius-full);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  font-weight: 700;
}

.currency-list-info {
  flex: 1;
}

.currency-list-code {
  font-weight: 600;
  color: var(--text-primary);
}

.currency-list-name {
  font-size: 0.8rem;
  color: var(--text-muted);
}

.currency-list-network {
  font-size: 0.75rem;
  color: var(--neon-cyan);
  background: rgba(0, 245, 255, 0.1);
  padding: 2px 8px;
  border-radius: var(--radius-full);
}

/* Order Modal */
.order-modal-content {
  max-width: 500px;
}

.order-step {
  text-align: center;
  padding: var(--space-xl) 0;
}

.order-step-icon {
  width: 80px;
  height: 80px;
  background: var(--gradient-glow);
  border: 2px solid rgba(0, 245, 255, 0.3);
  border-radius: var(--radius-full);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto var(--space-lg);
  font-size: 2rem;
}

.order-step-title {
  font-size: 1.5rem;
  margin-bottom: var(--space-md);
}

.order-step-desc {
  color: var(--text-secondary);
  margin-bottom: var(--space-xl);
}

.order-details {
  background: var(--bg-secondary);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
  margin-bottom: var(--space-lg);
}

.order-detail-row {
  display: flex;
  justify-content: space-between;
  padding: var(--space-sm) 0;
  border-bottom: 1px solid rgba(0, 245, 255, 0.1);
}

.order-detail-row:last-child {
  border-bottom: none;
}

.order-detail-label {
  color: var(--text-muted);
  font-size: 0.875rem;
}

.order-detail-value {
  font-weight: 600;
  color: var(--text-primary);
}

.order-detail-value.highlight {
  color: var(--neon-cyan);
}

.order-address-box {
  background: var(--bg-tertiary);
  border: 1px solid rgba(0, 245, 255, 0.2);
  border-radius: var(--radius-md);
  padding: var(--space-md);
  margin: var(--space-md) 0;
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.order-address {
  flex: 1;
  font-family: var(--font-mono);
  font-size: 0.875rem;
  color: var(--text-primary);
  word-break: break-all;
}

.order-copy-btn {
  padding: var(--space-sm) var(--space-md);
  background: rgba(0, 245, 255, 0.1);
  border: 1px solid rgba(0, 245, 255, 0.3);
  border-radius: var(--radius-md);
  color: var(--neon-cyan);
  cursor: pointer;
  transition: var(--transition-fast);
  white-space: nowrap;
}

.order-copy-btn:hover {
  background: rgba(0, 245, 255, 0.2);
}

.order-status-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-full);
  font-size: 0.875rem;
  font-weight: 500;
}

.order-status-badge.awaiting {
  background: rgba(255, 221, 0, 0.1);
  color: var(--neon-yellow);
  border: 1px solid rgba(255, 221, 0, 0.3);
}

.order-status-badge.confirming {
  background: rgba(0, 128, 255, 0.1);
  color: var(--neon-blue);
  border: 1px solid rgba(0, 128, 255, 0.3);
}

.order-status-badge.exchanging {
  background: rgba(184, 41, 221, 0.1);
  color: var(--neon-purple);
  border: 1px solid rgba(184, 41, 221, 0.3);
}

.order-status-badge.sending {
  background: rgba(0, 245, 255, 0.1);
  color: var(--neon-cyan);
  border: 1px solid rgba(0, 245, 255, 0.3);
}

.order-status-badge.complete {
  background: rgba(0, 255, 136, 0.1);
  color: var(--neon-green);
  border: 1px solid rgba(0, 255, 136, 0.3);
}

.order-status-badge.failed {
  background: rgba(255, 0, 0, 0.1);
  color: #ff4444;
  border: 1px solid rgba(255, 0, 0, 0.3);
}

.order-progress {
  display: flex;
  justify-content: space-between;
  margin: var(--space-xl) 0;
  position: relative;
}

.order-progress::before {
  content: '';
  position: absolute;
  top: 15px;
  left: 10%;
  right: 10%;
  height: 2px;
  background: var(--bg-tertiary);
  z-index: 0;
}

.order-progress-bar {
  position: absolute;
  top: 15px;
  left: 10%;
  height: 2px;
  background: var(--gradient-primary);
  z-index: 1;
  transition: var(--transition-slow);
}

.order-progress-step {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
  z-index: 2;
}

.order-progress-dot {
  width: 32px;
  height: 32px;
  background: var(--bg-tertiary);
  border: 2px solid rgba(0, 245, 255, 0.3);
  border-radius: var(--radius-full);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  transition: var(--transition-normal);
}

.order-progress-step.active .order-progress-dot {
  background: var(--gradient-primary);
  border-color: var(--neon-cyan);
  box-shadow: var(--shadow-neon-cyan);
}

.order-progress-step.completed .order-progress-dot {
  background: var(--neon-green);
  border-color: var(--neon-green);
}

.order-progress-label {
  font-size: 0.75rem;
  color: var(--text-muted);
}

.order-progress-step.active .order-progress-label {
  color: var(--neon-cyan);
}

/* QR Code Container */
.qr-code {
  width: 200px;
  height: 200px;
  background: white;
  border-radius: var(--radius-md);
  padding: var(--space-md);
  margin: var(--space-lg) auto;
  display: flex;
  align-items: center;
  justify-content: center;
}

.qr-code svg,
.qr-code img {
  max-width: 100%;
  max-height: 100%;
}

/* Responsive */
@media (max-width: 768px) {
  .modal-content {
    width: 95%;
    padding: var(--space-md);
  }
}


path: assets/ui/css/home.css
content: /* Hero Section - Exchange Widget Only */
.hero {
  min-height: 100vh;
  display: flex;
  align-items: center;
  padding-top: 70px;
  position: relative;
  overflow: hidden;
}

.hero-content {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3xl);
  align-items: center;
  position: relative;
  z-index: 1;
}

.hero-text {
  max-width: 600px;
}

.hero-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  background: rgba(0, 245, 255, 0.1);
  border: 1px solid rgba(0, 245, 255, 0.3);
  border-radius: var(--radius-full);
  font-size: 0.875rem;
  color: var(--neon-cyan);
  margin-bottom: var(--space-lg);
}

.hero-badge::before {
  content: '';
  width: 8px;
  height: 8px;
  background: var(--neon-green);
  border-radius: var(--radius-full);
  animation: pulse 2s infinite;
}

.hero-title {
  font-size: 3.5rem;
  font-weight: 800;
  line-height: 1.1;
  margin-bottom: var(--space-lg);
}

.hero-title span {
  background: var(--gradient-primary);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.hero-subtitle {
  font-size: 1.125rem;
  color: var(--text-secondary);
  margin-bottom: var(--space-xl);
  line-height: 1.7;
}

.hero-stats {
  display: flex;
  gap: var(--space-2xl);
  margin-top: var(--space-2xl);
}

.hero-stat {
  text-align: left;
}

.hero-stat-value {
  font-size: 2rem;
  font-weight: 700;
  color: var(--neon-cyan);
  text-shadow: 0 0 20px rgba(0, 245, 255, 0.5);
}

.hero-stat-label {
  font-size: 0.875rem;
  color: var(--text-muted);
  margin-top: var(--space-xs);
}

/* Exchange Widget */
.exchange-widget {
  background: var(--bg-card);
  border: 1px solid rgba(0, 245, 255, 0.2);
  border-radius: var(--radius-xl);
  padding: var(--space-xl);
  box-shadow: 
    0 20px 60px rgba(0, 0, 0, 0.5),
    0 0 40px rgba(0, 245, 255, 0.1);
  position: relative;
}

.exchange-widget::before {
  content: '';
  position: absolute;
  top: -1px;
  left: -1px;
  right: -1px;
  bottom: -1px;
  background: var(--gradient-primary);
  border-radius: var(--radius-xl);
  z-index: -1;
  opacity: 0.3;
}

.exchange-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-lg);
}

.exchange-title {
  font-size: 1.25rem;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.exchange-title::before {
  content: '⚡';
}

.exchange-timer {
  font-size: 0.875rem;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.exchange-timer span {
  color: var(--neon-cyan);
  font-family: var(--font-mono);
  font-weight: 600;
}

/* Currency Input */
.currency-input-group {
  background: var(--bg-secondary);
  border: 1px solid rgba(0, 245, 255, 0.1);
  border-radius: var(--radius-lg);
  padding: var(--space-md);
  margin-bottom: var(--space-md);
  transition: var(--transition-fast);
}

.currency-input-group:focus-within {
  border-color: rgba(0, 245, 255, 0.3);
  box-shadow: 0 0 15px rgba(0, 245, 255, 0.1);
}

.currency-input-label {
  font-size: 0.75rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: var(--space-sm);
}

.currency-input-row {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.currency-input {
  flex: 1;
  background: transparent;
  border: none;
  color: var(--text-primary);
  font-size: 1.5rem;
  font-weight: 600;
  font-family: var(--font-mono);
  outline: none;
}

.currency-input::placeholder {
  color: var(--text-muted);
}

.currency-select {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--space-xs) var(--space-sm);
  background: var(--bg-tertiary);
  border: 1px solid rgba(0, 245, 255, 0.2);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: var(--transition-fast);
  min-width: 100px;
}

.currency-network {
  font-size: 0.65rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 2px;
}

.currency-main {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.currency-select:hover {
  border-color: var(--neon-cyan);
  background: rgba(0, 245, 255, 0.05);
}

.currency-icon {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-full);
  background: var(--gradient-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  font-weight: 700;
}

.currency-code {
  font-weight: 600;
  font-size: 1rem;
}

.currency-dropdown-icon {
  margin-left: auto;
  color: var(--text-muted);
  font-size: 0.75rem;
}

.currency-balance {
  font-size: 0.75rem;
  color: var(--text-muted);
  margin-top: var(--space-sm);
}

.currency-balance span {
  color: var(--neon-cyan);
}

/* Swap Button */
.swap-button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  background: var(--gradient-primary);
  border: none;
  border-radius: var(--radius-full);
  cursor: pointer;
  margin: var(--space-md) auto;
  position: relative;
  z-index: 1;
  box-shadow: var(--shadow-neon-cyan);
  transition: var(--transition-normal);
}

.swap-button:hover {
  transform: rotate(180deg) scale(1.1);
  box-shadow: 0 0 30px rgba(0, 245, 255, 0.7);
}

.swap-button svg {
  width: 20px;
  height: 20px;
  color: var(--bg-primary);
}

/* Exchange Rate Info */
.exchange-rate-info {
  background: rgba(0, 245, 255, 0.05);
  border: 1px solid rgba(0, 245, 255, 0.1);
  border-radius: var(--radius-md);
  padding: var(--space-md);
  margin: var(--space-lg) 0;
}

.exchange-rate-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 0.875rem;
  margin-bottom: var(--space-sm);
}

.exchange-rate-row:last-child {
  margin-bottom: 0;
}

.exchange-rate-label {
  color: var(--text-muted);
}

.exchange-rate-value {
  color: var(--text-primary);
  font-weight: 500;
}

.exchange-rate-value.highlight {
  color: var(--neon-cyan);
}

/* Exchange Button */
.exchange-button {
  width: 100%;
  padding: var(--space-lg);
  font-size: 1.1rem;
  margin-top: var(--space-lg);
}

/* Responsive */
@media (max-width: 1024px) {
  .hero-content {
    grid-template-columns: 1fr;
    text-align: center;
  }
  
  .hero-text {
    max-width: 100%;
  }
  
  .hero-stats {
    justify-content: center;
  }
}

@media (max-width: 768px) {
  .hero {
    min-height: auto;
    padding: 100px var(--space-md) var(--space-3xl);
  }

  .hero-content {
    width: 100%;
    max-width: 100%;
  }

  .hero-text {
    max-width: 100%;
    width: 100%;
  }

  .hero-title {
    font-size: 2.5rem;
  }

  .hero-subtitle {
    padding: 0;
  }

  .hero-stats {
    flex-direction: column;
    gap: var(--space-lg);
  }

  .exchange-widget {
    margin: 0 auto;
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
    border-radius: var(--radius-lg);
    padding: var(--space-lg);
    overflow: hidden;
  }

  .currency-input-row {
    gap: var(--space-sm);
    min-width: 0;
  }

  .currency-input {
    font-size: 1.25rem;
    min-width: 0;
    width: 100%;
  }

  .currency-select {
    min-width: 80px;
    flex-shrink: 0;
    padding: var(--space-xs);
  }

  .currency-network {
    font-size: 0.6rem;
  }

  .currency-code {
    font-size: 0.8rem;
  }

  .currency-icon {
    width: 20px;
    height: 20px;
    font-size: 0.6rem;
  }

  .currency-balance {
    font-size: 0.7rem;
  }

  .exchange-rate-info {
    padding: var(--space-sm);
    min-width: 0;
  }

  .exchange-rate-row {
    font-size: 0.8rem;
    gap: var(--space-sm);
  }
  
  .currency-input-row {
    gap: var(--space-sm);
  }
  
  .currency-input {
    font-size: 1.25rem;
    min-width: 0;
  }
  
  .currency-select {
    min-width: 90px;
    padding: var(--space-xs);
  }
  
  .currency-code {
    font-size: 0.875rem;
  }
  
  .currency-icon {
    width: 24px;
    height: 24px;
    font-size: 0.625rem;
  }
  
  .exchange-header {
    flex-direction: column;
    gap: var(--space-sm);
    align-items: flex-start;
  }
  
  .exchange-rate-info {
    padding: var(--space-sm);
  }
  
  .exchange-rate-row {
    font-size: 0.8rem;
  }
}


path: assets/ui/css/layout.css
content: /* Layout utilities for exchange widget */

/* Container */
.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 var(--space-lg);
  width: 100%;
  box-sizing: border-box;
}

@media (max-width: 768px) {
  .container {
    padding: 0 var(--space-md);
  }
}


path: assets/ui/index.html
content: <!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>LightningEX - Instant Cryptocurrency Exchange</title>
  <meta name="description" content="Lightning-fast crypto swaps with the best rates. Exchange 240+ cryptocurrencies securely.">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="css/base.css">
  <link rel="stylesheet" href="css/layout.css">
  <link rel="stylesheet" href="css/home.css">
  <link rel="stylesheet" href="css/components.css">
  <style>
    /* Minimal layout adjustments for standalone widget */
    body {
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      padding: 20px 20px 60px 20px;
    }

    /* Top Logo Bar */
    .top-logo-bar {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 20px;
      z-index: 100;
    }

    .top-logo-link {
      display: flex;
      align-items: center;
      gap: 12px;
      text-decoration: none;
      transition: opacity 0.3s ease;
    }

    .top-logo-link:hover {
      opacity: 0.8;
    }

    .top-logo-icon {
      width: 36px;
      height: 36px;
      background: linear-gradient(135deg, #00f5ff 0%, #b829dd 100%);
      border-radius: 8px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 1.2rem;
      box-shadow: 0 0 15px rgba(0, 245, 255, 0.3);
    }

    .top-logo-text {
      font-size: 1.5rem;
      font-weight: 800;
      background: linear-gradient(90deg, #00f5ff 0%, #b829dd 100%);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      background-clip: text;
    }

    .top-logo-highlight {
      background: linear-gradient(90deg, #ffd700 0%, #ffaa00 50%, #ff6b00 100%);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      background-clip: text;
    }
    
    .hero {
      min-height: auto;
      padding-top: 80px;
      width: 100%;
      max-width: 1200px;
    }
    
    .hero::before,
    .hero::after {
      display: none;
    }
    
    .hero-content {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 60px;
      align-items: center;
    }
    
    @media (max-width: 1024px) {
      .hero-content {
        grid-template-columns: 1fr;
        text-align: center;
      }
      
      .hero-text {
        max-width: 100%;
      }
      
      .hero-stats {
        justify-content: center;
      }
    }
    
    @media (max-width: 768px) {
      body {
        padding: 16px 16px 60px 16px;
      }

      .hero-content {
        grid-template-columns: 1fr;
        gap: 0;
      }

      /* Hide hero text section on mobile */
      .hero-text {
        display: none;
      }

      .exchange-widget {
        margin: 0 !important;
        width: 100% !important;
        max-width: 100% !important;
        box-sizing: border-box;
        border-radius: var(--radius-lg);
      }

      .container {
        padding: 0 !important;
      }
    }
  </style>
</head>
<body>
  <!-- Top Logo Bar -->
  <div class="top-logo-bar">
    <a href="https://www.lightningex.io" target="_blank" class="top-logo-link">
      <div class="top-logo-icon">⚡</div>
      <span class="top-logo-text">Lightning<span class="top-logo-highlight">EX</span></span>
    </a>
  </div>

  <!-- Hero Section -->
  <section class="hero">
    <div class="container">
      <div class="hero-content">
        <div class="hero-text">
          <div class="hero-badge">Live Exchange</div>
          <h1 class="hero-title">
            Lightning Fast<br>
            <span>Crypto Swaps</span>
          </h1>
          <p class="hero-subtitle">
            Exchange 240+ cryptocurrencies instantly with the best rates. 
            No registration required, secure and anonymous.
          </p>
          <div class="hero-stats">
            <div class="hero-stat">
              <div class="hero-stat-value">240+</div>
              <div class="hero-stat-label">Supported Coins</div>
            </div>
            <div class="hero-stat">
              <div class="hero-stat-value">&lt;2min</div>
              <div class="hero-stat-label">Avg. Exchange Time</div>
            </div>
            <div class="hero-stat">
              <div class="hero-stat-value">$200M+</div>
              <div class="hero-stat-label">Volume Exchanged</div>
            </div>
          </div>
        </div>
        
        <!-- Exchange Widget -->
        <div class="exchange-widget">
          <div class="exchange-header">
            <h3 class="exchange-title">Exchange Crypto</h3>
            <div class="exchange-timer">Rate updates in <span id="rateTimer">20</span>s</div>
          </div>
          
          <!-- Send Input -->
          <div class="currency-input-group">
            <div class="currency-input-label">You Send</div>
            <div class="currency-input-row">
              <input type="number" class="currency-input" id="sendAmount" placeholder="0.00" step="any" required>
              <div class="currency-select" id="sendCurrencySelect">
                <div class="currency-network" id="sendNetwork">BTC</div>
                <div class="currency-main">
                  <div class="currency-icon" id="sendIcon">₿</div>
                  <span class="currency-code" id="sendCode">BTC</span>
                  <svg class="currency-dropdown-icon" width="12" height="12" viewBox="0 0 12 12">
                    <path fill="currentColor" d="M6 8L1 3h10z"/>
                  </svg>
                </div>
              </div>
            </div>
            <div class="currency-balance">Min: <span id="sendMin">0.001</span> <span id="sendMinCurrency">BTC</span> · Max: <span id="sendMax">420</span> <span id="sendMaxCurrency">BTC</span></div>
          </div>
          
          <!-- Swap Button -->
          <button class="swap-button" id="swapButton">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <path d="M7 16V4M7 4L3 8M7 4l4 4M17 8v12m0 0l4-4m-4 4l-4-4"/>
            </svg>
          </button>
          
          <!-- Receive Input -->
          <div class="currency-input-group">
            <div class="currency-input-label">You Receive</div>
            <div class="currency-input-row">
              <input type="text" class="currency-input" id="receiveAmount" placeholder="0.00" readonly>
              <div class="currency-select" id="receiveCurrencySelect">
                <div class="currency-network" id="receiveNetwork">ERC20</div>
                <div class="currency-main">
                  <div class="currency-icon" id="receiveIcon">Ξ</div>
                  <span class="currency-code" id="receiveCode">ETH</span>
                  <svg class="currency-dropdown-icon" width="12" height="12" viewBox="0 0 12 12">
                    <path fill="currentColor" d="M6 8L1 3h10z"/>
                  </svg>
                </div>
              </div>
            </div>
          </div>
          
          <!-- Exchange Rate Info -->
          <div class="exchange-rate-info">
            <div class="exchange-rate-row">
              <span class="exchange-rate-label">Exchange Rate</span>
              <span class="exchange-rate-value highlight" id="exchangeRate">-</span>
            </div>
            <div class="exchange-rate-row">
              <span class="exchange-rate-label">Network Fee</span>
              <span class="exchange-rate-value" id="networkFee">-</span>
            </div>
            <div class="exchange-rate-row">
              <span class="exchange-rate-label">Processing Time</span>
              <span class="exchange-rate-value" id="processingTime">2-5 minutes</span>
            </div>
          </div>
          
          <!-- Exchange Button -->
          <button class="btn btn-primary btn-glow exchange-button" id="exchangeButton">
            Exchange Now
          </button>
        </div>
      </div>
    </div>
  </section>

  <!-- Currency Selection Modal -->
  <div class="modal" id="currencyModal">
    <div class="modal-content currency-modal-content">
      <div class="modal-header">
        <h3>Select Currency</h3>
        <button class="modal-close" id="currencyModalClose">×</button>
      </div>
      <input type="text" class="input" id="currencySearch" placeholder="Search currency...">
      <div class="currency-list" id="currencyList">
        <!-- Currencies will be loaded dynamically -->
      </div>
    </div>
  </div>

  <!-- Order Modal -->
  <div class="modal" id="orderModal">
    <div class="modal-content order-modal-content">
      <div class="modal-header">
        <h3>Exchange Order</h3>
        <button class="modal-close" id="orderModalClose">×</button>
      </div>
      <div id="orderContent">
        <!-- Order content will be loaded dynamically -->
      </div>
    </div>
  </div>

  <!-- Footer -->
  <footer style="position: fixed; bottom: 0; left: 0; right: 0; text-align: center; padding: 16px; font-size: 0.85rem; color: var(--text-muted); background: rgba(10, 10, 15, 0.8); backdrop-filter: blur(10px); border-top: 1px solid rgba(0, 245, 255, 0.1); z-index: 100;">
    Supported by <a href="https://www.lightningex.io" target="_blank" style="color: var(--neon-cyan); text-decoration: none;">LightningEX</a>
  </footer>

  <script src="js/app.js"></script>
  <script src="js/exchange.js"></script>
</body>
</html>


path: assets/ui/js/app.js
content: /**
 * LightningEX - Simplified Application
 */

// API Configuration
const API_BASE_URL = 'https://api.lightningex.io';

// App State
const state = {
  currencies: [],
  selectedSendCurrency: null,
  selectedReceiveCurrency: null,
  exchangeRate: null,
  rateTimer: null
};

// Initialize App
document.addEventListener('DOMContentLoaded', () => {
  initApp();
});

function initApp() {
  // Only load currencies on the exchange page (index.html)
  const path = window.location.pathname;
  const isIndexPage = path.endsWith('index.html') || path === '/' || path.endsWith('/ui/');
  if (isIndexPage) {
    loadCurrencies();
  }
}

// Load Currencies
async function loadCurrencies() {
  try {
    const response = await fetch(`${API_BASE_URL}/exchange/currency/list`);
    const result = await response.json();
    
    if (result.code === 200 && result.data) {
      state.currencies = result.data;
      
      // Set default currencies (BTC on BTC network, ETH on ERC20 network)
      if (state.currencies.length > 0) {
        const btc = state.currencies.find(c => c.currency === 'BTC');
        const eth = state.currencies.find(c => c.currency === 'ETH');
        
        if (btc) {
          const btcNetwork = btc.networkList.find(n => n.network === 'BTC') || btc.networkList[0];
          state.selectedSendCurrency = { ...btc, network: btcNetwork?.network, networkName: btcNetwork?.name };
        }
        if (eth) {
          const ethNetwork = eth.networkList.find(n => n.network === 'ERC20') || 
                            eth.networkList.find(n => n.network === 'ETH') || 
                            eth.networkList[0];
          state.selectedReceiveCurrency = { ...eth, network: ethNetwork?.network, networkName: ethNetwork?.name };
        }
        
        updateCurrencyUI();
        
        // Fetch initial Pair Info after currencies loaded
        if (state.selectedSendCurrency && state.selectedReceiveCurrency) {
          fetchPairInfo();
        }
      }
    }
  } catch (error) {
    console.error('Failed to load currencies:', error);
  }
}

function updateCurrencyUI() {
  // Update send currency
  const sendIcon = document.getElementById('sendIcon');
  const sendCode = document.getElementById('sendCode');
  const sendNetwork = document.getElementById('sendNetwork');
  
  if (sendIcon && state.selectedSendCurrency) {
    sendIcon.textContent = state.selectedSendCurrency.currency.charAt(0);
  }
  if (sendCode && state.selectedSendCurrency) {
    sendCode.textContent = state.selectedSendCurrency.currency;
  }
  if (sendNetwork && state.selectedSendCurrency) {
    sendNetwork.textContent = state.selectedSendCurrency.networkName || state.selectedSendCurrency.network;
  }
  
  // Update receive currency
  const receiveIcon = document.getElementById('receiveIcon');
  const receiveCode = document.getElementById('receiveCode');
  const receiveNetwork = document.getElementById('receiveNetwork');
  
  if (receiveIcon && state.selectedReceiveCurrency) {
    receiveIcon.textContent = state.selectedReceiveCurrency.currency.charAt(0);
  }
  if (receiveCode && state.selectedReceiveCurrency) {
    receiveCode.textContent = state.selectedReceiveCurrency.currency;
  }
  if (receiveNetwork && state.selectedReceiveCurrency) {
    receiveNetwork.textContent = state.selectedReceiveCurrency.networkName || state.selectedReceiveCurrency.network;
  }
}

// Fetch Pair Info (min/max limits)
async function fetchPairInfo() {
  if (!state.selectedSendCurrency || !state.selectedReceiveCurrency) return;
  
  try {
    const response = await fetch(
      `${API_BASE_URL}/exchange/pair/info?send=${state.selectedSendCurrency.currency}&receive=${state.selectedReceiveCurrency.currency}`
    );
    const result = await response.json();
    
    if (result.code === 200 && result.data) {
      // Update min/max display
      const sendMin = document.getElementById('sendMin');
      const sendMax = document.getElementById('sendMax');
      const sendMinCurrency = document.getElementById('sendMinCurrency');
      const sendMaxCurrency = document.getElementById('sendMaxCurrency');
      
      if (sendMin) sendMin.textContent = result.data.min || '0.001';
      if (sendMax) sendMax.textContent = result.data.max || '420';
      if (sendMinCurrency) sendMinCurrency.textContent = state.selectedSendCurrency.currency;
      if (sendMaxCurrency) sendMaxCurrency.textContent = state.selectedSendCurrency.currency;
    }
  } catch (error) {
    console.error('Failed to fetch pair info:', error);
  }
}

// API Helpers
async function apiGet(endpoint, params = {}) {
  const queryString = new URLSearchParams(params).toString();
  const url = `${API_BASE_URL}${endpoint}${queryString ? '?' + queryString : ''}`;

  const response = await fetch(url);
  return response.json();
}

async function apiPost(endpoint, data = {}) {
  const url = `${API_BASE_URL}${endpoint}`;
  
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
  });
  
  return response.json();
}

// Utility Functions
function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

function formatNumber(num, decimals = 8) {
  return parseFloat(num).toFixed(decimals).replace(/\.?0+$/, '');
}

function copyToClipboard(text) {
  navigator.clipboard.writeText(text).then(() => {
    alert('Copied to clipboard!');
  }).catch(() => {
    alert('Failed to copy');
  });
}

// Toast Notifications
function showToast(type, message) {
  const container = document.querySelector('.toast-container') || createToastContainer();
  
  const toast = document.createElement('div');
  toast.style.cssText = `
    background: var(--bg-card);
    border: 1px solid ${type === 'error' ? '#ff4444' : type === 'success' ? '#00ff88' : '#00f5ff'};
    border-radius: 8px;
    padding: 12px 16px;
    margin-bottom: 8px;
    color: var(--text-primary);
    font-size: 14px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.3);
    animation: slideIn 0.3s ease;
  `;
  toast.textContent = message;
  
  container.appendChild(toast);
  
  setTimeout(() => {
    toast.remove();
  }, 3000);
}

function createToastContainer() {
  const container = document.createElement('div');
  container.className = 'toast-container';
  container.style.cssText = `
    position: fixed;
    top: 80px;
    right: 20px;
    z-index: 9999;
    display: flex;
    flex-direction: column;
    gap: 8px;
  `;
  document.body.appendChild(container);
  return container;
}

// Export for other modules
window.API_BASE_URL = API_BASE_URL;
window.state = state;
window.apiGet = apiGet;
window.apiPost = apiPost;
window.escapeHtml = escapeHtml;
window.formatNumber = formatNumber;
window.copyToClipboard = copyToClipboard;
window.updateCurrencyUI = updateCurrencyUI;
window.fetchPairInfo = fetchPairInfo;
window.showToast = showToast;


path: assets/ui/js/exchange.js
content: /**
 * LightningEX - Exchange Functionality
 */

document.addEventListener('DOMContentLoaded', () => {
  initExchange();
});

function initExchange() {
  initCurrencySelectors();
  initAmountInputs();
  initSwapButton();
  initExchangeButton();
  startRateTimer();
  // Initialize exchange rate display as empty
  clearExchangeRateUI();
}

// Currency Selectors
function initCurrencySelectors() {
  const sendSelect = document.getElementById('sendCurrencySelect');
  const receiveSelect = document.getElementById('receiveCurrencySelect');
  
  if (sendSelect) {
    sendSelect.addEventListener('click', () => openCurrencyModal('send'));
  }
  
  if (receiveSelect) {
    receiveSelect.addEventListener('click', () => openCurrencyModal('receive'));
  }
}

function openCurrencyModal(type) {
  const modal = document.getElementById('currencyModal');
  const search = document.getElementById('currencySearch');
  const list = document.getElementById('currencyList');
  const close = document.getElementById('currencyModalClose');
  
  if (!modal || !list) return;
  
  // Render currency list
  renderCurrencyList(type);
  
  modal.classList.add('active');
  
  // Search functionality
  if (search) {
    search.value = '';
    search.focus();
    search.oninput = () => filterCurrencies(search.value, type);
  }
  
  // Close handlers
  close.onclick = () => modal.classList.remove('active');
  modal.onclick = (e) => {
    if (e.target === modal) modal.classList.remove('active');
  };
}

function renderCurrencyList(type) {
  const list = document.getElementById('currencyList');
  if (!list || !window.state.currencies.length) return;
  
  const isSend = type === 'send';
  
  list.innerHTML = window.state.currencies.map(currency => {
    const networks = currency.networkList || [];
    const availableNetworks = isSend 
      ? networks.filter(n => n.sendStatus)
      : networks.filter(n => n.receiveStatus);
    
    if (availableNetworks.length === 0) return '';
    
    return availableNetworks.map(network => {
      const isSelected = isSend 
        ? window.state.selectedSendCurrency?.currency === currency.currency && 
          window.state.selectedSendCurrency?.network === network.network
        : window.state.selectedReceiveCurrency?.currency === currency.currency &&
          window.state.selectedReceiveCurrency?.network === network.network;
      
      return `
        <div class="currency-list-item ${isSelected ? 'active' : ''}" 
             data-currency="${currency.currency}" 
             data-network="${network.network}">
          <div class="currency-list-icon">${currency.currency.charAt(0)}</div>
          <div class="currency-list-info">
            <div class="currency-list-code">${currency.currency}</div>
            <div class="currency-list-name">${currency.name}</div>
          </div>
          <span class="currency-list-network">${network.name}</span>
        </div>
      `;
    }).join('');
  }).join('');
  
  // Add click handlers
  list.querySelectorAll('.currency-list-item').forEach(item => {
    item.addEventListener('click', () => {
      const currencyCode = item.dataset.currency;
      const networkCode = item.dataset.network;
      selectCurrency(type, currencyCode, networkCode);
      document.getElementById('currencyModal').classList.remove('active');
    });
  });
}

function filterCurrencies(query, type) {
  const items = document.querySelectorAll('.currency-list-item');
  const lowerQuery = query.toLowerCase();
  
  items.forEach(item => {
    const code = item.dataset.currency.toLowerCase();
    const name = item.querySelector('.currency-list-name').textContent.toLowerCase();
    
    if (code.includes(lowerQuery) || name.includes(lowerQuery)) {
      item.style.display = 'flex';
    } else {
      item.style.display = 'none';
    }
  });
}

function selectCurrency(type, currencyCode, networkCode) {
  const currency = window.state.currencies.find(c => c.currency === currencyCode);
  if (!currency) return;
  
  const network = currency.networkList.find(n => n.network === networkCode);
  if (!network) return;
  
  const selection = {
    ...currency,
    network: networkCode,
    networkName: network.name
  };
  
  if (type === 'send') {
    window.state.selectedSendCurrency = selection;
  } else {
    window.state.selectedReceiveCurrency = selection;
  }
  
  updateCurrencyUI();
  
  // Always fetch Pair Info first when currency/network changes (for min/max amount)
  fetchPairInfo().then(() => {
    // Then fetch Exchange Rate if there's a valid amount
    const sendAmount = document.getElementById('sendAmount')?.value;
    if (sendAmount && parseFloat(sendAmount) > 0) {
      fetchExchangeRate();
    }
  });
}

// Amount Inputs
function initAmountInputs() {
  const sendInput = document.getElementById('sendAmount');

  if (sendInput) {
    // Prevent invalid input
    sendInput.addEventListener('keydown', (e) => {
      // Prevent minus sign
      if (e.key === '-') {
        e.preventDefault();
      }
    });

    sendInput.addEventListener('input', () => {
      let value = sendInput.value;

      // Prevent decimal point at start
      if (value.startsWith('.')) {
        value = '0' + value;
      }

      // Remove leading zeros (except for "0." case)
      if (value.length > 1 && value.startsWith('0') && !value.startsWith('0.')) {
        value = value.replace(/^0+/, '');
      }

      // Ensure value is positive
      if (parseFloat(value) < 0) {
        value = Math.abs(parseFloat(value)).toString();
      }

      // Update input if changed
      if (value !== sendInput.value) {
        sendInput.value = value;
      }

      // Validate min/max and update border color
      validateAmountRange();

      // Fetch rate with debounce only if valid amount (1.5s delay)
      debounce(() => {
        const numValue = parseFloat(sendInput.value);
        if (numValue > 0) {
          fetchExchangeRate();
        } else {
          // Clear exchange rate display when amount is empty or 0
          clearExchangeRateUI();
        }
      }, 1500)();
    });
  }
}

function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// Validate amount against min/max and update border color
function validateAmountRange() {
  const sendInput = document.getElementById('sendAmount');
  if (!sendInput) return;
  
  const value = parseFloat(sendInput.value);
  const minAmount = parseFloat(window.state.pairInfo?.minimumAmount || '0');
  const maxAmount = parseFloat(window.state.pairInfo?.maximumAmount || '999999999');
  
  if (sendInput.value && (value < minAmount || value > maxAmount)) {
    sendInput.style.borderColor = '#ff4444';
    sendInput.style.boxShadow = '0 0 10px rgba(255, 68, 68, 0.3)';
  } else {
    sendInput.style.borderColor = '';
    sendInput.style.boxShadow = '';
  }
}

// Swap Button
function initSwapButton() {
  const swapBtn = document.getElementById('swapButton');
  
  if (swapBtn) {
    swapBtn.addEventListener('click', () => {
      // Swap currencies
      const temp = window.state.selectedSendCurrency;
      window.state.selectedSendCurrency = window.state.selectedReceiveCurrency;
      window.state.selectedReceiveCurrency = temp;
      
      // Swap amounts
      const sendInput = document.getElementById('sendAmount');
      const receiveInput = document.getElementById('receiveAmount');
      
      if (sendInput && receiveInput) {
        const tempAmount = sendInput.value;
        sendInput.value = receiveInput.value;
        receiveInput.value = tempAmount;
      }
      
      updateCurrencyUI();
      
      // Always fetch Pair Info first after swap (for min/max amount)
      fetchPairInfo().then(() => {
        // Then fetch Exchange Rate if there's a valid amount
        const sendAmount = document.getElementById('sendAmount')?.value;
        if (sendAmount && parseFloat(sendAmount) > 0) {
          fetchExchangeRate();
        }
      });
    });
  }
}

// Exchange Button
function initExchangeButton() {
  const exchangeBtn = document.getElementById('exchangeButton');
  
  if (exchangeBtn) {
    exchangeBtn.addEventListener('click', startExchange);
  }
}

async function startExchange() {
  const sendAmountInput = document.getElementById('sendAmount');
  const sendAmount = sendAmountInput.value;

  // Browser validation for amount
  if (!sendAmount || parseFloat(sendAmount) <= 0) {
    sendAmountInput.focus();
    window.showToast('error', 'Please enter a valid amount');
    return;
  }

  // Validate amount is within min/max range
  const numAmount = parseFloat(sendAmount);
  const minAmount = parseFloat(window.state.pairInfo?.minimumAmount || '0');
  const maxAmount = parseFloat(window.state.pairInfo?.maximumAmount || '999999999');
  
  if (numAmount < minAmount || numAmount > maxAmount) {
    sendAmountInput.focus();
    window.showToast('error', `Amount must be between ${minAmount} and ${maxAmount} ${window.state.selectedSendCurrency?.currency || ''}`);
    return;
  }

  if (!window.state.selectedSendCurrency || !window.state.selectedReceiveCurrency) {
    window.showToast('error', 'Please select currencies');
    return;
  }

  // Open order modal with address input step
  openOrderModal('address');
}

// API Calls
async function fetchPairInfo() {
  if (!window.state.selectedSendCurrency || !window.state.selectedReceiveCurrency) return;
  
  try {
    const result = await window.apiGet('/exchange/pair/info', {
      send: window.state.selectedSendCurrency.currency,
      receive: window.state.selectedReceiveCurrency.currency,
      sendNetwork: window.state.selectedSendCurrency.network,
      receiveNetwork: window.state.selectedReceiveCurrency.network
    });
    
    if (result.code === 200 && result.data) {
      updatePairInfo(result.data);
    }
  } catch (error) {
    console.error('Failed to fetch pair info:', error);
  }
}

function updatePairInfo(data) {
  // Save pair info to state for validation
  window.state.pairInfo = data;
  
  const sendMin = document.getElementById('sendMin');
  const sendMax = document.getElementById('sendMax');
  const sendMinCurrency = document.getElementById('sendMinCurrency');
  const sendMaxCurrency = document.getElementById('sendMaxCurrency');
  const networkFee = document.getElementById('networkFee');
  const processingTime = document.getElementById('processingTime');
  
  if (sendMin) sendMin.textContent = data.minimumAmount || '0.001';
  if (sendMax) sendMax.textContent = data.maximumAmount || '420';
  if (sendMinCurrency && window.state.selectedSendCurrency) sendMinCurrency.textContent = window.state.selectedSendCurrency.currency;
  if (sendMaxCurrency && window.state.selectedSendCurrency) sendMaxCurrency.textContent = window.state.selectedSendCurrency.currency;
  if (networkFee) networkFee.textContent = `${data.networkFee || '0'} ${window.state.selectedReceiveCurrency?.currency || ''}`;
  if (processingTime) processingTime.textContent = `${data.processingTime || '2-5'} minutes`;
  
  // Re-validate current input
  validateAmountRange();
}

async function fetchExchangeRate() {
  const sendAmount = document.getElementById('sendAmount').value;
  
  if (!sendAmount || parseFloat(sendAmount) <= 0) return;
  if (!window.state.selectedSendCurrency || !window.state.selectedReceiveCurrency) return;
  
  try {
    const result = await window.apiGet('/exchange/rate', {
      send: window.state.selectedSendCurrency.currency,
      receive: window.state.selectedReceiveCurrency.currency,
      sendNetwork: window.state.selectedSendCurrency.network,
      receiveNetwork: window.state.selectedReceiveCurrency.network,
      amount: sendAmount
    });
    
    if (result.code === 200 && result.data) {
      window.state.exchangeRate = result.data;
      updateExchangeRateUI(result.data);
    }
  } catch (error) {
    console.error('Failed to fetch exchange rate:', error);
  }
}

function updateExchangeRateUI(data) {
  const receiveInput = document.getElementById('receiveAmount');
  const exchangeRate = document.getElementById('exchangeRate');
  const networkFee = document.getElementById('networkFee');
  const processingTime = document.getElementById('processingTime');
  
  // Save rate data to state for use in order modal
  window.state.currentRate = data;
  
  if (receiveInput) receiveInput.value = data.receiveAmount || '0.00';
  if (exchangeRate) {
    const rate = data.rate || '0';
    exchangeRate.textContent = `1 ${window.state.selectedSendCurrency?.currency} ≈ ${rate} ${window.state.selectedReceiveCurrency?.currency}`;
  }
  if (networkFee) networkFee.textContent = `${data.networkFee || '0.0005'} ${window.state.selectedReceiveCurrency?.currency || ''}`;
  if (processingTime) processingTime.textContent = `${data.processingTime || '2-5'} minutes`;
}

// Clear exchange rate display when no valid amount
function clearExchangeRateUI() {
  const receiveInput = document.getElementById('receiveAmount');
  const exchangeRate = document.getElementById('exchangeRate');
  
  if (receiveInput) receiveInput.value = '';
  if (exchangeRate) exchangeRate.textContent = '--';
}

// Rate Timer
function startRateTimer() {
  const timerEl = document.getElementById('rateTimer');
  if (!timerEl) return;
  
  let timeLeft = 20;
  
  if (window.state.rateTimer) {
    clearInterval(window.state.rateTimer);
  }
  
  window.state.rateTimer = setInterval(() => {
    timeLeft--;
    if (timeLeft <= 0) {
      timeLeft = 20;
      // Only fetch rate if there's a valid amount
      const sendAmount = document.getElementById('sendAmount')?.value;
      if (sendAmount && parseFloat(sendAmount) > 0) {
        fetchExchangeRate();
      }
    }
    timerEl.textContent = timeLeft;
  }, 1000);
}

// Order Modal
function openOrderModal(step, orderData = null) {
  const modal = document.getElementById('orderModal');
  const content = document.getElementById('orderContent');
  const close = document.getElementById('orderModalClose');
  
  if (!modal || !content) return;
  
  if (step === 'address') {
    content.innerHTML = renderAddressStep();
    initAddressStep();
  } else if (step === 'deposit' && orderData) {
    content.innerHTML = renderDepositStep(orderData);
    initDepositStep(orderData);
    startOrderStatusCheck(orderData.id);
  } else if (step === 'status' && orderData) {
    content.innerHTML = renderStatusStep(orderData);
  }
  
  modal.classList.add('active');
  
  close.onclick = () => {
    modal.classList.remove('active');
    stopOrderStatusCheck();
  };
}

function renderAddressStep() {
  const sendAmount = document.getElementById('sendAmount').value;
  const receiveAmount = document.getElementById('receiveAmount').value;
  const rate = window.state.currentRate?.rate || '0';
  const processingTime = window.state.currentRate?.processingTime || '3-5';
  
  return `
    <div class="order-step">
      <div class="order-step-icon">📋</div>
      <h3 class="order-step-title">Enter Your Address</h3>
      <p class="order-step-desc">Please provide the wallet address where you want to receive ${window.state.selectedReceiveCurrency?.currency}</p>
      
      <div class="order-details">
        <div class="order-detail-row">
          <span class="order-detail-label">You Send</span>
          <span class="order-detail-value">${sendAmount} ${window.state.selectedSendCurrency?.currency} <span style="color: var(--text-muted); font-size: 0.875rem;">(${window.state.selectedSendCurrency?.networkName})</span></span>
        </div>
        <div class="order-detail-row">
          <span class="order-detail-label">You Receive</span>
          <span class="order-detail-value highlight">${receiveAmount} ${window.state.selectedReceiveCurrency?.currency} <span style="color: var(--text-muted); font-size: 0.875rem;">(${window.state.selectedReceiveCurrency?.networkName})</span></span>
        </div>
        <div class="order-detail-row">
          <span class="order-detail-label">Exchange Rate</span>
          <span class="order-detail-value">1 ${window.state.selectedSendCurrency?.currency} ≈ ${rate} ${window.state.selectedReceiveCurrency?.currency}</span>
        </div>
        <div class="order-detail-row">
          <span class="order-detail-label">Network Fee</span>
          <span class="order-detail-value">${window.state.currentRate?.networkFee || '0'} ${window.state.selectedReceiveCurrency?.currency}</span>
        </div>
        <div class="order-detail-row">
          <span class="order-detail-label">Processing Time</span>
          <span class="order-detail-value">${processingTime} minutes</span>
        </div>
      </div>
      
      <div style="text-align: left; margin: var(--space-lg) 0;">
        <label style="display: block; margin-bottom: var(--space-sm); color: var(--text-secondary); font-size: 0.875rem;">
          ${window.state.selectedReceiveCurrency?.currency} Address (${window.state.selectedReceiveCurrency?.networkName})
        </label>
        <input type="text" class="input" id="receiveAddress" placeholder="Enter your ${window.state.selectedReceiveCurrency?.currency} address" required>
        <p style="margin-top: var(--space-sm); font-size: 0.75rem; color: var(--text-muted);">
          Make sure the address supports ${window.state.selectedReceiveCurrency?.networkName} network
        </p>
      </div>
      
      <button class="btn btn-primary btn-glow" id="confirmAddressBtn" style="width: 100%;">
        Confirm & Create Order
      </button>
    </div>
  `;
}

function initAddressStep() {
  const confirmBtn = document.getElementById('confirmAddressBtn');
  
  if (confirmBtn) {
    confirmBtn.addEventListener('click', async () => {
      const addressInput = document.getElementById('receiveAddress');
      const address = addressInput.value.trim();
      
      if (!address) {
        addressInput.focus();
        window.showToast('error', 'Please enter a valid address');
        return;
      }
      
      // Validate address
      try {
        const validation = await window.apiGet('/exchange/address/validate', {
          currency: window.state.selectedReceiveCurrency.currency,
          address: address,
          network: window.state.selectedReceiveCurrency.network
        });
        
        if (validation.code !== 200 || !validation.data) {
          window.showToast('error', 'Invalid address. Please check and try again.');
          return;
        }
        
        // Create order
        await createOrder(address);
      } catch (error) {
        console.error('Validation error:', error);
        window.showToast('error', 'Failed to validate address. Please try again.');
      }
    });
  }
}

async function createOrder(address) {
  const sendAmount = document.getElementById('sendAmount').value;

  try {
    const result = await window.apiPost('/exchange/order/place', {
      send: window.state.selectedSendCurrency.currency,
      receive: window.state.selectedReceiveCurrency.currency,
      sendNetwork: window.state.selectedSendCurrency.network,
      receiveNetwork: window.state.selectedReceiveCurrency.network,
      amount: parseFloat(sendAmount),
      receiveAddress: address
    });
    
    if (result.code === 200 && result.data) {
      window.showToast('success', 'Order created successfully!');
      
      // Redirect to order detail page
      window.location.href = `order.html?id=${result.data}`;
    } else {
      window.showToast('error', result.msg || 'Failed to create order');
    }
  } catch (error) {
    console.error('Create order error:', error);
    window.showToast('error', 'Failed to create order. Please try again.');
  }
}

function renderDepositStep(order) {
  const statusClass = getStatusClass(order.status);
  const statusTip = getStatusTip(order);
  
  return `
    <div class="order-step">
      <div class="order-step-icon">💎</div>
      <h3 class="order-step-title">Awaiting Your Deposit</h3>
      
      <div class="order-status-badge ${statusClass}">
        ${order.status}
      </div>
      
      <p class="order-step-desc" style="margin-top: var(--space-md);">${statusTip}</p>
      
      <div class="order-details">
        <div class="order-detail-row">
          <span class="order-detail-label">Order ID</span>
          <span class="order-detail-value">${order.id}</span>
        </div>
        <div class="order-detail-row">
          <span class="order-detail-label">Send Amount</span>
          <span class="order-detail-value">${order.sendAmount} ${order.send}</span>
        </div>
        <div class="order-detail-row">
          <span class="order-detail-label">Receive Amount</span>
          <span class="order-detail-value highlight">${order.receiveAmount} ${order.receive}</span>
        </div>
      </div>
      
      <div style="margin: var(--space-lg) 0;">
        <label style="display: block; margin-bottom: var(--space-sm); color: var(--text-secondary); font-size: 0.875rem;">
          Send ${order.send} to this address (${order.sendNetwork})
        </label>
        <div class="order-address-box">
          <span class="order-address">${order.sendAddress}</span>
          <button class="order-copy-btn" onclick="window.copyToClipboard('${order.sendAddress}')">Copy</button>
        </div>
        ${order.sendTag ? `
          <label style="display: block; margin-top: var(--space-md); margin-bottom: var(--space-sm); color: var(--text-secondary); font-size: 0.875rem;">
            Tag/MEMO (Required)
          </label>
          <div class="order-address-box">
            <span class="order-address">${order.sendTag}</span>
            <button class="order-copy-btn" onclick="window.copyToClipboard('${order.sendTag}')">Copy</button>
          </div>
        ` : ''}
      </div>
      
      <div class="order-progress">
        <div class="order-progress-bar" style="width: ${getProgressWidth(order.status)}%"></div>
        <div class="order-progress-step ${order.status === 'Awaiting Deposit' ? 'active' : 'completed'}">
          <div class="order-progress-dot">1</div>
          <span class="order-progress-label">Deposit</span>
        </div>
        <div class="order-progress-step ${order.status === 'Confirming Deposit' ? 'active' : (['Exchanging', 'Sending', 'Complete'].includes(order.status) ? 'completed' : '')}">
          <div class="order-progress-dot">2</div>
          <span class="order-progress-label">Confirm</span>
        </div>
        <div class="order-progress-step ${order.status === 'Exchanging' ? 'active' : (['Sending', 'Complete'].includes(order.status) ? 'completed' : '')}">
          <div class="order-progress-dot">3</div>
          <span class="order-progress-label">Exchange</span>
        </div>
        <div class="order-progress-step ${order.status === 'Sending' ? 'active' : (order.status === 'Complete' ? 'completed' : '')}">
          <div class="order-progress-dot">4</div>
          <span class="order-progress-label">Send</span>
        </div>
      </div>
    </div>
  `;
}

function initDepositStep(order) {
  // Any additional initialization for deposit step
}

function getStatusClass(status) {
  const statusMap = {
    'Awaiting Deposit': 'awaiting',
    'Confirming Deposit': 'confirming',
    'Exchanging': 'exchanging',
    'Sending': 'sending',
    'Complete': 'complete',
    'Failed': 'failed',
    'Action Request': 'failed',
    'Request Overdue': 'failed'
  };
  return statusMap[status] || 'awaiting';
}

function getStatusTip(order) {
  const tips = {
    'Awaiting Deposit': `Your order will automatically proceed to the next step once your deposit receives its first confirmation on the blockchain. If you do not deposit ${order.sendAmount} ${order.send}, or your deposit does not arrive within 1 hour, for security purposes your order will not be processed automatically.`,
    'Confirming Deposit': `You successfully sent ${order.sendAmount} ${order.send}! Please wait while your deposit is being confirmed. Your order will automatically proceed to the next step after the deposit transaction gets 1 confirmation. Nothing more is expected from you in this step.`,
    'Exchanging': `Your deposit has been confirmed! We are now exchanging your ${order.sendAmount} ${order.send} to ${order.receiveAmount} ${order.receive}. This may take a few minutes, please be patient.`,
    'Sending': `Your order is almost complete! We are forwarding ${order.receiveAmount} ${order.receive} to the following address: ${order.receiveAddress}`,
    'Complete': `Your order is successfully completed! We forwarded ${order.receiveAmount} ${order.receive} to the following address: ${order.receiveAddress}`,
    'Failed': 'The order failed because of one of the following reasons: You didn\'t send the correct amount, your deposit was not confirmed within 2 hours, or another reason.',
    'Action Request': 'This transaction has been detected with risks and needs to be verified before proceeding. Please contact lightningex.io for support.',
    'Request Overdue': 'The transaction request has expired. If you have other questions, please seek support from lightningex.io.'
  };
  return tips[order.status] || '';
}

function getProgressWidth(status) {
  const progressMap = {
    'Awaiting Deposit': 0,
    'Confirming Deposit': 25,
    'Exchanging': 50,
    'Sending': 75,
    'Complete': 100,
    'Failed': 0,
    'Action Request': 0,
    'Request Overdue': 0
  };
  return progressMap[status] || 0;
}

// Order Status Polling
function startOrderStatusCheck(orderId) {
  if (window.state.orderCheckInterval) {
    clearInterval(window.state.orderCheckInterval);
  }
  
  window.state.orderCheckInterval = setInterval(async () => {
    try {
      const result = await window.apiGet('/exchange/order/get', { id: orderId });
      
      if (result.code === 200 && result.data) {
        const order = result.data;
        
        // Update UI if status changed
        if (order.status !== document.querySelector('.order-status-badge')?.textContent.trim()) {
          openOrderModal('deposit', order);
          
          if (order.status === 'Complete') {
            window.showToast('success', 'Your exchange is complete!');
            stopOrderStatusCheck();
          } else if (['Failed', 'Action Request', 'Request Overdue'].includes(order.status)) {
            window.showToast('error', 'Order issue detected. Please contact support.');
            stopOrderStatusCheck();
          }
        }
      }
    } catch (error) {
      console.error('Status check error:', error);
    }
  }, 15000); // Check every 15 seconds
}

function stopOrderStatusCheck() {
  if (window.state.orderCheckInterval) {
    clearInterval(window.state.orderCheckInterval);
    window.state.orderCheckInterval = null;
  }
}


path: assets/ui/js/order.js
content: /**
 * LightningEX - Order Detail Page
 */

const API_BASE = 'https://api.lightningex.io';
let orderId = null;
let pollInterval = null;

function getOrderIdFromUrl() {
  const params = new URLSearchParams(window.location.search);
  return params.get('id');
}

document.addEventListener('DOMContentLoaded', () => {
  orderId = getOrderIdFromUrl();
  if (!orderId) {
    showError();
    return;
  }
  document.getElementById('displayOrderId').textContent = orderId;
  loadOrderStatus();
  pollInterval = setInterval(loadOrderStatus, 15000);
});

function showError() {
  document.getElementById('loadingState').classList.add('hidden');
  document.getElementById('errorState').classList.remove('hidden');
}

async function loadOrderStatus() {
  try {
    const response = await fetch(`${API_BASE}/exchange/order/get?id=${orderId}`);
    const result = await response.json();
    
    if (result.code !== 200) {
      showError();
      return;
    }

    const data = result.data;
    document.getElementById('loadingState').classList.add('hidden');
    document.getElementById('orderContent').classList.remove('hidden');

    updateProgressSteps(data.status);
    updateStatusContent(data);
    updateTransactionDetails(data);

    if (['Complete', 'Failed', 'Refund', 'Request Overdue'].includes(data.status)) {
      clearInterval(pollInterval);
    }
  } catch (error) {
    console.error('Failed to load order:', error);
  }
}

function updateProgressSteps(currentStatus) {
  const steps = document.querySelectorAll('.step');
  const statusOrder = ['Awaiting Deposit', 'Confirming Deposit', 'Exchanging', 'Sending', 'Complete'];
  const currentIndex = statusOrder.indexOf(currentStatus);

  steps.forEach((step, index) => {
    step.classList.remove('active', 'completed');
    if (index < currentIndex) {
      step.classList.add('completed');
      step.querySelector('.step-circle').textContent = '✓';
    } else if (index === currentIndex) {
      step.classList.add('active');
      step.querySelector('.step-circle').textContent = index + 1;
    } else {
      step.querySelector('.step-circle').textContent = index + 1;
    }
  });

  if (['Failed', 'Refund', 'Action Request', 'Request Overdue'].includes(currentStatus)) {
    steps.forEach(step => step.classList.remove('active'));
  }
}

const STATUS_MESSAGES = {
  'Awaiting Deposit': {
    lines: [
      { text: 'Your order will automatically proceed to the next step once your deposit receives its first confirmation on the blockchain.' },
      { text: 'If you do not deposit the amount shown above, or your deposit does not arrive within 1 hour, for security purposes your order will not be processed automatically.' }
    ]
  },
  'Confirming Deposit': {
    lines: [
      { text: 'You successfully sent {sendAmount} {send}! Please wait while your deposit is being confirmed.', highlight: 'You successfully sent {sendAmount} {send}!' },
      { text: 'Your order will automatically proceed to the next step after the deposit transaction gets 1 confirmation.' },
      { text: 'Nothing more is expected from you in this step.' }
    ]
  },
  'Exchanging': {
    lines: [
      { text: 'Your deposit has been confirmed! We are now exchanging your {sendAmount} {send} to {receiveAmount} {receive}.', highlight: 'We are now exchanging your {sendAmount} {send} to {receiveAmount} {receive}' },
      { text: 'This may take a few minutes, please be patient.' }
    ]
  },
  'Sending': {
    lines: [
      { text: 'Your order is almost complete! We are forwarding {receiveAmount} {receive} to the following address: {receiveAddress}', highlight: 'We are forwarding {receiveAmount} {receive}' }
    ]
  },
  'Complete': {
    lines: [
      { text: 'Your order is successfully completed! We forwarded {receiveAmount} {receive} to the following address: {receiveAddress}', highlight: 'We forwarded {receiveAmount} {receive}' }
    ]
  },
  'Action Request': {
    lines: [
      { text: 'This transaction has been detected with risks and needs to be verified before proceeding.' },
      { text: 'Please contact lightningex.io for support.' }
    ]
  },
  'Failed': {
    lines: [
      { text: 'The order failed because of one of the following reasons:', highlight: 'The order failed' },
      { text: '• You didn\'t send {sendAmount} {send} on the {sendNetwork} network.' },
      { text: '• Your deposit was not confirmed within 2 hours.' },
      { text: '• For any other reason.' }
    ]
  },
  'Request Overdue': {
    lines: [
      { text: 'The transaction request has expired.', highlight: 'expired' },
      { text: 'If you have other questions, please seek support from lightningex.io.' }
    ]
  }
};

function formatStatusMessage(status, data) {
  const msgInfo = STATUS_MESSAGES[status];
  if (!msgInfo) return '';

  return msgInfo.lines.map(lineObj => {
    const line = typeof lineObj === 'string' ? lineObj : lineObj.text;
    const highlight = typeof lineObj === 'object' ? lineObj.highlight : null;

    let formatted = line;
    Object.keys(data).forEach(key => {
      if (data[key] !== null && data[key] !== undefined) {
        formatted = formatted.replace(new RegExp(`{${key}}`, 'g'), data[key]);
      }
    });

    if (highlight) {
      let highlightText = highlight;
      Object.keys(data).forEach(key => {
        if (data[key] !== null && data[key] !== undefined) {
          highlightText = highlightText.replace(new RegExp(`{${key}}`, 'g'), data[key]);
        }
      });
      formatted = formatted.replace(highlightText, `<span class="status-highlight">${highlightText}</span>`);
    }

    return formatted;
  }).join('<br>');
}

function updateStatusContent(data) {
  const container = document.getElementById('statusContent');
  const statusClass = getStatusClass(data.status);
  const statusMessage = formatStatusMessage(data.status, data);

  let html = `
    <div class="status-header">
      <div class="status-badge ${statusClass}">${data.status}</div>
    </div>
  `;

  if (statusMessage) {
    html += `
      <div class="status-message-box">
        <p>${statusMessage}</p>
      </div>
    `;
  }

  if (data.status === 'Awaiting Deposit') {
    html += `
      <div class="deposit-section">
        <div class="qr-container">
          <div id="qrcode"></div>
        </div>
        <div class="deposit-info">
          <div class="info-row">
            <span class="info-label">Send ${data.send} on ${data.sendNetwork} network to this address</span>
            <div class="info-value">
              <span class="address-value">${data.sendAddress}</span>
              <button class="copy-btn" onclick="copyToClipboard('${data.sendAddress}')">Copy</button>
            </div>
          </div>
          ${data.sendTag ? `
          <div class="info-row">
            <span class="info-label">Tag/MEMO (Required)</span>
            <div class="info-value">
              <span class="address-value">${data.sendTag}</span>
              <button class="copy-btn" onclick="copyToClipboard('${data.sendTag}')">Copy</button>
            </div>
          </div>
          ` : ''}
          <div class="info-row">
            <span class="info-label">Amount to Send</span>
            <div class="info-value">
              <span>${data.sendAmount} ${data.send}</span>
              <button class="copy-btn" onclick="copyToClipboard('${data.sendAmount}')">Copy</button>
            </div>
          </div>
          <div class="warning-box">
            ⚠️ Please send exactly the specified amount. Sending a different amount may result in delays or additional fees.
          </div>
        </div>
      </div>
    `;

    setTimeout(() => {
      const qrContainer = document.getElementById('qrcode');
      if (qrContainer) {
        qrContainer.innerHTML = '';
        new QRCode(qrContainer, {
          text: data.sendAddress,
          width: 140,
          height: 140,
          colorDark: '#000000',
          colorLight: '#ffffff'
        });
      }
    }, 0);
  }

  if (['Confirming Deposit', 'Exchanging', 'Sending'].includes(data.status)) {
    html += `
      <div class="processing-container">
        <div class="loading-spinner"></div>
        <p style="color: var(--neon-cyan);">Estimated time: ${data.processingTime || '3-5'} minutes</p>
      </div>
    `;
  }

  if (data.status === 'Complete') {
    html += `
      <div class="complete-container">
        <div class="complete-icon">🎉</div>
        <h2 style="color: var(--neon-green); margin-bottom: 20px;">Exchange Complete!</h2>
        <a href="./index.html" class="btn btn-primary">Start New Exchange →</a>
      </div>
    `;
  }

  if (['Failed', 'Refund', 'Action Request', 'Request Overdue'].includes(data.status)) {
    const icon = data.status === 'Action Request' ? '⚠️' : (data.status === 'Request Overdue' ? '⏰' : '❌');
    html += `
      <div class="failed-container">
        <div class="failed-icon">${icon}</div>
        ${data.statusNote ? `<p style="color: var(--text-secondary); margin-top: 10px;">${data.statusNote}</p>` : ''}
        <a href="https://www.lightningex.io" target="_blank" class="btn btn-secondary" style="margin-top: 20px;">Contact Support</a>
      </div>
    `;
  }

  container.innerHTML = html;
}

function updateTransactionDetails(data) {
  document.getElementById('sendAmount').textContent = data.sendAmount;
  document.getElementById('sendCurrency').textContent = `${data.send} (${data.sendNetwork})`;
  document.getElementById('receiveAmount').textContent = data.receiveAmount;
  document.getElementById('receiveCurrency').textContent = `${data.receive} (${data.receiveNetwork})`;

  const detailList = document.getElementById('detailList');
  const createdAt = data.createdAt ? new Date(data.createdAt).toLocaleString() : 'N/A';
  
  detailList.innerHTML = `
    <div class="detail-item">
      <span class="detail-item-label">Order ID</span>
      <span class="detail-item-value">${data.id}</span>
    </div>
    <div class="detail-item">
      <span class="detail-item-label">Created At</span>
      <span class="detail-item-value">${createdAt}</span>
    </div>
    <div class="detail-item">
      <span class="detail-item-label">Receive Address</span>
      <span class="detail-item-value">${data.receiveAddress}</span>
    </div>
    ${data.receiveTag ? `
    <div class="detail-item">
      <span class="detail-item-label">Receive Tag/MEMO</span>
      <span class="detail-item-value">${data.receiveTag}</span>
    </div>
    ` : ''}
    ${data.networkFee ? `
    <div class="detail-item">
      <span class="detail-item-label">Network Fee</span>
      <span class="detail-item-value">${data.networkFee} ${data.receive}</span>
    </div>
    ` : ''}
  `;

  const txLinksContainer = document.getElementById('txLinks');
  let linksHtml = '';
  
  if (data.hashIn && data.hashIn.length > 0 && data.hashInExplorer) {
    linksHtml += `
      <div class="tx-link">
        <span>Incoming Transaction</span>
        <a href="${data.hashInExplorer.replace('{{txid}}', data.hashIn[0])}" target="_blank">View on Explorer →</a>
      </div>
    `;
  }
  
  if (data.hashOut && data.hashOut.length > 0 && data.hashOutExplorer) {
    linksHtml += `
      <div class="tx-link">
        <span>Outgoing Transaction</span>
        <a href="${data.hashOutExplorer.replace('{{txid}}', data.hashOut[0])}" target="_blank">View on Explorer →</a>
      </div>
    `;
  }
  
  txLinksContainer.innerHTML = linksHtml;
}

function getStatusClass(status) {
  const statusClasses = {
    'Awaiting Deposit': 'awaiting',
    'Confirming Deposit': 'confirming',
    'Exchanging': 'exchanging',
    'Sending': 'sending',
    'Complete': 'complete',
    'Failed': 'failed',
    'Refund': 'refund',
    'Action Request': 'action-request',
    'Request Overdue': 'request-overdue'
  };
  return statusClasses[status] || 'awaiting';
}

function copyToClipboard(text) {
  navigator.clipboard.writeText(text).then(() => {
    alert('Copied to clipboard!');
  });
}

function copyOrderUrl() {
  const url = window.location.href;
  navigator.clipboard.writeText(url).then(() => {
    const btn = document.getElementById('copyUrlBtn');
    const originalText = btn.textContent;
    btn.textContent = 'Copied!';
    setTimeout(() => {
      btn.textContent = originalText;
    }, 2000);
  });
}

function copyOrderId() {
  if (!orderId) return;
  navigator.clipboard.writeText(orderId).then(() => {
    alert('Order ID copied!');
  });
}


path: assets/ui/order.html
content: <!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Order Details - LightningEX</title>
  <meta name="description" content="View your LightningEX exchange order details and track status.">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="./css/base.css">
  <link rel="stylesheet" href="./css/layout.css">
  <link rel="stylesheet" href="./css/components.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
  <style>
    /* Top Logo Bar */
    .top-logo-bar {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 20px;
      z-index: 100;
    }

    .top-logo-link {
      display: flex;
      align-items: center;
      gap: 12px;
      text-decoration: none;
      transition: opacity 0.3s ease;
    }

    .top-logo-link:hover {
      opacity: 0.8;
    }

    .top-logo-icon {
      width: 36px;
      height: 36px;
      background: linear-gradient(135deg, #00f5ff 0%, #b829dd 100%);
      border-radius: 8px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 1.2rem;
      box-shadow: 0 0 15px rgba(0, 245, 255, 0.3);
    }

    .top-logo-text {
      font-size: 1.5rem;
      font-weight: 800;
      background: linear-gradient(90deg, #00f5ff 0%, #b829dd 100%);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      background-clip: text;
    }

    .top-logo-highlight {
      background: linear-gradient(90deg, #ffd700 0%, #ffaa00 50%, #ff6b00 100%);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      background-clip: text;
    }

    * {
      box-sizing: border-box;
    }

    html, body {
      min-height: 100vh;
      padding: 80px 20px 60px 20px;
      overflow-x: hidden;
      max-width: 100vw;
    }

    .page-header {
      padding: 40px 0;
      text-align: center;
    }
    
    .page-title {
      font-size: 2rem;
      margin-bottom: 8px;
    }
    
    .order-id-display {
      color: var(--text-secondary);
      font-size: 0.9rem;
      font-family: var(--font-mono);
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 8px;
    }

    .copy-icon {
      width: 16px;
      height: 16px;
      cursor: pointer;
      opacity: 0.6;
      transition: opacity 0.2s;
    }

    .copy-icon:hover {
      opacity: 1;
      color: var(--neon-cyan);
    }
    
    .order-content {
      padding: var(--space-xl) 0;
    }
    
    .order-container {
      max-width: 900px;
      margin: 0 auto;
    }
    
    .progress-card {
      background: var(--bg-card);
      border: 1px solid rgba(0, 245, 255, 0.1);
      border-radius: var(--radius-lg);
      padding: var(--space-2xl);
      margin-bottom: var(--space-xl);
    }
    
    .steps {
      display: flex;
      justify-content: space-between;
      position: relative;
      margin-bottom: var(--space-2xl);
    }
    
    .steps::before {
      content: '';
      position: absolute;
      top: 20px;
      left: 10%;
      right: 10%;
      height: 2px;
      background: rgba(255, 255, 255, 0.1);
      z-index: 0;
    }
    
    .step {
      display: flex;
      flex-direction: column;
      align-items: center;
      position: relative;
      z-index: 1;
      flex: 1;
    }
    
    .step-circle {
      width: 40px;
      height: 40px;
      border-radius: 50%;
      background: rgba(255, 255, 255, 0.1);
      border: 2px solid rgba(255, 255, 255, 0.2);
      display: flex;
      align-items: center;
      justify-content: center;
      font-weight: 600;
      font-size: 14px;
      transition: all 0.3s;
      color: var(--text-secondary);
    }
    
    .step-label {
      margin-top: 8px;
      font-size: 11px;
      color: var(--text-muted);
      text-align: center;
      max-width: 80px;
    }
    
    .step.active .step-circle {
      background: var(--neon-cyan);
      border-color: var(--neon-cyan);
      color: var(--bg-primary);
      box-shadow: 0 0 20px rgba(0, 245, 255, 0.5);
    }
    
    .step.active .step-label {
      color: var(--neon-cyan);
    }
    
    .step.completed .step-circle {
      background: var(--neon-green);
      border-color: var(--neon-green);
      color: var(--bg-primary);
    }
    
    .step.completed .step-label {
      color: var(--neon-green);
    }
    
    .status-content {
      background: rgba(0, 0, 0, 0.3);
      border-radius: var(--radius-lg);
      padding: var(--space-xl);
    }
    
    .status-header {
      display: flex;
      align-items: center;
      gap: var(--space-md);
      margin-bottom: var(--space-lg);
    }
    
    .status-badge {
      padding: 8px 16px;
      border-radius: 20px;
      font-weight: 600;
      font-size: 14px;
      text-transform: uppercase;
    }
    
    .status-badge.awaiting {
      background: rgba(255, 221, 0, 0.2);
      border: 1px solid var(--neon-yellow);
      color: var(--neon-yellow);
    }
    
    .status-badge.confirming {
      background: rgba(0, 245, 255, 0.2);
      border: 1px solid var(--neon-cyan);
      color: var(--neon-cyan);
    }
    
    .status-badge.exchanging {
      background: rgba(184, 41, 221, 0.2);
      border: 1px solid var(--neon-purple);
      color: var(--neon-purple);
    }
    
    .status-badge.sending {
      background: rgba(255, 0, 255, 0.2);
      border: 1px solid var(--neon-pink);
      color: var(--neon-pink);
    }
    
    .status-badge.complete {
      background: rgba(0, 255, 136, 0.2);
      border: 1px solid var(--neon-green);
      color: var(--neon-green);
    }
    
    .status-badge.failed,
    .status-badge.refund,
    .status-badge.action-request,
    .status-badge.request-overdue {
      background: rgba(255, 68, 68, 0.2);
      border: 1px solid #ff4444;
      color: #ff4444;
    }
    
    .status-highlight {
      color: var(--neon-cyan);
      font-weight: 600;
      background: rgba(0, 245, 255, 0.1);
      padding: 2px 8px;
      border-radius: 4px;
    }
    
    .status-message-box {
      background: rgba(0, 245, 255, 0.05);
      border-left: 3px solid var(--neon-cyan);
      padding: 16px 20px;
      margin-bottom: var(--space-lg);
      border-radius: 0 var(--radius-md) var(--radius-md) 0;
      width: 100%;
      box-sizing: border-box;
    }
    
    .status-message-box p {
      color: var(--text-secondary);
      line-height: 1.6;
      margin: 0;
    }
    
    .deposit-section {
      display: grid;
      grid-template-columns: auto 1fr;
      gap: var(--space-xl);
      align-items: start;
    }
    
    .qr-container {
      background: white;
      padding: 16px;
      border-radius: var(--radius-md);
      width: 180px;
      height: 180px;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    
    .deposit-info {
      display: flex;
      flex-direction: column;
      gap: var(--space-md);
    }
    
    .info-row {
      display: flex;
      flex-direction: column;
      gap: 4px;
    }
    
    .info-label {
      color: var(--text-muted);
      font-size: 12px;
      text-transform: uppercase;
    }
    
    .info-value {
      font-family: var(--font-mono);
      font-size: 14px;
      background: rgba(0, 0, 0, 0.5);
      padding: 12px 16px;
      border-radius: var(--radius-md);
      border: 1px solid rgba(0, 245, 255, 0.2);
      display: flex;
      justify-content: space-between;
      align-items: center;
      gap: 12px;
    }

    .address-value {
      word-break: break-all;
      overflow-wrap: break-word;
    }
    
    .copy-btn {
      background: rgba(0, 245, 255, 0.1);
      border: 1px solid var(--neon-cyan);
      color: var(--neon-cyan);
      padding: 6px 12px;
      border-radius: var(--radius-sm);
      cursor: pointer;
      font-size: 12px;
      transition: var(--transition-fast);
      white-space: nowrap;
    }
    
    .copy-btn:hover {
      background: var(--neon-cyan);
      color: var(--bg-primary);
    }
    
    .warning-box {
      background: rgba(255, 136, 0, 0.1);
      border: 1px solid var(--neon-orange);
      border-radius: var(--radius-md);
      padding: 12px 16px;
      color: var(--neon-orange);
      font-size: 13px;
    }
    
    .details-card {
      background: var(--bg-card);
      border: 1px solid rgba(0, 245, 255, 0.1);
      border-radius: var(--radius-lg);
      padding: var(--space-xl);
      margin-bottom: var(--space-xl);
    }
    
    .details-card h3 {
      margin-bottom: var(--space-lg);
      color: var(--neon-cyan);
    }
    
    .details-grid {
      display: grid;
      grid-template-columns: repeat(2, 1fr);
      gap: var(--space-lg);
    }
    
    .detail-card {
      background: rgba(0, 0, 0, 0.3);
      border-radius: var(--radius-md);
      padding: var(--space-lg);
    }
    
    .detail-card h4 {
      font-size: 12px;
      color: var(--text-muted);
      text-transform: uppercase;
      margin-bottom: var(--space-sm);
      font-weight: 500;
    }
    
    .detail-amount {
      font-family: var(--font-mono);
      font-size: 24px;
      color: var(--neon-cyan);
    }
    
    .detail-currency {
      font-size: 14px;
      color: var(--text-secondary);
    }
    
    .tx-links {
      margin-top: var(--space-lg);
      padding-top: var(--space-lg);
      border-top: 1px solid rgba(255, 255, 255, 0.1);
    }
    
    .tx-link {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 12px 0;
      border-bottom: 1px solid rgba(255, 255, 255, 0.05);
    }
    
    .tx-link:last-child {
      border-bottom: none;
    }
    
    .tx-link a {
      color: var(--neon-cyan);
      text-decoration: none;
      font-size: 13px;
    }
    
    .tx-link a:hover {
      text-decoration: underline;
    }
    
    .loading-container {
      text-align: center;
      padding: 60px;
    }
    
    .loading-spinner {
      width: 50px;
      height: 50px;
      border: 3px solid rgba(0, 245, 255, 0.2);
      border-top-color: var(--neon-cyan);
      border-radius: 50%;
      animation: spin 1s linear infinite;
      margin: 0 auto 20px;
    }
    
    @keyframes spin {
      to { transform: rotate(360deg); }
    }
    
    .error-container {
      text-align: center;
      padding: 60px;
      background: var(--bg-card);
      border-radius: var(--radius-lg);
      border: 1px solid rgba(255, 68, 68, 0.3);
    }
    
    .error-icon {
      font-size: 48px;
      margin-bottom: 20px;
    }
    
    .processing-container {
      text-align: center;
      padding: var(--space-xl);
    }
    
    .processing-container .loading-spinner {
      width: 40px;
      height: 40px;
      margin: 0 auto var(--space-md);
    }
    
    .complete-container {
      text-align: center;
      padding: var(--space-xl);
    }
    
    .complete-icon {
      font-size: 48px;
      margin-bottom: var(--space-md);
    }
    
    .failed-container {
      text-align: center;
      padding: var(--space-xl);
    }
    
    .failed-icon {
      font-size: 48px;
      margin-bottom: var(--space-md);
    }
    
    .detail-list {
      margin-top: var(--space-lg);
    }
    
    .detail-item {
      display: flex;
      justify-content: space-between;
      padding: 12px 0;
      border-bottom: 1px solid rgba(255, 255, 255, 0.05);
    }
    
    .detail-item:last-child {
      border-bottom: none;
    }
    
    .detail-item-label {
      color: var(--text-muted);
      font-size: 14px;
    }
    
    .detail-item-value {
      color: var(--text-primary);
      font-family: var(--font-mono);
      font-size: 14px;
      text-align: right;
      word-break: break-all;
      overflow-wrap: break-word;
    }
    
    @media (max-width: 768px) {
      html, body {
        padding: 80px 12px 60px 12px;
        overflow-x: hidden;
      }

      .container {
        padding: 0;
        width: 100%;
      }

      .order-container {
        width: 100%;
        max-width: 100%;
      }

      .progress-card {
        padding: var(--space-md);
      }

      .status-message-box {
        padding: 12px;
        margin-bottom: var(--space-md);
      }

      .status-content {
        padding: var(--space-md);
      }

      .steps::before {
        left: 20px;
        right: 20px;
      }
      
      .step-label {
        font-size: 9px;
        max-width: 50px;
      }
      
      .deposit-section {
        grid-template-columns: 1fr;
        justify-items: center;
      }
      
      .details-grid {
        grid-template-columns: 1fr;
      }

      .details-card {
        padding: var(--space-md);
      }
      
      .info-value {
        font-size: 11px;
        padding: 10px 12px;
        gap: 8px;
      }

      .detail-item-value {
        font-size: 12px;
        word-break: break-all;
        max-width: 60%;
      }

      .tx-link {
        flex-direction: column;
        align-items: flex-start;
        gap: 4px;
      }
    }
  </style>
</head>
<body>
  <!-- Top Logo Bar -->
  <div class="top-logo-bar">
    <a href="https://www.lightningex.io" target="_blank" class="top-logo-link">
      <div class="top-logo-icon">⚡</div>
      <span class="top-logo-text">Lightning<span class="top-logo-highlight">EX</span></span>
    </a>
  </div>

  <section class="page-header">
    <div class="container">
      <h1 class="page-title">Exchange Order</h1>
      <div class="order-id-display">
        Order ID: <span id="displayOrderId">-</span>
        <svg class="copy-icon" onclick="copyOrderId()" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
          <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
        </svg>
      </div>
    </div>
  </section>

  <section class="order-content">
    <div class="container order-container">
      <div id="loadingState" class="loading-container">
        <div class="loading-spinner"></div>
        <p style="color: var(--text-secondary);">Loading order details...</p>
      </div>

      <div id="errorState" class="error-container hidden">
        <div class="error-icon">⚠️</div>
        <h2 style="color: var(--text-primary); margin-bottom: 12px;">Order Not Found</h2>
        <p style="color: var(--text-secondary);">The order ID is invalid or has expired.</p>
        <a href="./index.html" class="btn btn-primary" style="margin-top: 24px;">Start New Exchange</a>
      </div>

      <div id="orderContent" class="hidden">
        <div class="progress-card">
          <div class="steps" id="progressSteps">
            <div class="step" data-status="Awaiting Deposit">
              <div class="step-circle">1</div>
              <div class="step-label">Awaiting Deposit</div>
            </div>
            <div class="step" data-status="Confirming Deposit">
              <div class="step-circle">2</div>
              <div class="step-label">Confirming</div>
            </div>
            <div class="step" data-status="Exchanging">
              <div class="step-circle">3</div>
              <div class="step-label">Exchanging</div>
            </div>
            <div class="step" data-status="Sending">
              <div class="step-circle">4</div>
              <div class="step-label">Sending</div>
            </div>
            <div class="step" data-status="Complete">
              <div class="step-circle">5</div>
              <div class="step-label">Complete</div>
            </div>
          </div>

          <div class="status-content" id="statusContent">
          </div>
        </div>

        <div class="details-card">
          <h3>Transaction Details</h3>
          <div class="details-grid">
            <div class="detail-card">
              <h4>You Send</h4>
              <div class="detail-amount" id="sendAmount">-</div>
              <div class="detail-currency" id="sendCurrency">-</div>
            </div>
            <div class="detail-card">
              <h4>You Receive</h4>
              <div class="detail-amount" id="receiveAmount">-</div>
              <div class="detail-currency" id="receiveCurrency">-</div>
            </div>
          </div>
          
          <div class="detail-list" id="detailList">
          </div>
          
          <div class="tx-links" id="txLinks"></div>
        </div>
        
        <div class="details-card" style="background: rgba(0, 245, 255, 0.05);">
          <h3 style="font-size: 16px; margin-bottom: 12px;">📌 Save this page</h3>
          <p style="color: var(--text-secondary); font-size: 14px; margin-bottom: 16px;">
            Bookmark this page or save the URL to track your order status. You can return anytime to check the progress.
          </p>
          <button class="btn btn-secondary" onclick="copyOrderUrl()" id="copyUrlBtn">
            Copy Order URL
          </button>
        </div>
      </div>
    </div>
  </section>

  <!-- Footer -->
  <footer style="position: fixed; bottom: 0; left: 0; right: 0; text-align: center; padding: 16px; font-size: 0.85rem; color: var(--text-muted); background: rgba(10, 10, 15, 0.8); backdrop-filter: blur(10px); border-top: 1px solid rgba(0, 245, 255, 0.1); z-index: 100;">
    Supported by <a href="https://www.lightningex.io" target="_blank" style="color: var(--neon-cyan); text-decoration: none;">LightningEX</a>
  </footer>

  <script src="./js/order.js"></script>
</body>
</html>


path: package.json
content: {
  "name": "crypto-swap",
  "version": "1.3.0",
  "description": "LightningEX Cryptocurrency Swap CLI",
  "main": "swap.js",
  "bin": {
    "crypto-swap": "./swap.js"
  },
  "scripts": {
    "start": "node swap.js"
  },
  "engines": {
    "node": ">=14.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/yoyoemily/crypto-swap"
  },
  "author": "yoyoemily",
  "homepage": "https://github.com/yoyoemily/crypto-swap#readme",
  "files": [
    "swap.js",
    "package.json",
    "SKILL.md",
    "LICENSE",
    "README.md",
    "assets/"
  ]
}

path: swap.js
content: #!/usr/bin/env node
/**
 * Crypto Exchange CLI - LightningEX API Client (Node.js)
 * Usage: crypto-swap [command] [options]
 */
const https = require('https');
const readline = require('readline');
const path = require('path');
const fs = require('fs');
const http = require('http');
const API_BASE = 'api.lightningex.io';
const useColor = !process.env.NO_COLOR && process.stdout.isTTY;
const COLORS = {
  reset: useColor ? '\x1b[0m' : '',
  bright: useColor ? '\x1b[1m' : '',
  green: useColor ? '\x1b[32m' : '',
  brightGreen: useColor ? '\x1b[1;32m' : ''
};
const STATUS_MESSAGES = {
  'Awaiting Deposit': {
    title: '⏳ Awaiting Deposit',
    lines: [
      'Your order will automatically proceed to the next step once your deposit receives its first confirmation on the blockchain.',
      'If you do not deposit the amount shown above, or your deposit does not arrive within 1 hour, for security purposes your order will not be processed automatically.'
    ]
  },
  'Confirming Deposit': {
    title: '✅ Confirming Deposit',
    lines: [
      'You successfully sent {sendAmount} {send}! Please wait while your deposit is being confirmed.',
      'Your order will automatically proceed to the next step after the deposit transaction gets 1 confirmation.',
      'Nothing more is expected from you in this step.'
    ]
  },
  'Exchanging': {
    title: '🔄 Exchanging',
    lines: [
      'Your deposit has been confirmed! We are now exchanging your {sendAmount} {send} to {receiveAmount} {receive}.',
      'This may take a few minutes, please be patient.'
    ]
  },
  'Sending': {
    title: '📤 Sending',
    lines: [
      'Your order is almost complete! We are forwarding {receiveAmount} {receive} to the following address: {receiveAddress}'
    ]
  },
  'Complete': {
    title: '🎉 Complete',
    lines: [
      'Your order is successfully completed! We forwarded {receiveAmount} {receive} to the following address: {receiveAddress}'
    ]
  },
  'Action Request': {
    title: '⚠️ Action Request',
    lines: [
      'This transaction has been detected with risks and needs to be verified before proceeding.',
      'Please contact lightningex.io for support.'
    ]
  },
  'Failed': {
    title: '❌ Failed',
    lines: [
      'The order failed because of one of the following reasons:',
      '• You didn\'t send {sendAmount} {send} on the {sendNetwork} network.',
      '• Your deposit was not confirmed within 2 hours.',
      '• For any other reason.'
    ]
  },
  'Request Overdue': {
    title: '⏰ Request Overdue',
    lines: [
      'The transaction request has expired.',
      'If you have other questions, please seek support from lightningex.io.'
    ]
  }
};
function apiRequest(endpoint, method = 'GET', data = null, silent = false) {
  return new Promise((resolve, reject) => {
    const options = {
      hostname: API_BASE,
      path: endpoint,
      method: method,
      headers: {
        'Content-Type': 'application/json'
      },
      timeout: 30000
    };
    const req = https.request(options, (res) => {
      let body = '';
      res.on('data', (chunk) => body += chunk);
      res.on('end', () => {
        try {
          const result = JSON.parse(body);
          if (result.code !== 200 && !silent) {
            console.error(`❌ API Error: ${result.msg || 'Unknown error'}`);
          }
          resolve(result);
        } catch (e) {
          if (!silent) console.error(`❌ Parse Error: ${e.message}`);
          resolve({ code: 500, msg: e.message });
        }
      });
    });
    req.on('error', (e) => {
      if (!silent) console.error(`❌ Error: ${e.message}`);
      resolve({ code: 500, msg: e.message });
    });
    req.on('timeout', () => {
      req.destroy();
      if (!silent) console.error('❌ Request timeout');
      resolve({ code: 500, msg: 'Timeout' });
    });
    if (data) {
      req.write(JSON.stringify(data));
    }
    req.end();
  });
}
function createRL() {
  return readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });
}
function prompt(rl, question) {
  return new Promise((resolve) => {
    rl.question(question, (answer) => resolve(answer.trim()));
  });
}
function printHeader(title) {
  console.log(`\n${'='.repeat(60)}`);
  console.log(`  ${title}`);
  console.log(`${'='.repeat(60)}`);
}
function printStep(stepNum, total, title) {
  console.log(`\n📍 Step ${stepNum}/${total}: ${title}`);
  console.log('-'.repeat(40));
}
async function selectFromList(items, titleKey, subtitleKey = null, promptText = 'Select') {
  if (!items || items.length === 0) {
    console.log('❌ No options available');
    return null;
  }
  console.log();
  items.forEach((item, i) => {
    const title = item[titleKey] || 'Unknown';
    if (subtitleKey && item[subtitleKey]) {
      console.log(`  ${i + 1}. ${title} (${item[subtitleKey]})`);
    } else {
      console.log(`  ${i + 1}. ${title}`);
    }
  });
  const rl = createRL();
  while (true) {
    const choice = await prompt(rl, `\n${promptText} (1-${items.length}): `);
    if (['q', 'quit', 'exit'].includes(choice.toLowerCase())) {
      rl.close();
      process.exit(0);
    }
    const idx = parseInt(choice) - 1;
    if (idx >= 0 && idx < items.length) {
      rl.close();
      return items[idx];
    }
    console.log('❌ Invalid selection');
  }
}
function formatStatusMessage(status, orderData) {
  if (!STATUS_MESSAGES[status]) {
    return `Status: ${status}`;
  }
  const msgInfo = STATUS_MESSAGES[status];
  const lines = msgInfo.lines.map(line => {
    return line.replace(/\{(\w+)\}/g, (match, key) => orderData[key] || match);
  });
  return '\n   ' + [msgInfo.title, ...lines.map(l => '• ' + l)].join('\n   ');
}
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
async function cmdWizard() {
  console.log(`
╔══════════════════════════════════════════════════════════╗
║                                                          ║
║  ⚡ LightningEX - Crypto Exchange Wizard                 ║
║                                                          ║
║  Follow the steps to complete your exchange              ║
║                                                          ║
╚══════════════════════════════════════════════════════════╝
    `);
  // Step 1: Load currencies
  printStep(1, 8, 'Loading available currencies...');
  const result = await apiRequest('/exchange/currency/list');
  if (result.code !== 200) {
    console.log('❌ Failed to load currencies');
    return;
  }
  const currencies = result.data;
  const sendCurrencies = currencies.filter(c => c.sendStatusAll);
  const receiveCurrencies = currencies.filter(c => c.receiveStatusAll);
  console.log(`✓ Loaded ${sendCurrencies.length} send currencies, ${receiveCurrencies.length} receive currencies`);
  // Step 2: Select send currency
  printStep(2, 8, 'Select currency to send');
  const sendCurrency = await selectFromList(
    sendCurrencies,
    'currency',
    'name',
    'Select currency to send'
  );
  if (!sendCurrency) return;
  console.log(`${COLORS.brightGreen}✓ You selected send currency: ${sendCurrency.currency} (${sendCurrency.name})${COLORS.reset}`);
  // Step 3: Select send network
  printStep(3, 8, `Select network for ${sendCurrency.currency} send`);
  const sendNetworks = sendCurrency.networkList?.filter(n => n.sendStatus) || [];
  const sendNetwork = await selectFromList(
    sendNetworks,
    'name',
    'network',
    `Select network for ${sendCurrency.currency} send`
  );
  if (!sendNetwork) return;
  console.log(`${COLORS.brightGreen}✓ You selected send network: ${sendNetwork.name} (${sendNetwork.network})${COLORS.reset}`);
  // Step 4: Select receive currency
  printStep(4, 8, 'Select currency to receive');
  const receiveCurrency = await selectFromList(
    receiveCurrencies,
    'currency',
    'name',
    'Select currency to receive'
  );
  if (!receiveCurrency) return;
  console.log(`${COLORS.brightGreen}✓ You selected receive currency: ${receiveCurrency.currency} (${receiveCurrency.name})${COLORS.reset}`);
  // Step 5: Select receive network
  printStep(5, 8, `Select network for ${receiveCurrency.currency} receive`);
  const receiveNetworks = receiveCurrency.networkList?.filter(n => n.receiveStatus) || [];
  if (receiveNetworks.length === 0) {
    console.log(`❌ No receive networks available for ${receiveCurrency.currency}`);
    return;
  }
  const receiveNetwork = await selectFromList(
    receiveNetworks,
    'name',
    'network',
    `Select network for ${receiveCurrency.currency} receive`
  );
  if (!receiveNetwork) return;
  console.log(`${COLORS.brightGreen}✓ You selected receive network: ${receiveNetwork.name} (${receiveNetwork.network})${COLORS.reset}`);
  // Step 6: Fetch pair info (now requires all 4 selections)
  printStep(6, 8, 'Fetching pair information...');
  console.log('\n📊 Loading exchange limits...');
  const pairResult = await apiRequest(
    `/exchange/pair/info?send=${sendCurrency.currency}` +
    `&receive=${receiveCurrency.currency}` +
    `&sendNetwork=${sendNetwork.network}` +
    `&receiveNetwork=${receiveNetwork.network}`
  );
  if (pairResult.code !== 200) {
    console.log(`❌ Failed to get pair info: ${pairResult.msg || 'Unknown error'}`);
    return;
  }
  const pairInfo = pairResult.data;
  console.log(`
    ┌─────────────────────────────────────────┐
    │  Exchange Limits                        │
    ├─────────────────────────────────────────┤
    │  Minimum: ${pairInfo.minimumAmount.padStart(20)} ${sendCurrency.currency}  │
    │  Maximum: ${pairInfo.maximumAmount.padStart(20)} ${sendCurrency.currency}  │
    │  Network Fee: ${pairInfo.networkFee.padStart(16)} ${receiveCurrency.currency}  │
    │  Processing: ${pairInfo.processingTime.padStart(17)} min  │
    └─────────────────────────────────────────┘
    `);
  // Step 7: Enter amount
  printStep(7, 8, 'Enter exchange amount');
  const rl = createRL();
  let amount;
  while (true) {
    const amountInput = await prompt(
      rl,
      `Amount to send (${pairInfo.minimumAmount} - ${pairInfo.maximumAmount} ${sendCurrency.currency}): `
    );
    const parsed = parseFloat(amountInput);
    const minAmt = parseFloat(pairInfo.minimumAmount);
    const maxAmt = parseFloat(pairInfo.maximumAmount);
    if (isNaN(parsed)) {
      console.log('❌ Please enter a valid number');
      continue;
    }
    if (parsed < minAmt) {
      console.log(`❌ Amount too small. Minimum is ${pairInfo.minimumAmount}`);
      continue;
    }
    if (parsed > maxAmt) {
      console.log(`❌ Amount too large. Maximum is ${pairInfo.maximumAmount}`);
      continue;
    }
    amount = parsed;
    console.log(`${COLORS.brightGreen}✓ You entered ${amount} ${sendCurrency.currency} to send${COLORS.reset}`);
    break;
  }
  // Get exchange rate
  console.log('\n📈 Fetching exchange rate...');
  const rateResult = await apiRequest(
    `/exchange/rate?send=${sendCurrency.currency}` +
    `&receive=${receiveCurrency.currency}` +
    `&sendNetwork=${sendNetwork.network}` +
    `&receiveNetwork=${receiveNetwork.network}` +
    `&amount=${amount}`
  );
  if (rateResult.code !== 200) {
    console.log(`❌ Failed to get rate: ${rateResult.msg || 'Unknown error'}`);
    rl.close();
    return;
  }
  const rateInfo = rateResult.data;
  console.log(`
    ┌─────────────────────────────────────────┐
    │  Exchange Rate                          │
    ├─────────────────────────────────────────┤
    │  1 ${sendCurrency.currency.padEnd(6)} = ${rateInfo.rate.padStart(16)} ${receiveCurrency.currency}  │
    │                                         │
    │  You Send:      ${rateInfo.sendAmount.padStart(20)} ${sendCurrency.currency}  │
    │  You Receive:   ${rateInfo.receiveAmount.padStart(20)} ${receiveCurrency.currency}  │
    │  Network Fee:   ${rateInfo.networkFee.padStart(20)} ${receiveCurrency.currency}  │
    └─────────────────────────────────────────┘
    `);
  // Step 8: Enter receive address
  printStep(8, 8, 'Enter receive address');
  let address;
  while (true) {
    address = await prompt(
      rl,
      `Your ${receiveCurrency.currency} (${receiveNetwork.network}) address: `
    );
    if (!address) {
      console.log('❌ Address cannot be empty');
      continue;
    }
    console.log('🔍 Validating address...');
    const valResult = await apiRequest(
      `/exchange/address/validate?currency=${receiveCurrency.currency}` +
      `&address=${address}&network=${receiveNetwork.network}`
    );
    if (valResult.code === 200 && valResult.data) {
      console.log(`${COLORS.brightGreen}✓ You entered receive address: ${address}${COLORS.reset}`);
      break;
    } else {
      console.log('❌ Invalid address. Please check and try again.');
    }
  }
  // Final confirmation
  printHeader('Order Summary');
  console.log(`
    Send:        ${amount} ${sendCurrency.currency} (${sendNetwork.name})
    Receive:     ${rateInfo.receiveAmount} ${receiveCurrency.currency} (${receiveNetwork.name})
    Rate:        1 ${sendCurrency.currency} = ${rateInfo.rate} ${receiveCurrency.currency}
    To Address:  ${address}
    `);
  const confirm = await prompt(rl, '\n✋ Confirm and place order? (yes/no): ');
  if (!['yes', 'y'].includes(confirm.toLowerCase())) {
    console.log('❌ Order cancelled');
    rl.close();
    return;
  }
  // Place order
  console.log('\n🚀 Placing order...');
  const orderData = {
    send: sendCurrency.currency,
    sendNetwork: sendNetwork.network,
    receive: receiveCurrency.currency,
    receiveNetwork: receiveNetwork.network,
    amount: String(amount),
    receiveAddress: address
  };
  const orderResult = await apiRequest('/exchange/order/place', 'POST', orderData);
  if (orderResult.code !== 200) {
    console.log(`❌ Order failed: ${orderResult.msg || 'Unknown error'}`);
    rl.close();
    return;
  }
  const orderId = orderResult.data;
  console.log('\n📋 Fetching order details...');
  const orderDetails = await apiRequest(`/exchange/order/get?id=${orderId}`);
  if (orderDetails.code !== 200) {
    console.log(`⚠️  Order created but failed to get details. Order ID: ${orderId}`);
    rl.close();
    return;
  }
  const order = orderDetails.data;
  console.log(`
╔══════════════════════════════════════════════════════════╗
║  ✅ Order Placed Successfully!                           ║
╠══════════════════════════════════════════════════════════╣
║  Order ID: ${orderId.padEnd(45)} ║
╠══════════════════════════════════════════════════════════╣
║  ⬇️  SEND YOUR FUNDS TO:                                 ║
╠══════════════════════════════════════════════════════════╣
║  Currency:   ${(order.send + ' (' + order.sendNetwork + ')').padEnd(42)} ║
║  Address:    ${order.sendAddress.padEnd(42)} ║
${order.sendTag ? `║  Tag/MEMO:  ${order.sendTag.padEnd(42)} ║` : ''}
║  Amount:    ${(order.sendAmount + ' ' + order.send).padEnd(42)} ║
╠══════════════════════════════════════════════════════════╣
║  ⚠️  IMPORTANT:                                          ║
║  • Send EXACTLY the specified amount                     ║
║  • Include the Tag/MEMO if provided                      ║
║  • Transaction will be processed after confirmation      ║
╚══════════════════════════════════════════════════════════╝
    `);
  // Auto-monitor
  console.log('\n📊 Auto-monitoring order progress...');
  console.log(`\n🔑 Order ID: ${orderId}`);
  console.log('   (Save this ID to check status later)\n');
  console.log('Press Ctrl+C to stop monitoring\n');
  const statusOrder = ['Awaiting Deposit', 'Confirming Deposit', 'Exchanging', 'Sending', 'Complete'];
  let lastStatus = null;
  rl.close();
  try {
    while (true) {
      const result = await apiRequest(`/exchange/order/get?id=${orderId}`, 'GET', null, true);
      if (result.code !== 200) {
        await sleep(15000);
        continue;
      }
      const data = result.data;
      const status = data.status;
      if (status !== lastStatus) {
        lastStatus = status;
        // Progress bar
        if (statusOrder.includes(status)) {
          const currentIdx = statusOrder.indexOf(status);
          const progress = (currentIdx / (statusOrder.length - 1)) * 100;
          const barWidth = 30;
          const filled = Math.floor(barWidth * progress / 100);
          const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
          console.log(`\r[${bar}] ${Math.round(progress)}% - ${status}`);
        } else {
          console.log(`\rStatus: ${status}`);
        }
        console.log(formatStatusMessage(status, data));
        // Additional info
        if (status === 'Awaiting Deposit') {
          console.log(`\n   🔑 Order ID: ${orderId}`);
          console.log(`   💰 Send: ${data.sendAmount} ${data.send} (${data.sendNetwork})`);
          console.log(`   📍 Deposit Address: ${data.sendAddress}`);
          if (data.sendTag) console.log(`   🏷️  Tag/MEMO: ${data.sendTag}`);
        } else if (status === 'Confirming Deposit' && data.hashIn?.length) {
          console.log(`\n   🔗 Transaction: ${data.hashIn[0]}`);
          if (data.hashInExplorer) {
            const explorerUrl = data.hashInExplorer.replace('{{txid}}', data.hashIn[0]);
            console.log(`   🔍 Explorer: ${explorerUrl}`);
          }
        } else if (status === 'Sending' && data.hashOut?.length) {
          console.log(`\n   🔗 Outgoing TX: ${data.hashOut[0]}`);
          if (data.hashOutExplorer) {
            const explorerUrl = data.hashOutExplorer.replace('{{txid}}', data.hashOut[0]);
            console.log(`   🔍 Explorer: ${explorerUrl}`);
          }
        }
        if (status === 'Complete') {
          console.log(`
╔══════════════════════════════════════════════════════════╗
║  🎉 Exchange Complete!                                   ║
╠══════════════════════════════════════════════════════════╣
║  Received: ${(data.receiveAmount + ' ' + data.receive).padEnd(42)} ║
║  To:       ${data.receiveAddress.slice(0, 40).padEnd(42)} ║
╚══════════════════════════════════════════════════════════╝
          `);
          if (data.hashOut?.length) {
            console.log(`Transaction: ${data.hashOut[0]}`);
            if (data.hashOutExplorer) {
              const explorerUrl = data.hashOutExplorer.replace('{{txid}}', data.hashOut[0]);
              console.log(`Explorer: ${explorerUrl}`);
            }
          }
          break;
        } else if (['Failed', 'Action Request', 'Request Overdue'].includes(status)) {
          const title = STATUS_MESSAGES[status]?.title || `❌ ${status}`;
          console.log(`
╔══════════════════════════════════════════════════════════╗
║  ${title.padEnd(56)} ║
╠══════════════════════════════════════════════════════════╣
║  ${(data.statusNote || 'Please contact support at lightningex.io').slice(0, 54).padEnd(54)} ║
╚══════════════════════════════════════════════════════════╝
          `);
          break;
        }
      }
      await sleep(15000);
    }
  } catch (e) {
    if (e.name === 'SIGINT') {
      console.log(`\n\n👋 Monitoring stopped.`);
      console.log(`Order ID: ${orderId}`);
      console.log(`Check status anytime: crypto-exchange status --id ${orderId}`);
    }
  }
}
async function cmdCurrencies() {
  const result = await apiRequest('/exchange/currency/list');
  console.log('\nSupported Currencies:');
  console.log('='.repeat(80));
  for (const currency of result.data) {
    const symbol = currency.currency;
    const name = currency.name;
    const sendOk = currency.sendStatusAll ? '✓' : '✗';
    const recvOk = currency.receiveStatusAll ? '✓' : '✗';
    console.log(`\n${symbol} - ${name} [Send:${sendOk} Receive:${recvOk}]`);
    for (const net of currency.networkList || []) {
      const netSend = net.sendStatus ? '✓' : '✗';
      const netRecv = net.receiveStatus ? '✓' : '✗';
      const isDefault = net.isDefault ? ' [DEFAULT]' : '';
      console.log(`  └── ${net.network} (${net.name}) Send:${netSend} Recv:${netRecv}${isDefault}`);
    }
  }
}
async function cmdPairList(args) {
  let params = `send=${args.send}&receive=${args.receive}`;
  if (args.send_network || args.sendNetwork) {
    params += `&sendNetwork=${args.send_network || args.sendNetwork}`;
  }
  const result = await apiRequest(`/exchange/pair/list?${params}`);
  if (result.code !== 200) {
    console.log(`❌ Failed to get pair list: ${result.msg || 'Unknown error'}`);
    return;
  }
  const pairs = result.data;
  console.log('\nSupported Currency Pairs');
  console.log('='.repeat(60));
  if (pairs.length === 0) {
    console.log('No pairs found for the specified criteria.');
    return;
  }
  pairs.forEach((pair, index) => {
    console.log(`  ${index + 1}. ${pair}`);
  });
  console.log(`\nTotal: ${pairs.length} pair(s)`);
}
async function cmdPair(args) {
  let params = `send=${args.send}&receive=${args.receive}`;
  const sendNetwork = args.sendNetwork || args.send_network;
  const receiveNetwork = args.receiveNetwork || args.receive_network;
  if (sendNetwork) params += `&sendNetwork=${sendNetwork}`;
  if (receiveNetwork) params += `&receiveNetwork=${receiveNetwork}`;
  const result = await apiRequest(`/exchange/pair/info?${params}`);
  const data = result.data;
  console.log(`\nPair Info: ${args.send} → ${args.receive}`);
  console.log('='.repeat(50));
  console.log(`Minimum Amount: ${data.minimumAmount}`);
  console.log(`Maximum Amount: ${data.maximumAmount}`);
  console.log(`Network Fee: ${data.networkFee}`);
  console.log(`Confirmations: ${data.confirmations}`);
  console.log(`Processing Time: ${data.processingTime} minutes`);
}
async function cmdRate(args) {
  let params = `send=${args.send}&receive=${args.receive}&amount=${args.amount}`;
  const sendNetwork = args.sendNetwork || args.send_network;
  const receiveNetwork = args.receiveNetwork || args.receive_network;
  if (sendNetwork) params += `&sendNetwork=${sendNetwork}`;
  if (receiveNetwork) params += `&receiveNetwork=${receiveNetwork}`;
  const result = await apiRequest(`/exchange/rate?${params}`);
  const data = result.data;
  console.log(`\nExchange Rate: ${args.send} → ${args.receive}`);
  console.log('='.repeat(50));
  console.log(`Rate: 1 ${args.send} = ${data.rate} ${args.receive}`);
  console.log(`You Send: ${data.sendAmount} ${args.send}`);
  console.log(`You Receive: ${data.receiveAmount} ${args.receive}`);
  console.log(`Network Fee: ${data.networkFee}`);
  console.log(`Processing Time: ${data.processingTime} minutes`);
}
async function cmdValidate(args) {
  let params = `currency=${args.currency}&address=${args.address}`;
  if (args.network) params += `&network=${args.network}`;
  const result = await apiRequest(`/exchange/address/validate?${params}`);
  const valid = result.data;
  const status = valid ? '✓ Valid' : '✗ Invalid';
  console.log(`\nAddress Validation: ${status}`);
}
async function cmdOrder(args) {
  const sendNetwork = args.sendNetwork || args.send_network;
  const receiveNetwork = args.receiveNetwork || args.receive_network;
  const receiveAddress = args.address || args.receive_address || args.receiveAddress;
  const orderData = {
    send: args.send,
    receive: args.receive,
    amount: String(args.amount),
    receiveAddress: receiveAddress
  };
  if (sendNetwork) orderData.sendNetwork = sendNetwork;
  if (receiveNetwork) orderData.receiveNetwork = receiveNetwork;
  if (!args.yes) {
    let params = `send=${args.send}&receive=${args.receive}&amount=${args.amount}`;
    if (sendNetwork) params += `&sendNetwork=${sendNetwork}`;
    if (receiveNetwork) params += `&receiveNetwork=${receiveNetwork}`;
    const rateResult = await apiRequest(`/exchange/rate?${params}`);
    const rateData = rateResult.data;
    console.log('\nOrder Preview:');
    console.log('='.repeat(50));
    console.log(`Send: ${args.amount} ${args.send}`);
    console.log(`Receive: ${rateData.receiveAmount} ${args.receive}`);
    console.log(`To Address: ${receiveAddress}`);
    console.log(`Network Fee: ${rateData.networkFee}`);
    const rl = createRL();
    const confirm = await prompt(rl, '\nConfirm order? (yes/no): ');
    rl.close();
    if (confirm.toLowerCase() !== 'yes') {
      console.log('Order cancelled.');
      return;
    }
  }
  const result = await apiRequest('/exchange/order/place', 'POST', orderData);
  const orderId = result.data;
  console.log('\n✓ Order placed successfully!');
  console.log(`Order ID: ${orderId}`);
  console.log(`Track status: crypto-exchange monitor --id ${orderId}`);
}
async function cmdStatus(args) {
  const result = await apiRequest(`/exchange/order/get?id=${args.id}`);
  const data = result.data;
  console.log(`\nOrder Status: ${args.id}`);
  console.log('='.repeat(60));
  console.log(formatStatusMessage(data.status, data));
  if (data.statusNote) {
    console.log(`\nNote: ${data.statusNote}`);
  }
  console.log(`\n${'─'.repeat(60)}`);
  console.log(`Send: ${data.sendAmount} ${data.send} (${data.sendNetwork})`);
  console.log(`Receive: ${data.receiveAmount} ${data.receive} (${data.receiveNetwork})`);
  console.log(`\nDeposit Address: ${data.sendAddress}`);
  if (data.sendTag) console.log(`Deposit Tag: ${data.sendTag}`);
  console.log(`Receive Address: ${data.receiveAddress}`);
  if (data.hashIn?.length) {
    console.log(`\nIncoming TX: ${data.hashIn.join(', ')}`);
    if (data.hashInExplorer) {
      for (const tx of data.hashIn) {
        const explorerUrl = data.hashInExplorer.replace('{{txid}}', tx);
        console.log(`  Explorer: ${explorerUrl}`);
      }
    }
  }
  if (data.hashOut?.length) {
    console.log(`\nOutgoing TX: ${data.hashOut.join(', ')}`);
    if (data.hashOutExplorer) {
      for (const tx of data.hashOut) {
        const explorerUrl = data.hashOutExplorer.replace('{{txid}}', tx);
        console.log(`  Explorer: ${explorerUrl}`);
      }
    }
  }
  const created = new Date(data.createdAt).toLocaleString();
  console.log(`\nCreated: ${created}`);
}
async function cmdMonitor(args) {
  console.log(`Monitoring order ${args.id}...`);
  console.log('Press Ctrl+C to stop\n');
  let lastStatus = null;
  try {
    while (true) {
      const result = await apiRequest(`/exchange/order/get?id=${args.id}`);
      const data = result.data;
      const status = data.status;
      if (status !== lastStatus) {
        lastStatus = status;
        console.log(`\n${'='.repeat(60)}`);
        console.log(formatStatusMessage(status, data));
        if (status === 'Awaiting Deposit') {
          console.log(`\n💰 Send: ${data.sendAmount} ${data.send} (${data.sendNetwork})`);
          console.log(`📍 Send to: ${data.sendAddress}`);
          if (data.sendTag) console.log(`🏷️  Tag/MEMO: ${data.sendTag}`);
        } else if (status === 'Confirming Deposit' && data.hashIn?.length) {
          console.log(`\n🔗 TX: ${data.hashIn[0]}`);
        } else if (status === 'Sending' && data.hashOut?.length) {
          console.log(`\n🔗 Outgoing: ${data.hashOut[0]}`);
        }
      }
      if (['Complete', 'Failed', 'Action Request', 'Request Overdue'].includes(status)) {
        console.log(`\n${'='.repeat(60)}`);
        console.log(`Final Status: ${status}`);
        if (data.hashOut?.length) {
          console.log(`Outgoing TX: ${data.hashOut.join(', ')}`);
          if (data.hashOutExplorer) {
            for (const tx of data.hashOut) {
              const explorerUrl = data.hashOutExplorer.replace('{{txid}}', tx);
              console.log(`Explorer: ${explorerUrl}`);
            }
          }
        }
        break;
      }
      await sleep(15000);
    }
  } catch (e) {
    console.log('\n\nMonitoring stopped.');
  }
}
async function cmdUI(args) {
  const port = args.port || 8080;
  // Find UI directory (swap.js is in skill root, so __dirname is the skill dir)
  const skillDir = __dirname;
  const uiDir = path.join(skillDir, 'assets', 'ui');
  if (!fs.existsSync(uiDir)) {
    console.error(`Error: UI assets not found at ${uiDir}`);
    process.exit(1);
  }
  const server = http.createServer((req, res) => {
    // Remove query string from URL
    const urlPath = req.url.split('?')[0];
    let filePath = path.join(uiDir, urlPath === '/' ? 'index.html' : urlPath);
    
    // Prevent path traversal: ensure filePath is within uiDir
    const resolvedPath = path.resolve(filePath);
    const resolvedUiDir = path.resolve(uiDir);
    if (!resolvedPath.startsWith(resolvedUiDir)) {
      res.writeHead(403);
      res.end('Forbidden');
      return;
    }
    
    const ext = path.extname(filePath);
    const contentType = {
      '.html': 'text/html',
      '.js': 'text/javascript',
      '.css': 'text/css',
      '.json': 'application/json'
    }[ext] || 'application/octet-stream';
    fs.readFile(filePath, (err, content) => {
      if (err) {
        res.writeHead(404);
        res.end('Not found');
        return;
      }
      res.writeHead(200, { 'Content-Type': contentType });
      res.end(content);
    });
  });
  server.listen(port);
  server.on('error', (err) => {
    if (err.code === 'EADDRINUSE') {
      // Port in use, try random port
      console.log(`Port ${port} in use, trying another port...`);
      server.listen(0);
    } else {
      console.error('Server error:', err.message);
      process.exit(1);
    }
  });
  // Handle the 'listening' event for the fallback port
  server.on('listening', () => {
    const actualPort = server.address().port;
    const url = `http://localhost:${actualPort}`;
    console.log(`🚀 Crypto Swap UI running at ${url}`);
    console.log('Press Ctrl+C to stop');
  });
}
function parseArgs() {
  const args = process.argv.slice(2);
  const result = { command: null, options: {} };
  if (args.length === 0) {
    result.command = 'wizard';
    return result;
  }
  result.command = args[0];
  let i = 1;
  while (i < args.length) {
    const arg = args[i];
    if (arg.startsWith('--')) {
      const key = arg.slice(2).replace(/-/g, '_');
      if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
        result.options[key] = args[i + 1];
        i += 2;
      } else {
        result.options[key] = true;
        i++;
      }
    } else if (arg.startsWith('-')) {
      const key = arg.slice(1);
      if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
        result.options[key] = args[i + 1];
        i += 2;
      } else {
        result.options[key] = true;
        i++;
      }
    } else {
      i++;
    }
  }
  return result;
}
function showHelp() {
  console.log(`
LightningEX Crypto Swap CLI
Usage: crypto-swap [command] [options]
Commands:
  wizard                    Start interactive exchange wizard (default)
  currencies                List supported currencies
  pair-list                 List supported currency-network pairs
  pair                      Get pair information
  rate                      Get exchange rate
  validate                  Validate address
  status                    Get order status
  monitor                   Monitor order until complete
  ui                        Launch web UI
Options:
  --send, -s               Currency to send
  --receive, -r            Currency to receive
  --amount, -a             Amount to send
  --receive-address, -addr Receive address
  --send-network           Send network
  --receive-network        Receive network
  --currency, -c           Currency for validation
  --id, -i                 Order ID
  --port, -p               Port for UI (default: 8080)
  --yes, -y                Skip confirmation
  --sendNetwork            Send network (for pair-list)
Examples:
  crypto-swap
  crypto-swap wizard
  crypto-swap currencies
  crypto-swap pair-list --send USDT --receive USDT
  crypto-swap pair-list --send USDT --receive USDT --send-network TRX
  crypto-swap pair --send USDT --receive USDT --send-network TRX --receive-network BSC
  crypto-swap rate --send USDT --receive USDT --send-network TRX --receive-network BSC --amount 100
  crypto-swap validate --currency USDT --network BSC --address 0x...
  crypto-swap status --id I1Y0...
  crypto-swap monitor --id I1Y0...
  crypto-swap ui --port 8080
`);
}
async function main() {
  const args = parseArgs();
  if (args.options.h || args.options.help) {
    showHelp();
    return;
  }
  try {
    switch (args.command) {
      case 'wizard':
        await cmdWizard();
        break;
      case 'currencies':
        await cmdCurrencies();
        break;
      case 'pair-list':
        if (!args.options.send || !args.options.receive) {
          console.log('Usage: crypto-swap pair-list --send <currency> --receive <currency>');
          console.log('       [--send-network <network>]');
          return;
        }
        await cmdPairList(args.options);
        break;
      case 'pair':
        if (!args.options.send || !args.options.receive) {
          console.log('Usage: crypto-swap pair --send <currency> --receive <currency>');
          console.log('       [--send-network <network>] [--receive-network <network>]');
          return;
        }
        await cmdPair(args.options);
        break;
      case 'rate':
        if (!args.options.send || !args.options.receive || !args.options.amount) {
          console.log('Usage: crypto-swap rate --send <currency> --receive <currency> --amount <amount>');
          console.log('       [--send-network <network>] [--receive-network <network>]');
          return;
        }
        await cmdRate(args.options);
        break;
      case 'validate':
        if (!args.options.currency || !args.options.address) {
          console.log('Usage: crypto-swap validate --currency <currency> --address <address>');
          console.log('       [--network <network>]');
          return;
        }
        await cmdValidate(args.options);
        break;
      case 'order':
        console.log('❌ The "order" command is disabled. Please use the interactive wizard to place orders:');
        console.log('   crypto-swap wizard');
        return;
      case 'status':
        if (!args.options.id) {
          console.log('Usage: crypto-swap status --id <order-id>');
          return;
        }
        await cmdStatus(args.options);
        break;
      case 'monitor':
        if (!args.options.id) {
          console.log('Usage: crypto-swap monitor --id <order-id>');
          return;
        }
        await cmdMonitor(args.options);
        break;
      case 'ui':
        await cmdUI(args.options);
        break;
      default:
        console.log(`Unknown command: ${args.command}`);
        showHelp();
    }
  } catch (e) {
    console.error(`Error: ${e.message}`);
    process.exit(1);
  }
}
main();

Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)