diff --git a/api/helpers.php b/api/helpers.php new file mode 100644 index 0000000..8cc8475 --- /dev/null +++ b/api/helpers.php @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/api/proxy.php b/api/proxy.php new file mode 100644 index 0000000..eafc637 --- /dev/null +++ b/api/proxy.php @@ -0,0 +1,118 @@ + $time_period) { +// $_SESSION['request_count'] = 0; +// $_SESSION['last_request_time'] = $time; +// } + +// $_SESSION['request_count']++; + +// if ($_SESSION['request_count'] > $request_limit) { +// http_response_code(429); +// echo json_encode(['error' => 'Too Many Requests']); +// exit; +// } + +$creds = get_api_credentials(); +if (!$creds) { + http_response_code(401); + echo json_encode(['error' => 'API key not configured.']); + exit; +} + +$request_body = json_decode(file_get_contents('php://input'), true); +if (!$request_body || !isset($request_body['type'])) { + http_response_code(400); + echo json_encode(['error' => 'Invalid request body.']); + exit; +} + +$api_url = 'https://api.hyperliquid.xyz/info'; + +// Prepare payload for Hyperliquid +$action = $request_body; +$nonce = (int)(microtime(true) * 1000); +$payload_to_sign = [ + 'T' => $action['type'], +]; + +// Construct signature payload based on action type +switch ($action['type']) { + case 'query': + $signature_payload = [ + 'type' => $action['type'], + 'query' => $action['query'], + 'nonce' => $nonce, + ]; + break; + case 'order': + $signature_payload = [ + 'type' => 'order', + 'orders' => $action['orders'], + 'nonce' => $nonce, + ]; + break; + default: + $signature_payload = [ + 'type' => $action['type'], + 'nonce' => $nonce, + ]; + break; +} + +$signature = hash_hmac('sha256', json_encode($signature_payload), $creds['secret']); + +$payload = [ + 'action' => $action, + 'nonce' => $nonce, + 'signature' => base64_encode($signature) +]; + + +$ch = curl_init($api_url); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +curl_setopt($ch, CURLOPT_POST, true); +curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); +curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', +]); + +$response = curl_exec($ch); +$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); +$curl_error = curl_error($ch); +curl_close($ch); + +if ($curl_error) { + http_response_code(500); + echo json_encode(['error' => 'cURL Error: ' . $curl_error]); + exit; +} + +if ($http_code >= 400) { + http_response_code($http_code); + // Forward Hyperliquid's error response if available + $error_response = json_decode($response, true); + if ($error_response) { + echo json_encode($error_response); + } else { + echo json_encode(['error' => 'Received status code: ' . $http_code]); + } + exit; +} + +echo $response; + +?> \ No newline at end of file diff --git a/api/save_key.php b/api/save_key.php new file mode 100644 index 0000000..df0becc --- /dev/null +++ b/api/save_key.php @@ -0,0 +1,53 @@ + false, 'error' => 'Invalid request method.']); + exit; +} + +$input = file_get_contents('php://input'); +$data = json_decode($input, true); + +if (json_last_error() !== JSON_ERROR_NONE || !isset($data['apiKey']) || !isset($data['apiSecret'])) { + http_response_code(400); // Bad Request + echo json_encode(['success' => false, 'error' => 'Invalid JSON or missing credentials.']); + exit; +} + +$apiKey = $data['apiKey']; +$apiSecret = $data['apiSecret']; + +// In a real application, you MUST encrypt this data. +// For this example, we'll store it in a JSON file in a protected directory. + +$storageDir = __DIR__ . '/../.keys'; // Store outside of web root if possible, or protect with .htaccess +if (!is_dir($storageDir)) { + if (!mkdir($storageDir, 0750, true)) { + http_response_code(500); // Internal Server Error + echo json_encode(['success' => false, 'error' => 'Failed to create storage directory.']); + exit; + } +} + +// Add a .htaccess file to the directory to deny direct access +if (!file_exists($storageDir . '/.htaccess')) { + file_put_contents($storageDir . '/.htaccess', 'deny from all'); +} + +$credentials = [ + 'apiKey' => $apiKey, + 'apiSecret' => $apiSecret, // Again, ENCRYPT THIS in a real app +]; + +$filePath = $storageDir . '/credentials.json'; + +if (file_put_contents($filePath, json_encode($credentials, JSON_PRETTY_PRINT))) { + chmod($filePath, 0640); // Set restrictive permissions + echo json_encode(['success' => true]); +} else { + http_response_code(500); // Internal Server Error + echo json_encode(['success' => false, 'error' => 'Failed to save credentials.']); +} diff --git a/api/sign.js b/api/sign.js new file mode 100644 index 0000000..6bd5b09 --- /dev/null +++ b/api/sign.js @@ -0,0 +1,40 @@ + +const { Wallet } = require('ethers'); + +// This is the verifying contract address for Arbitrum from the documentation +const HYPERLIQUID_EXCHANGE_ADDRESS = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; +const HYPERLIQUID_CHAIN_ID = 1337; + +async function sign() { + const [,, privateKey, typedDataJson] = process.argv; + + if (!privateKey || !typedDataJson) { + console.error('Usage: node sign.js '); + process.exit(1); + } + + try { + const wallet = new Wallet(privateKey); + const typedData = JSON.parse(typedDataJson); + + const domain = { + name: 'Hyperliquid Exchange', + version: '1', + chainId: HYPERLIQUID_CHAIN_ID, + verifyingContract: HYPERLIQUID_EXCHANGE_ADDRESS, + }; + + const signature = await wallet.signTypedData( + domain, + typedData.types, + typedData.message + ); + + console.log(signature); + } catch (error) { + console.error('Error signing payload:', error); + process.exit(1); + } +} + +sign(); diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..63270a6 --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,225 @@ + +:root { + --bg-dark: #1a1a1a; + --surface-dark: #2c2c2c; + --text-dark: #e0e0e0; + --border-dark: #444; + + --bg-light: #f0f0f0; + --surface-light: #ffffff; + --text-light: #212529; + --border-light: #dee2e6; + + --primary: #00f0ff; + --secondary: #ff00ff; + --success: #00ff7f; + --danger: #ff4d4d; + + --font-family-monospace: Menlo, Monaco, 'Courier New', monospace; +} + +#user-state-container { + grid-area: user-state; + background-color: var(--terminal-card-bg); + padding: 1rem; + border-radius: 0.25rem; +} + +#user-state-data { + font-size: 0.8rem; + white-space: pre-wrap; + word-break: break-all; +} + +/* Dark and Light theme styles */ +body { + transition: background-color 0.3s, color 0.3s; +} + +body.dark-theme { + background-color: var(--bg-dark); + color: var(--text-dark); +} + +body.light-theme { + background-color: var(--bg-light); + color: var(--text-light); +} + +.trading-terminal-card { + background-color: var(--surface-dark); + border-radius: 4px; + padding: 16px; + overflow-y: auto; +} + +body.light-theme .trading-terminal-card { + background-color: var(--surface-light); +} + +.trading-terminal { + display: grid; + grid-template-areas: + "header header header" + "user-state user-state user-state" + "order-entry chart order-book" + "positions positions positions"; + grid-template-columns: 280px 1fr 280px; + grid-template-rows: auto auto 1fr auto; + gap: 1rem; + padding: 1rem; + height: 100vh; +} + + + + +.terminal-header { grid-area: header; } +.order-entry { grid-area: order-entry; } +.chart { grid-area: chart; } +.order-book { grid-area: order-book; } +.positions { + grid-area: positions; +} + +/* Data Feed Styles */ +.order-book-data, .trade-feed-data { + font-size: 0.8rem; + height: 100%; + overflow-y: auto; + max-height: 300px; /* Or adjust as needed */ +} + +.order-book-header, .trade-row, .ask-row, .bid-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + padding: 2px 5px; +} + +.trade-feed-data .trade-row { + grid-template-columns: 1fr 1fr 1fr; +} + +.order-book-header { + font-weight: bold; + color: var(--text-color-muted); +} + +.ask-row > div:first-child { + color: var(--color-danger); +} + +.bid-row > div:first-child { + color: var(--color-success); +} + +.trade-row.buy > div:first-child { + color: var(--color-success); +} + +.trade-row.sell > div:first-child { + color: var(--color-danger); +} + +.brand-title { + color: var(--primary); + font-weight: bold; +} +.brand-title span { + color: var(--secondary); +} + +.form-label { + font-size: 0.8rem; + margin-bottom: 0.25rem; +} + +.leverage-slider-container .value { + color: var(--primary); +} + +.btn-success { + background-color: var(--success); + border-color: var(--success); + color: #000; +} +.btn-danger { + background-color: var(--danger); + border-color: var(--danger); +} + +.placeholder-text { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + font-size: 1.5rem; + color: #888; +} + +.modal { + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0,0,0,0.7); + display: flex; /* Use flexbox for centering */ + align-items: center; + justify-content: center; +} + +.modal-content { + background-color: var(--surface-dark); + padding: 24px; + border: 1px solid var(--border-dark); + border-radius: 4px; + width: 80%; + max-width: 500px; + position: relative; + animation-name: fadeIn; + animation-duration: 0.3s; +} + +@keyframes fadeIn { + from {opacity: 0} + to {opacity: 1} +} + +.close-btn { + color: #aaa; + position: absolute; + top: 10px; + right: 20px; + font-size: 28px; + font-weight: bold; + cursor: pointer; +} + +.close-btn:hover, +.close-btn:focus { + color: var(--text-dark); + text-decoration: none; + cursor: pointer; +} + +.modal .form-group { + margin-bottom: 16px; +} + +.modal label { + display: block; + margin-bottom: 8px; +} + +.modal input[type="password"] { + width: 100%; + background-color: var(--bg-dark); + color: var(--text-dark); + border: 1px solid var(--border-dark); + padding: 8px; + border-radius: 4px; +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..616466d --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,192 @@ +document.addEventListener('DOMContentLoaded', () => { + // Theme Toggle + const themeToggle = document.getElementById('theme-toggle'); + if (themeToggle) { + const currentTheme = localStorage.getItem('theme') || 'dark-theme'; + document.body.classList.add(currentTheme); + themeToggle.textContent = currentTheme === 'dark-theme' ? 'Light Mode' : 'Dark Mode'; + + themeToggle.addEventListener('click', () => { + if (document.body.classList.contains('dark-theme')) { + document.body.classList.replace('dark-theme', 'light-theme'); + localStorage.setItem('theme', 'light-theme'); + themeToggle.textContent = 'Dark Mode'; + } else { + document.body.classList.replace('light-theme', 'dark-theme'); + localStorage.setItem('theme', 'dark-theme'); + themeToggle.textContent = 'Light Mode'; + } + }); + } + + // Leverage Slider + const leverageSlider = document.getElementById('leverage'); + const leverageValue = document.getElementById('leverage-value'); + if (leverageSlider && leverageValue) { + leverageValue.textContent = leverageSlider.value; + leverageSlider.addEventListener('input', () => { + leverageValue.textContent = leverageSlider.value; + }); + } + + // API Key Modal + const apiKeyModal = document.getElementById('api-key-modal'); + const apiKeyModalBtn = document.getElementById('api-key-modal-btn'); + if (apiKeyModal && apiKeyModalBtn) { + const closeBtn = apiKeyModal.querySelector('.close-btn'); + const apiKeyForm = document.getElementById('api-key-form'); + const apiKeyStatus = document.getElementById('api-key-status'); + + apiKeyModalBtn.addEventListener('click', () => { + apiKeyModal.style.display = 'flex'; + }); + + if (closeBtn) { + closeBtn.addEventListener('click', () => { + apiKeyModal.style.display = 'none'; + }); + } + + window.addEventListener('click', (event) => { + if (event.target == apiKeyModal) { + apiKeyModal.style.display = 'none'; + } + }); + + if (apiKeyForm) { + apiKeyForm.addEventListener('submit', (event) => { + event.preventDefault(); + const apiKey = document.getElementById('api-key').value; + const apiSecret = document.getElementById('api-secret').value; + + fetch('api/save_key.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ apiKey, apiSecret }), + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + apiKeyStatus.textContent = 'API Key saved successfully!'; + apiKeyStatus.style.color = 'var(--success)'; + setTimeout(() => { apiKeyModal.style.display = 'none'; }, 2000); + } else { + apiKeyStatus.textContent = `Error: ${data.error}`; + apiKeyStatus.style.color = 'var(--danger)'; + } + }) + .catch(error => { + apiKeyStatus.textContent = `Error: ${error.message}`; + apiKeyStatus.style.color = 'var(--danger)'; + }); + }); + } + } + + // User State Button + const getUserStateButton = document.getElementById('getUserStateButton'); + const userStateContainer = document.getElementById('user-state-container'); + const userStateData = document.getElementById('user-state-data'); + if (getUserStateButton && userStateContainer && userStateData) { + getUserStateButton.addEventListener('click', () => { + fetch('api/proxy.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ type: 'clearinghouseState' }), + }) + .then(response => { + if (response.status === 401) { + alert('API Key not configured. Please add it via the API Key button.'); + return null; + } + return response.json(); + }) + .then(data => { + if (data) { + userStateData.textContent = JSON.stringify(data, null, 2); + userStateContainer.style.display = 'block'; + } + }) + .catch(error => { + console.error('Error fetching user state:', error); + alert('An error occurred while fetching user state.'); + }); + }); + } + + // WebSocket Connection + connectWebSocket(); +}); + +function connectWebSocket() { + const ws = new WebSocket('wss://api.hyperliquid.xyz/ws'); + + ws.onopen = () => { + console.log('Connected to Hyperliquid WebSocket API'); + ws.send(JSON.stringify({ "method": "subscribe", "subscription": { "type": "l2Book", "coin": "ETH" } })); + ws.send(JSON.stringify({ "method": "subscribe", "subscription": { "type": "trades", "coin": "ETH" } })); + }; + + ws.onmessage = (event) => { + const response = JSON.parse(event.data); + if (response.channel === 'l2Book' && response.data) { + updateOrderBook(response.data); + } + if (response.channel === 'trades' && response.data) { + updateTradeFeed(response.data); + } + }; + + ws.onerror = (error) => { + console.error('WebSocket Error:', error); + }; + + ws.onclose = () => { + console.log('WebSocket connection closed. Reconnecting...'); + setTimeout(connectWebSocket, 5000); + }; +} + +function updateOrderBook(data) { + const orderBookData = document.getElementById('order-book-data'); + if (!orderBookData) return; + + const asks = data.levels[1].slice(0, 8).reverse(); + const bids = data.levels[0].slice(0, 8); + + let html = '
'; + html += '
Price (USD)
Size
'; + + asks.forEach(ask => { + html += `
${parseFloat(ask.px).toFixed(2)}
${parseFloat(ask.sz).toFixed(4)}
`; + }); + + bids.forEach(bid => { + html += `
${parseFloat(bid.px).toFixed(2)}
${parseFloat(bid.sz).toFixed(4)}
`; + }); + + html += '
'; + orderBookData.innerHTML = html; +} + +function updateTradeFeed(trades) { + const tradeFeedData = document.getElementById('trade-feed-data'); + if (!tradeFeedData) return; + + const maxTrades = 15; + trades.forEach(trade => { + const tradeElement = document.createElement('div'); + tradeElement.className = `trade-row ${trade.side === 'B' ? 'buy' : 'sell'}`; + + const time = new Date(trade.time).toLocaleTimeString(); + const price = parseFloat(trade.px).toFixed(2); + const size = parseFloat(trade.sz).toFixed(4); + + tradeElement.innerHTML = `
${price}
${size}
${time}
`; + tradeFeedData.prepend(tradeElement); + }); + + while (tradeFeedData.children.length > maxTrades) { + tradeFeedData.removeChild(tradeFeedData.lastChild); + } +} \ No newline at end of file diff --git a/index.php b/index.php index 7205f3d..259913d 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,120 @@ - - + - - - New Style - - - - - - - - - - - - - - - - - - - + + + 26zxHyperliquid + + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

+ +
+
+

26zxHyperliquid

+ + + +
+ + + + + + + +
+
+ + + +
+
Recent Trades
+
+
+
-
-
- Page updated: (UTC) -
+ + + - + \ No newline at end of file