Vite (plain JavaScript) quickstart
Build a page that displays a public BTC REST price and live WebSocket updates from @siebly/hyperliquid-api. No API key is needed. Prices are BTC mid prices.
Create the project
Use Node.js with npm. Download the complete Vite starter, or create an empty directory and copy all files below.
mkdir hyperliquid-vite-quickstart
cd hyperliquid-vite-quickstartCopy the files below, or extract the downloaded archive into this directory (adjust the download path if needed):
tar -xzf ~/Downloads/vite.tar.gzInstall the dependencies after saving or extracting the files:
npm installThe SDK installs from latest. Vite 5.4.21 is the tested build tool. No additional SDK polyfill configuration is needed for this recipe. See Runtime compatibility for the recorded browser execution results.
Project files
package.json
{
"name": "hyperliquid-vite-quickstart",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "vite build",
"preview": "vite preview --host 127.0.0.1"
},
"dependencies": {
"@siebly/hyperliquid-api": "latest"
},
"devDependencies": {
"vite": "5.4.21"
}
}market.js
export const endpoints = {"rest": "https://api.hyperliquid.xyz/info", "websocket": "wss://api.hyperliquid.xyz/ws"};
export function client(sdk) { return new sdk.RestClient({}, {timeout:15000}); }
export async function read(client) { return (await client.getAllMids()).BTC; }
export function stream(sdk, logger) { return new sdk.WebsocketClient({}, logger); }
export const event = 'message';
export function value(message) { return message.channel === 'allMids' ? message.data?.mids?.BTC : undefined; }
export async function subscribe(ws) { await ws.subscribe({ type: 'allMids' }); }price.js
import * as sdk from '@siebly/hyperliquid-api';
import * as market from './market.js';
function positivePrice(value) {
if (!Number.isFinite(Number(value)) || Number(value) <= 0) {
throw new Error('The exchange did not return a positive BTC price.');
}
return String(value);
}
export async function readPrice() {
return positivePrice(await market.read(market.client(sdk)));
}
// Returns cleanup without waiting for subscription acknowledgement.
export function watchPrice(onPrice, onError) {
const ws = market.stream(sdk);
let stopped = false;
const receive = message => {
const value = market.value(message);
if (!stopped && value !== undefined) {
clearTimeout(deadline);
try { onPrice(positivePrice(value)); } catch (error) { fail(error); }
}
};
const fail = error => { if (!stopped) onError(error); };
const deadline = setTimeout(() => fail(new Error('No BTC update within 30 seconds. Check network access.')), 30000);
ws.on(market.event, receive);
ws.on('exception', fail);
// React may clean up an effect before its queued subscription starts.
Promise.resolve().then(async () => {
if (stopped) return;
try {
await market.subscribe(ws);
} finally {
// A connection may finish opening after cleanup was requested.
if (stopped) ws.closeAll();
}
}).catch(fail);
return () => {
if (stopped) return;
stopped = true;
clearTimeout(deadline);
ws.closeAll();
ws.removeListener(market.event, receive);
ws.removeListener('exception', fail);
};
}index.html
<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><title>BTC price</title></head>
<body>
<h1>BTC price</h1>
<p>REST: <output id="rest">Loading</output></p>
<p>WebSocket: <output id="live">Connecting</output></p>
<p id="error" role="alert"></p>
<button id="stop" type="button">Stop stream</button>
<script type="module" src="/main.js"></script>
</body>
</html>main.js
import { readPrice, watchPrice } from './price.js';
const showError = error => { document.querySelector('#error').textContent = error.message || String(error); };
readPrice().then(price => { document.querySelector('#rest').textContent = price; }).catch(showError);
const stop = watchPrice(price => { document.querySelector('#live').textContent = price; }, showError);
document.querySelector('#stop').addEventListener('click', () => {
stop();
document.querySelector('#live').textContent = 'Stopped';
});
window.addEventListener('pagehide', stop, { once: true });
if (import.meta.hot) import.meta.hot.dispose(stop);Run
npm run devOpen the URL printed by Vite, normally http://localhost:5173/. The page shows a REST price and a changing WebSocket price. Click Stop stream to close the client. Navigation and Vite hot replacement also close the connection.
To check the production bundle:
npm run build
npm run previewOpen the preview URL printed by Vite. Both price fields should contain positive numbers, such as 65000.5. REST and streaming prices may differ.
Troubleshooting
- If an SDK import fails, run
npm installin the starter directory and check that the installed package matches the import inprice.js. - Run the page through Vite, rather than opening
index.htmldirectly. Check the browser network panel for blocked REST or WSS requests. The exchange must permit your origin and region; a successful bundle alone does not prove network access. - Keep private keys and API secrets on a server. This page uses public market data. See WebSocket subscriptions for subscription details.