Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c255bd35c4 |
16
api/helpers.php
Normal file
16
api/helpers.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
function get_api_credentials() {
|
||||
$key_file = __DIR__ . '/../.keys/api_credentials.json';
|
||||
if (!file_exists($key_file)) {
|
||||
return null;
|
||||
}
|
||||
$creds_json = file_get_contents($key_file);
|
||||
return json_decode($creds_json, true);
|
||||
}
|
||||
|
||||
function sign_request($secret, $payload) {
|
||||
return hash_hmac('sha256', json_encode($payload), $secret);
|
||||
}
|
||||
|
||||
?>
|
||||
118
api/proxy.php
Normal file
118
api/proxy.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__ . '/helpers.php';
|
||||
|
||||
// Basic rate limiting (implement a more robust solution in production)
|
||||
// session_start();
|
||||
// $request_limit = 10; // 10 requests
|
||||
// $time_period = 60; // per minute
|
||||
// $time = time();
|
||||
|
||||
// if (!isset($_SESSION['request_count'])) {
|
||||
// $_SESSION['request_count'] = 0;
|
||||
// $_SESSION['last_request_time'] = $time;
|
||||
// }
|
||||
|
||||
// if ($time - $_SESSION['last_request_time'] > $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;
|
||||
|
||||
?>
|
||||
53
api/save_key.php
Normal file
53
api/save_key.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Basic security check: ensure it's a POST request
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405); // Method Not Allowed
|
||||
echo json_encode(['success' => 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.']);
|
||||
}
|
||||
40
api/sign.js
Normal file
40
api/sign.js
Normal file
@ -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 <privateKey> <typedDataJson>');
|
||||
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();
|
||||
225
assets/css/custom.css
Normal file
225
assets/css/custom.css
Normal file
@ -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;
|
||||
}
|
||||
192
assets/js/main.js
Normal file
192
assets/js/main.js
Normal file
@ -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 = '<div class="order-book-column">';
|
||||
html += '<div class="order-book-header"><div>Price (USD)</div><div>Size</div></div>';
|
||||
|
||||
asks.forEach(ask => {
|
||||
html += `<div class="ask-row"><div>${parseFloat(ask.px).toFixed(2)}</div><div>${parseFloat(ask.sz).toFixed(4)}</div></div>`;
|
||||
});
|
||||
|
||||
bids.forEach(bid => {
|
||||
html += `<div class="bid-row"><div>${parseFloat(bid.px).toFixed(2)}</div><div>${parseFloat(bid.sz).toFixed(4)}</div></div>`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
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 = `<div>${price}</div><div>${size}</div><div>${time}</div>`;
|
||||
tradeFeedData.prepend(tradeElement);
|
||||
});
|
||||
|
||||
while (tradeFeedData.children.length > maxTrades) {
|
||||
tradeFeedData.removeChild(tradeFeedData.lastChild);
|
||||
}
|
||||
}
|
||||
250
index.php
250
index.php
@ -1,150 +1,120 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
?>
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<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;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>26zxHyperliquid</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
|
||||
<div class="trading-terminal">
|
||||
<header class="trading-terminal-card terminal-header d-flex justify-content-between align-items-center">
|
||||
<h1 class="brand-title fs-4 mb-0">26zx<span>Hyperliquid</span></h1>
|
||||
<button id="theme-toggle">Toggle Theme</button>
|
||||
<button id="api-key-modal-btn">API Key</button>
|
||||
<button id="getUserStateButton" class="btn btn-sm btn-info ms-2">Get User State</button>
|
||||
</header>
|
||||
|
||||
<div id="user-state-container" class="trading-terminal-card" style="display: none;">
|
||||
<h5 class="mb-3">User State</h5>
|
||||
<pre id="user-state-data"></pre>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
|
||||
<div id="api-key-modal" class="modal" style="display: none;">
|
||||
<div class="modal-content">
|
||||
<span class="close-btn">×</span>
|
||||
<h2>API Key Management</h2>
|
||||
<form id="api-key-form">
|
||||
<div class="form-group">
|
||||
<label for="api-key">API Key</label>
|
||||
<input type="password" id="api-key" name="api-key" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="api-secret">API Secret</label>
|
||||
<input type="password" id="api-secret" name="api-secret" required>
|
||||
</div>
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
<div id="api-key-status"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="trading-terminal-card order-entry">
|
||||
<h5 class="mb-3">Order Entry</h5>
|
||||
<form>
|
||||
<div class="mb-2">
|
||||
<label for="coin" class="form-label">Coin</label>
|
||||
<select class="form-select form-select-sm" id="coin">
|
||||
<option selected>ETH</option>
|
||||
<option>BTC</option>
|
||||
<option>SOL</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm w-100 mb-3" role="group">
|
||||
<input type="radio" class="btn-check" name="order-type" id="limit-order" autocomplete="off" checked>
|
||||
<label class="btn btn-outline-primary" for="limit-order">Limit</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="order-type" id="market-order" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="market-order">Market</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label for="price" class="form-label">Price (USD)</label>
|
||||
<input type="number" class="form-control form-control-sm" id="price" placeholder="3000.00">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="amount" class="form-label">Amount</label>
|
||||
<input type="number" class="form-control form-control-sm" id="amount" placeholder="0.1">
|
||||
</div>
|
||||
|
||||
<div class="mb-3 leverage-slider-container">
|
||||
<label for="leverage" class="form-label">Leverage: <span class="value" id="leverage-value">10</span>x</label>
|
||||
<input type="range" class="form-range" min="1" max="100" step="1" value="10" id="leverage">
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="button" class="btn btn-sm btn-success">Buy / Long</button>
|
||||
<button type="button" class="btn btn-sm btn-danger">Sell / Short</button>
|
||||
</div>
|
||||
</form>
|
||||
</aside>
|
||||
|
||||
<main class="trading-terminal-card chart" id="tv_chart_container">
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
|
||||
<aside id="order-book-container" class="trading-terminal-card order-book">
|
||||
<h5 class="mb-3">Order Book</h5>
|
||||
<div id="order-book-data" class="order-book-data"></div>
|
||||
</aside>
|
||||
|
||||
<footer id="trade-feed-container" class="trading-terminal-card positions">
|
||||
<h5 class="mb-3">Recent Trades</h5>
|
||||
<div id="trade-feed-data" class="trade-feed-data"></div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||
<script type="text/javascript">
|
||||
new TradingView.widget(
|
||||
{
|
||||
"autosize": true,
|
||||
"symbol": "BITSTAMP:BTCUSD",
|
||||
"interval": "D",
|
||||
"timezone": "Etc/UTC",
|
||||
"theme": "dark",
|
||||
"style": "1",
|
||||
"locale": "en",
|
||||
"enable_publishing": false,
|
||||
"allow_symbol_change": true,
|
||||
"container_id": "tv_chart_container"
|
||||
}
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user