HyperVapor Documentation
Everything you need to read pool data, watch on-chain events, and integrate with the HyperVapor protocol — REST API, smart contracts, and webhooks, on one page.
Introduction
HyperVapor is a multi-pool prediction protocol on Monad mainnet (chainId 143). Each pool tracks one price symbol and lets users take a long or short position against other LPs in the same pool. Rebalances move 1% of the losing side's assets to the winner whenever the price ticks past a threshold or the interval elapses.
This page covers three integration surfaces:
Read pools, positions, history. No auth.
Factory + 10 pools. Addresses & ABIs.
Real-time event delivery via QuickNode.
Quickstart
Hit the API directly — no key, no signup.
# 1. List every pool
curl https://xxm.xyz/api/v1/pools | jq '.data[].label'
# 2. Latest 10 rebalances on the MON-Native-10m pool
POOL=0xa9791f163002f6a63cc07aff8142095a9a858f6458a00cae00f7acad6482059b
curl "https://xxm.xyz/api/v1/pools/$POOL/rebalances?limit=10" | jq
# 3. A user's full position history
USER=0x0000000000000000000000000000000000000000
curl "https://xxm.xyz/api/v1/user/history/$USER?limit=20" | jqREST API
Public read-only endpoints under https://xxm.xyz/api/v1/. Every endpoint with a Try it button runs live in your browser.
Conventions
- Response shape. Success returns
{ success: true, data, ...extra }. Errors return{ error: string }with a non-2xx status. - Pool filter.
?pool=accepts a 32-byte pool id or a 20-byte pool address. Omit to query across all pools. - Pagination.
limit(default 50, max 1000) andoffset. - Time format. ISO-8601 UTC.
from/tofilters accept the same. - Big numbers. All on-chain amounts are decimal strings, never JSON numbers. Wrap with
BigInt()on the client.
System
Health and process metadata.
/api/v1/healthApp liveness probe.
Pools
Registry of pool contracts deployed by the v2.1 factory and their metadata / config events.
/api/v1/poolsList every pool registered in the indexer.
/api/v1/pools/:poolIdFetch a single pool by its bytes32 id.
| Name | In | Type | Description |
|---|---|---|---|
| poolId* | path | 0x… (bytes32 pool id) | Hex pool id from the factory's `MarketCreated` event. |
/api/v1/pools/:poolId/rebalancesRebalance events for a single pool, newest first.
| Name | In | Type | Description |
|---|---|---|---|
| poolId* | path | 0x… (bytes32 pool id) | Hex pool id from the factory's `MarketCreated` event. |
| limit | query | integer (1–1000, default 50) | Maximum number of items to return. |
| offset | query | integer (default 0) | Skip the first N matching items (use with limit to paginate). |
/api/v1/pools/:poolId/snapshotsPeriodic pool_snapshots rows for a single pool (oldest → newest by default).
Each row captures the on-chain TVL of a pool: long_total_assets, short_total_assets, long_total_shares, short_total_shares, total_assets, plus the block number / timestamp at which the read happened. The snapshotter inside the indexer process writes a row every POOL_SNAPSHOT_INTERVAL_SEC seconds (default 300s) and de-dupes against the previous row, so idle pools only get a new snapshot when their state actually changes.
| Name | In | Type | Description |
|---|---|---|---|
| poolId* | path | 0x… (bytes32 pool id) | Hex pool id from the factory's `MarketCreated` event. |
| limit | query | integer (1–500, default 100) | Maximum number of items to return. |
| offset | query | integer (default 0) | Skip the first N matching items (use with limit to paginate). |
| from | query | ISO timestamp | Inclusive lower bound on the event timestamp. e.g. 2026-01-01T00:00:00Z |
| to | query | ISO timestamp | Inclusive upper bound on the event timestamp. e.g. 2026-12-31T23:59:59Z |
| order | query | "asc" | "desc" (default "asc") | Time ordering of returned rows. |
/api/v1/pools/:poolId/config-eventsConfig-change events for a pool (e.g. fees, listing toggle).
| Name | In | Type | Description |
|---|---|---|---|
| poolId* | path | 0x… (bytes32 pool id) | Hex pool id from the factory's `MarketCreated` event. |
| limit | query | integer (1–1000, default 50) | Maximum number of items to return. |
| offset | query | integer (default 0) | Skip the first N matching items (use with limit to paginate). |
Events (per user)
User-scoped activity. All endpoints accept `?pool=<id|address>` to scope results to a single market.
/api/v1/deposits/:userDeposits made by a user.
| Name | In | Type | Description |
|---|---|---|---|
| user* | path | 0x… (20-byte EVM address) | User address (case-insensitive; lower-cased server-side). |
| pool | query | 0x… (bytes32 pool id or 20-byte pool address) | Filter results to a single pool. Accepts either the bytes32 pool id or the 20-byte pool contract address. |
| limit | query | integer (1–1000, default 50) | Maximum number of items to return. |
| offset | query | integer (default 0) | Skip the first N matching items (use with limit to paginate). |
| from | query | ISO timestamp | Inclusive lower bound on the event timestamp. e.g. 2026-01-01T00:00:00Z |
| to | query | ISO timestamp | Inclusive upper bound on the event timestamp. e.g. 2026-12-31T23:59:59Z |
/api/v1/withdrawals/:userWithdrawals made by a user.
| Name | In | Type | Description |
|---|---|---|---|
| user* | path | 0x… (20-byte EVM address) | User address (case-insensitive; lower-cased server-side). |
| pool | query | 0x… (bytes32 pool id or 20-byte pool address) | Filter results to a single pool. Accepts either the bytes32 pool id or the 20-byte pool contract address. |
| limit | query | integer (1–1000, default 50) | Maximum number of items to return. |
| offset | query | integer (default 0) | Skip the first N matching items (use with limit to paginate). |
| from | query | ISO timestamp | Inclusive lower bound on the event timestamp. e.g. 2026-01-01T00:00:00Z |
| to | query | ISO timestamp | Inclusive upper bound on the event timestamp. e.g. 2026-12-31T23:59:59Z |
/api/v1/switches/:userLong↔short switches made by a user.
| Name | In | Type | Description |
|---|---|---|---|
| user* | path | 0x… (20-byte EVM address) | User address (case-insensitive; lower-cased server-side). |
| pool | query | 0x… (bytes32 pool id or 20-byte pool address) | Filter results to a single pool. Accepts either the bytes32 pool id or the 20-byte pool contract address. |
| limit | query | integer (1–1000, default 50) | Maximum number of items to return. |
| offset | query | integer (default 0) | Skip the first N matching items (use with limit to paginate). |
| from | query | ISO timestamp | Inclusive lower bound on the event timestamp. e.g. 2026-01-01T00:00:00Z |
| to | query | ISO timestamp | Inclusive upper bound on the event timestamp. e.g. 2026-12-31T23:59:59Z |
/api/v1/deposit-records/:userConvenience: deposit count + first 50 records for a user (and optionally a pool).
| Name | In | Type | Description |
|---|---|---|---|
| user* | path | 0x… (20-byte EVM address) | User address (case-insensitive; lower-cased server-side). |
| pool | query | 0x… (bytes32 pool id or 20-byte pool address) | Filter results to a single pool. Accepts either the bytes32 pool id or the 20-byte pool contract address. |
User aggregations
Combined / derived per-user views.
/api/v1/user/history/:userUnified, sorted feed of deposits / withdrawals / switches for a user.
| Name | In | Type | Description |
|---|---|---|---|
| user* | path | 0x… (20-byte EVM address) | User address (case-insensitive; lower-cased server-side). |
| pool | query | 0x… (bytes32 pool id or 20-byte pool address) | Filter results to a single pool. Accepts either the bytes32 pool id or the 20-byte pool contract address. |
| limit | query | integer (1–1000, default 50) | Maximum number of items to return. |
/api/v1/user/stats/:userPer-pool + overall totals (deposits, withdrawals, fees) for a user.
| Name | In | Type | Description |
|---|---|---|---|
| user* | path | 0x… (20-byte EVM address) | User address (case-insensitive; lower-cased server-side). |
/api/v1/user/pnl-history/:userPeriodic PnL snapshots for a user, optionally per pool.
| Name | In | Type | Description |
|---|---|---|---|
| user* | path | 0x… (20-byte EVM address) | User address (case-insensitive; lower-cased server-side). |
| pool | query | 0x… (bytes32 pool id or 20-byte pool address) | Filter results to a single pool. Accepts either the bytes32 pool id or the 20-byte pool contract address. |
| limit | query | integer (default 100) | Maximum number of items to return. |
/api/v1/user/principal/:userPer-pool principal balance derived from deposit/withdraw history.
| Name | In | Type | Description |
|---|---|---|---|
| user* | path | 0x… (20-byte EVM address) | User address (case-insensitive; lower-cased server-side). |
| pool | query | 0x… (bytes32 pool id or 20-byte pool address) | Filter results to a single pool. Accepts either the bytes32 pool id or the 20-byte pool contract address. |
Platform-wide events
Cross-pool feeds. All endpoints accept `?pool=<id|address>` for filtering.
/api/v1/rebalancesAll rebalances across every pool, newest first.
| Name | In | Type | Description |
|---|---|---|---|
| pool | query | 0x… (bytes32 pool id or 20-byte pool address) | Filter results to a single pool. Accepts either the bytes32 pool id or the 20-byte pool contract address. |
| limit | query | integer (1–1000, default 50) | Maximum number of items to return. |
| offset | query | integer (default 0) | Skip the first N matching items (use with limit to paginate). |
| from | query | ISO timestamp | Inclusive lower bound on the event timestamp. e.g. 2026-01-01T00:00:00Z |
| to | query | ISO timestamp | Inclusive upper bound on the event timestamp. e.g. 2026-12-31T23:59:59Z |
/api/v1/pool/historyAll pool_snapshots rows (across pools), oldest → newest by default.
| Name | In | Type | Description |
|---|---|---|---|
| pool | query | 0x… (bytes32 pool id or 20-byte pool address) | Filter results to a single pool. Accepts either the bytes32 pool id or the 20-byte pool contract address. |
| limit | query | integer (1–500, default 100) | Maximum number of items to return. |
| offset | query | integer (default 0) | Skip the first N matching items (use with limit to paginate). |
| from | query | ISO timestamp | Inclusive lower bound on the event timestamp. e.g. 2026-01-01T00:00:00Z |
| to | query | ISO timestamp | Inclusive upper bound on the event timestamp. e.g. 2026-12-31T23:59:59Z |
| order | query | "asc" | "desc" (default "asc") | Time ordering of returned rows. |
/api/v1/pool/snapshots/latestLatest pool_snapshot for every pool (one row per pool).
Convenience endpoint backing the dashboard summary cards. Optionally filter to a single pool with `?pool=<id|address>`. Pools that haven't been snapshotted yet are omitted.
| Name | In | Type | Description |
|---|---|---|---|
| pool | query | 0x… (bytes32 pool id or 20-byte pool address) | Filter results to a single pool. Accepts either the bytes32 pool id or the 20-byte pool contract address. |
/api/v1/platform/eventsCombined factory + pool-config events stream for ops dashboards.
| Name | In | Type | Description |
|---|---|---|---|
| pool | query | 0x… (bytes32 pool id or 20-byte pool address) | Filter results to a single pool. Accepts either the bytes32 pool id or the 20-byte pool contract address. |
| limit | query | integer (1–1000, default 50) | Maximum number of items to return. |
| offset | query | integer (default 0) | Skip the first N matching items (use with limit to paginate). |
/api/v1/factory/eventsRaw events emitted by the v2.1 factory contract.
| Name | In | Type | Description |
|---|---|---|---|
| limit | query | integer (1–1000, default 50) | Maximum number of items to return. |
| offset | query | integer (default 0) | Skip the first N matching items (use with limit to paginate). |
Admin (internal)
Reverse-proxied to the indexer process. The /webhook endpoint is the QuickNode target. The refresh endpoint requires the X-Admin-Token header.
/api/admin/indexer/healthIndexer process health (proxied from 127.0.0.1:3001).
/api/admin/indexer/statsIndexer stats: pool counts, byPool breakdown, last event.
/api/admin/indexer/refresh-registryRe-hydrate the in-memory pool registry from the factory contract via RPC.
| Name | In | Type | Description |
|---|---|---|---|
| X-Admin-Token* | header | string | Must match the indexer's ADMIN_TOKEN env var. |
/api/admin/indexer/snapshotsSnapshotter schedule + last-run summary (pool TVL + user PnL).
/api/admin/indexer/snapshots/runForce a one-shot snapshot run. Body: { kind: "pool" | "pnl" | "all" } (default "pool").
| Name | In | Type | Description |
|---|---|---|---|
| X-Admin-Token | header | string | Required only if ADMIN_TOKEN is set on the indexer process. |
/webhookQuickNode stream target. Signature-verified by the indexer; do not call manually.
Smart Contracts
One factory, one price tracker, and ten pools. All addresses are immutable — bookmark them or fetch them from the factory at runtime.
Network
- Chain — Monad Mainnet (
chainId 143) - RPC —
https://rpc.monad.xyz - Explorer —
https://monad.xyz/address/<ADDRESS>
Core addresses
Live pools (10)
Two intervals (10m and 1h) per asset — pick the cadence that fits your strategy.
| Label | Type | Symbol | Collateral | Interval | Address |
|---|---|---|---|---|---|
| MON-Native-10m | Native | MON | NATIVE | 10m | |
| MON-Native-1h | Native | MON | NATIVE | 1h | |
| BTC-USDC-10m | Market | BTC | USDC | 10m | |
| BTC-USDC-1h | Market | BTC | USDC | 1h | |
| ETH-USDC-10m | Market | ETH | USDC | 10m | |
| ETH-USDC-1h | Market | ETH | USDC | 1h | |
| SOL-USDC-10m | Market | SOL | USDC | 10m | |
| SOL-USDC-1h | Market | SOL | USDC | 1h | |
| HYPE-USDC-10m | Market | HYPE | USDC | 10m | |
| HYPE-USDC-1h | Market | HYPE | USDC | 1h |
Pool actions
Every pool exposes the same four user actions. Native pools take msg.value; ERC20 pools require an approve first.
// Take a position. isLong = true → long, false → short.
function deposit(bool isLong, uint256 amount) external; // Market
function deposit(bool isLong) external payable; // Native
// Exit both sides at once. Always available, even if paused.
function withdraw() external;
// Move your entire balance from one side to the other.
function switchPosition(bool fromLongToShort) external;
// Permissionless settlement. Anyone can call when a rebalance is due.
function rebalance() external;
// Read your position.
function getUserPosition(address user) external view returns (
uint256 longShares, uint256 shortShares,
uint256 longPrincipal, uint256 shortPrincipal
);
function previewWithdraw(address user) external view returns (
uint256 grossLong, uint256 grossShort,
uint256 netLong, uint256 netShort,
uint256 totalFee, uint256 totalProfitTax
);Discover pools on-chain
Don't hardcode pool addresses — read them from the factory so new markets show up automatically.
import { createPublicClient, http } from "viem";
const client = createPublicClient({
chain: { id: 143, name: "Monad", nativeCurrency: { name: "MON", symbol: "MON", decimals: 18 }, rpcUrls: { default: { http: ["https://rpc.monad.xyz"] } } },
transport: http(),
});
const FACTORY_ABI = [
{ type: "function", name: "getAllMarketIds", inputs: [], outputs: [{ type: "bytes32[]" }], stateMutability: "view" },
{ type: "function", name: "getMarketInfo", inputs: [{ name: "id", type: "bytes32" }],
outputs: [{ components: [
{ name: "market", type: "address" },
{ name: "marketType", type: "uint8" }, // 0 = ERC20, 1 = NATIVE
{ name: "priceSymbol", type: "string" },
{ name: "collateralToken", type: "address" },
{ name: "priceProvider", type: "address" },
{ name: "marketOwner", type: "address" },
{ name: "rebalanceInterval", type: "uint256" }, // 600 (10m) or 3600 (1h)
{ name: "createdAt", type: "uint256" },
{ name: "listed", type: "bool" },
{ name: "exists", type: "bool" },
], type: "tuple" }],
stateMutability: "view" },
] as const;
const ids = await client.readContract({
address: "0x63072Cdc78874DaDA5A6053f07E636eF3aec8c57",
abi: FACTORY_ABI,
functionName: "getAllMarketIds",
});
const pools = await Promise.all(ids.map((id) =>
client.readContract({ address: "0x63072Cdc78874DaDA5A6053f07E636eF3aec8c57", abi: FACTORY_ABI, functionName: "getMarketInfo", args: [id] })
));Events
The six event topics you need to filter for if you're building an indexer, subgraph, or alerting bot. topic0 is the keccak256 of the canonical signature.
Topic0 reference
| Scope | Name | Signature | Topic0 |
|---|---|---|---|
Pool | Deposited | Deposited(address,bool,uint256,uint256,uint256,uint256) | |
Pool | Withdrawn | Withdrawn(address,uint256,uint256,uint256,uint256,uint256,uint256,uint256) | |
Pool | Switched | Switched(address,bool,uint256,uint256,uint256,uint256,uint256,uint256) | |
Pool | Rebalanced | Rebalanced(uint256,bool,uint256) | |
Factory | MarketCreated | MarketCreated(bytes32,address,uint8,string,address,address,address,address,uint256) | |
Factory | MarketStatusUpdated | MarketStatusUpdated(bytes32,bool) |
Webhooks
We push every relevant on-chain log to your endpoint via QuickNode Streams with HMAC-signed payloads. Set it up once and you have an independent mirror of the protocol state.
How it works
- QuickNode subscribes to Block w/ Receipts on Monad mainnet.
- Each block runs through the filter (below) which keeps only the 6 known event topics.
- The payload is
POST-ed with HMAC-SHA256 signature to your URL. - You verify the signature, decode the events, and write to your store.
QuickNode setup
- Create a Stream → Network
Monad Mainnet, DatasetBlock w/ Receipts, Start blockLatest. - Paste the filter below and use Test Filter to confirm logs come through.
- Destination →
POST https://your-endpoint/webhook. - Security → enable HMAC, save the secret as
QUICKNODE_WEBHOOK_SECRETin your env. - Retries → exponential backoff. Decode idempotently by
(txHash, logIndex).
// QuickNode Stream Filter — HyperVapor
// Forward every log whose topic0 matches a known v2.1 event.
const TOPICS = new Set([
// Pool
"0xc2cf288cd8c138e724d1e17ce848afd04e0d32e56883d38635d5a81523dbce02", // Deposited
"0x06c514d6b519b3f249f6f8b7fc10573de80de5388bfc514bfb432616ee930be7", // Withdrawn
"0x0945716462655c1f308382e972112dec530f9595b0ee214386dbb14adf618dea", // Switched
"0x5ef5c3de2451b8e81ef437df0ed11d9c3e7fd1485531ad5dd1874f64df4d7d70", // Rebalanced
// Factory
"0xd782b4fbb126b84aa938ca9ef083461d682456398f5d1b1f4112c40a6376ee00", // MarketCreated
"0x1fb3747e10a9f9d9b8368bbf1e725aa452f6c4eb3527168288f16899f453bef4", // MarketStatusUpdated
]);
function main(streamData) {
const blocks = [];
for (const item of streamData.data ?? []) {
const block = item.block ?? item;
const logs = (item.receipts ?? block.receipts ?? [])
.flatMap((r) => r.logs ?? [])
.filter((l) => l.topics?.[0] && TOPICS.has(l.topics[0].toLowerCase()));
if (logs.length) blocks.push({ number: block.number, hash: block.hash, timestamp: block.timestamp, logs });
}
return blocks.length ? { data: blocks, metadata: streamData.metadata } : null;
}Payload shape
{
"data": [
{
"number": "0x...",
"hash": "0x...",
"timestamp": "0x...", // hex unix seconds
"logs": [
{
"address": "0x5fa1585458ca8fc1bb2b9b1528d488cdcbb8643c",
"topics": ["0xc2cf288c...", "0x000...abc"],
"data": "0x...",
"blockNumber": "0x...",
"transactionHash": "0x...",
"transactionIndex": "0x...",
"blockHash": "0x...",
"logIndex": "0x...",
"removed": false
}
]
}
],
"metadata": { "...": "..." }
}Verify the signature
QuickNode sets X-QN-Signature to HMAC-SHA256(secret, rawBody), hex-encoded. Verify before trusting any payload.
import { createHmac, timingSafeEqual } from "crypto";
import express from "express";
const app = express();
// IMPORTANT: capture the raw body BEFORE JSON.parse runs.
app.use(express.json({
limit: "10mb",
verify: (req, _res, buf) => { (req as any).rawBody = buf.toString(); },
}));
app.post("/webhook", (req, res) => {
const sig = (req.header("x-qn-signature") ?? "").replace(/^sha256=/, "");
const expected = createHmac("sha256", process.env.QUICKNODE_WEBHOOK_SECRET!)
.update((req as any).rawBody)
.digest("hex");
const a = Buffer.from(sig);
const b = Buffer.from(expected);
if (a.length !== b.length || !timingSafeEqual(a, b)) {
return res.status(401).json({ error: "Invalid signature" });
}
// req.body is safe to use now
res.json({ ok: true });
});Versioning
- REST API — versioned via URL prefix (
/api/v1/). A breaking change introduces/api/v2/and the old version stays online for at least 90 days. - Smart contracts — currently
v2.1. Pool and factory addresses are immutable; a new release means a new factory and a new set of pools. - Event signatures — frozen for a given contract version. The topic0 hashes above are the canonical reference.