typescript
import { z } from "zod";
import { wallet, WalletFullContext } from "opentool/wallet";
import { store } from "opentool/store";
import {
buildHyperliquidMarketIdentity,
cancelHyperliquidOrders,
cancelHyperliquidOrdersByCloid,
isHyperliquidSpotSymbol,
normalizeHyperliquidBaseSymbol,
parseSpotPairSymbol,
resolveHyperliquidChainConfig,
resolveHyperliquidOrderSymbol,
resolveHyperliquidPair,
} from "opentool/adapters/hyperliquid";
export const profile = {
description:
"Cancel a Hyperliquid order by oid or client order id (cloid). Logs to store.",
};
export const schema = z.object({
oid: z.union([
z.number().int().nonnegative(),
z.string().regex(/^\d+$/, "oid must be a positive integer"),
]).optional(),
cloid: z
.string()
.regex(/^0x[a-fA-F0-9]{32}$/, "cloid must be a 0x-prefixed 16-byte hex")
.optional(),
symbol: z.string().min(1, "symbol is required").default("BTC-USD"),
environment: z.enum(["mainnet", "testnet"]).default("testnet"),
});
export async function POST(req: Request): Promise<Response> {
const body = await req.json().catch(() => ({}));
const { oid, cloid, symbol, environment } = schema.parse(body);
const rawSymbol = symbol.trim();
const orderSymbol = resolveHyperliquidOrderSymbol(rawSymbol);
if (!orderSymbol) {
return Response.json(
{ ok: false, error: "symbol must be a valid Hyperliquid market" },
{ status: 400 }
);
}
const pair = resolveHyperliquidPair(rawSymbol);
const parsedPair = pair ? parseSpotPairSymbol(pair) : null;
const assetSymbol =
normalizeHyperliquidBaseSymbol(rawSymbol) ??
normalizeHyperliquidBaseSymbol(orderSymbol) ??
orderSymbol;
const isSpot = isHyperliquidSpotSymbol(orderSymbol);
const marketIdentity = buildHyperliquidMarketIdentity({
environment,
symbol: pair ?? orderSymbol,
rawSymbol,
isSpot,
base: parsedPair?.base ?? null,
quote: parsedPair?.quote ?? null,
});
if (!marketIdentity) {
return Response.json(
{ ok: false, error: "Unable to resolve market identity for cancel." },
{ status: 400 }
);
}
if (!oid && !cloid) {
return Response.json(
{ ok: false, error: "oid or cloid is required" },
{ status: 400 }
);
}
const chainConfig = resolveHyperliquidChainConfig(environment);
const ctx = await wallet({
chain: chainConfig.chain,
rpcUrl: chainConfig.rpcUrl ?? process.env.RPC_URL,
});
let result: unknown;
if (cloid) {
result = await cancelHyperliquidOrdersByCloid({
wallet: ctx as WalletFullContext,
environment,
cancels: [{ symbol: orderSymbol, cloid: cloid as `0x${string}` }],
});
} else {
const numericOid = typeof oid === "string" ? Number.parseInt(oid, 10) : oid;
result = await cancelHyperliquidOrders({
wallet: ctx as WalletFullContext,
environment,
cancels: [{ symbol: orderSymbol, oid: numericOid! }],
});
}
await store({
source: "hyperliquid",
ref: (cloid ?? oid ?? "").toString(),
status: "cancelled",
walletAddress: ctx.address,
action: "order",
network: environment === "mainnet" ? "hyperliquid" : "hyperliquid-testnet",
market: marketIdentity,
metadata: {
symbol: orderSymbol,
assetSymbol,
pair: pair ?? null,
rawSymbol,
cancelled: cloid ?? oid,
environment,
},
});
return Response.json({ ok: true, result });
}