@livo-build/runtime 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/eip712.d.ts +45 -0
- package/dist/eip712.js +160 -0
- package/dist/eip712.test.d.ts +1 -0
- package/dist/eip712.test.js +106 -0
- package/dist/hyperliquid.d.ts +360 -0
- package/dist/hyperliquid.js +350 -0
- package/dist/hyperliquid.test.d.ts +1 -0
- package/dist/hyperliquid.test.js +31 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +11 -0
- package/dist/polymarket.d.ts +146 -0
- package/dist/polymarket.js +495 -0
- package/dist/polymarket.test.d.ts +1 -0
- package/dist/polymarket.test.js +111 -0
- package/dist/watcher.d.ts +83 -0
- package/dist/watcher.js +155 -0
- package/package.json +2 -1
package/dist/eip712.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type Bytes, type Hex } from "./hex.js";
|
|
2
|
+
export interface TypedDataField {
|
|
3
|
+
name: string;
|
|
4
|
+
type: string;
|
|
5
|
+
}
|
|
6
|
+
export interface TypedDataDomain {
|
|
7
|
+
name?: string;
|
|
8
|
+
version?: string;
|
|
9
|
+
chainId?: number | bigint;
|
|
10
|
+
verifyingContract?: string;
|
|
11
|
+
salt?: string;
|
|
12
|
+
}
|
|
13
|
+
export type TypedDataTypes = Record<string, readonly TypedDataField[]>;
|
|
14
|
+
export interface TypedDataDefinition {
|
|
15
|
+
domain: TypedDataDomain;
|
|
16
|
+
types: TypedDataTypes;
|
|
17
|
+
primaryType: string;
|
|
18
|
+
message: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
/** The 32-byte EIP-712 digest (`keccak256(0x1901 ‖ domainSeparator ‖ hashStruct)`). */
|
|
21
|
+
export declare function hashTypedData(def: TypedDataDefinition): Bytes;
|
|
22
|
+
/**
|
|
23
|
+
* Sign EIP-712 typed data with a raw private key. Returns the 65-byte
|
|
24
|
+
* `r ‖ s ‖ v` signature (v = 27/28), the form Ethereum verifiers expect.
|
|
25
|
+
*/
|
|
26
|
+
export declare function signTypedData(def: TypedDataDefinition & {
|
|
27
|
+
privateKey: string;
|
|
28
|
+
}): Hex;
|
|
29
|
+
/** Viem-style typed-data params (what a viem local account's signTypedData receives). */
|
|
30
|
+
export interface ViemTypedDataParams {
|
|
31
|
+
domain: TypedDataDomain;
|
|
32
|
+
types: TypedDataTypes;
|
|
33
|
+
primaryType: string;
|
|
34
|
+
message: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
export interface LocalAccount {
|
|
37
|
+
address: Hex;
|
|
38
|
+
signTypedData(params: ViemTypedDataParams): Promise<Hex>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A viem-"local account"-shaped signer backed by a raw private key — `{ address,
|
|
42
|
+
* signTypedData }`. Drops into SDKs that accept a viem account (e.g.
|
|
43
|
+
* @nktkas/hyperliquid's ExchangeClient) without depending on viem itself.
|
|
44
|
+
*/
|
|
45
|
+
export declare function localAccount(privateKey: string): LocalAccount;
|
package/dist/eip712.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// EIP-712 typed-data hashing + signing — the missing primitive both the
|
|
2
|
+
// Hyperliquid and Polymarket helpers need (their auth/order signatures are all
|
|
3
|
+
// EIP-712). Implemented on the same audited @noble crypto as tx.ts/sig.ts and
|
|
4
|
+
// validated byte-for-byte against viem in the test suite. Worker-safe, no deps
|
|
5
|
+
// beyond noble.
|
|
6
|
+
//
|
|
7
|
+
// `localAccount(privateKey)` returns a viem-"local account"-shaped object
|
|
8
|
+
// ({ address, signTypedData(params) }) so it drops straight into SDKs that accept
|
|
9
|
+
// a viem account (e.g. @nktkas/hyperliquid's ExchangeClient) without pulling viem.
|
|
10
|
+
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
11
|
+
import { keccak_256 } from "@noble/hashes/sha3";
|
|
12
|
+
import { bytesToHex, concatBytes, hexToBytes, toBytes, toBytesSigned, } from "./hex.js";
|
|
13
|
+
import { toChecksumAddress } from "./tx.js";
|
|
14
|
+
// The canonical field order for the EIP712Domain struct — only the present fields
|
|
15
|
+
// are included, matching what viem/ethers produce.
|
|
16
|
+
const DOMAIN_FIELDS = [
|
|
17
|
+
{ name: "name", type: "string" },
|
|
18
|
+
{ name: "version", type: "string" },
|
|
19
|
+
{ name: "chainId", type: "uint256" },
|
|
20
|
+
{ name: "verifyingContract", type: "address" },
|
|
21
|
+
{ name: "salt", type: "bytes32" },
|
|
22
|
+
];
|
|
23
|
+
function domainTypes(domain) {
|
|
24
|
+
return DOMAIN_FIELDS.filter((f) => domain[f.name] !== undefined && domain[f.name] !== null).map((f) => ({ name: f.name, type: f.type }));
|
|
25
|
+
}
|
|
26
|
+
// Collect the struct types `primaryType` depends on (transitively), excluding the
|
|
27
|
+
// well-known EIP712Domain. Used to build the canonical `encodeType` string.
|
|
28
|
+
function dependencies(primaryType, types, found = new Set()) {
|
|
29
|
+
const base = primaryType.replace(/\[.*\]$/, "");
|
|
30
|
+
if (found.has(base) || !types[base] || base === "EIP712Domain")
|
|
31
|
+
return [...found];
|
|
32
|
+
found.add(base);
|
|
33
|
+
for (const field of types[base]) {
|
|
34
|
+
for (const dep of dependencies(field.type, types, found))
|
|
35
|
+
found.add(dep);
|
|
36
|
+
}
|
|
37
|
+
return [...found];
|
|
38
|
+
}
|
|
39
|
+
function encodeType(primaryType, types) {
|
|
40
|
+
const deps = dependencies(primaryType, types).filter((d) => d !== primaryType);
|
|
41
|
+
deps.sort();
|
|
42
|
+
const ordered = [primaryType, ...deps];
|
|
43
|
+
return ordered
|
|
44
|
+
.map((t) => `${t}(${(types[t] ?? []).map((f) => `${f.type} ${f.name}`).join(",")})`)
|
|
45
|
+
.join("");
|
|
46
|
+
}
|
|
47
|
+
function typeHash(primaryType, types) {
|
|
48
|
+
return keccak_256(new TextEncoder().encode(encodeType(primaryType, types)));
|
|
49
|
+
}
|
|
50
|
+
function isArrayType(type) {
|
|
51
|
+
return type.endsWith("]");
|
|
52
|
+
}
|
|
53
|
+
// Encode one field to its 32-byte (or, for arrays/structs, hashed-to-32) chunk.
|
|
54
|
+
function encodeField(types, type, value) {
|
|
55
|
+
if (types[type]) {
|
|
56
|
+
// nested struct → hash of its encodeData
|
|
57
|
+
return keccak_256(encodeData(type, value, types));
|
|
58
|
+
}
|
|
59
|
+
if (isArrayType(type)) {
|
|
60
|
+
const baseType = type.slice(0, type.lastIndexOf("["));
|
|
61
|
+
const arr = value;
|
|
62
|
+
return keccak_256(concatBytes(...arr.map((item) => encodeField(types, baseType, item))));
|
|
63
|
+
}
|
|
64
|
+
if (type === "string") {
|
|
65
|
+
return keccak_256(new TextEncoder().encode(String(value)));
|
|
66
|
+
}
|
|
67
|
+
if (type === "bytes") {
|
|
68
|
+
return keccak_256(hexToBytes(String(value)));
|
|
69
|
+
}
|
|
70
|
+
if (type === "bool") {
|
|
71
|
+
return toBytes(value ? 1n : 0n, 32);
|
|
72
|
+
}
|
|
73
|
+
if (type === "address") {
|
|
74
|
+
const addr = hexToBytes(String(value));
|
|
75
|
+
if (addr.length !== 20)
|
|
76
|
+
throw new Error(`address must be 20 bytes: ${String(value)}`);
|
|
77
|
+
return concatBytes(new Uint8Array(12), addr);
|
|
78
|
+
}
|
|
79
|
+
const bytesN = type.match(/^bytes(\d+)$/);
|
|
80
|
+
if (bytesN) {
|
|
81
|
+
const raw = hexToBytes(String(value));
|
|
82
|
+
const out = new Uint8Array(32);
|
|
83
|
+
out.set(raw.slice(0, Number(bytesN[1])), 0); // right-padded
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
const uint = type.match(/^uint(\d+)?$/);
|
|
87
|
+
if (uint)
|
|
88
|
+
return toBytes(toBigIntValue(value), 32);
|
|
89
|
+
const int = type.match(/^int(\d+)?$/);
|
|
90
|
+
if (int)
|
|
91
|
+
return toBytesSigned(toBigIntValue(value), 32);
|
|
92
|
+
throw new Error(`unsupported EIP-712 type: ${type}`);
|
|
93
|
+
}
|
|
94
|
+
function toBigIntValue(value) {
|
|
95
|
+
if (typeof value === "bigint")
|
|
96
|
+
return value;
|
|
97
|
+
if (typeof value === "number")
|
|
98
|
+
return BigInt(value);
|
|
99
|
+
return BigInt(String(value)); // decimal or 0x-hex string
|
|
100
|
+
}
|
|
101
|
+
function encodeData(primaryType, data, types) {
|
|
102
|
+
const parts = [typeHash(primaryType, types)];
|
|
103
|
+
for (const field of types[primaryType]) {
|
|
104
|
+
parts.push(encodeField(types, field.type, data[field.name]));
|
|
105
|
+
}
|
|
106
|
+
return concatBytes(...parts);
|
|
107
|
+
}
|
|
108
|
+
function hashStruct(primaryType, data, types) {
|
|
109
|
+
return keccak_256(encodeData(primaryType, data, types));
|
|
110
|
+
}
|
|
111
|
+
function domainSeparator(domain) {
|
|
112
|
+
const types = { EIP712Domain: domainTypes(domain) };
|
|
113
|
+
const data = {};
|
|
114
|
+
for (const f of domainTypes(domain)) {
|
|
115
|
+
const key = f.name;
|
|
116
|
+
data[f.name] = f.type === "uint256" ? toBigIntValue(domain[key]) : domain[key];
|
|
117
|
+
}
|
|
118
|
+
return hashStruct("EIP712Domain", data, types);
|
|
119
|
+
}
|
|
120
|
+
/** The 32-byte EIP-712 digest (`keccak256(0x1901 ‖ domainSeparator ‖ hashStruct)`). */
|
|
121
|
+
export function hashTypedData(def) {
|
|
122
|
+
// `types` may carry an EIP712Domain entry (viem-style) — ignore it; the domain
|
|
123
|
+
// separator is derived from the present domain fields.
|
|
124
|
+
const sep = domainSeparator(def.domain);
|
|
125
|
+
const struct = hashStruct(def.primaryType, def.message, def.types);
|
|
126
|
+
return keccak_256(concatBytes(new Uint8Array([0x19, 0x01]), sep, struct));
|
|
127
|
+
}
|
|
128
|
+
function normalizeKey(privateKey) {
|
|
129
|
+
const k = privateKey.trim().replace(/^0x/, "");
|
|
130
|
+
if (!/^[0-9a-fA-F]{64}$/.test(k))
|
|
131
|
+
throw new Error("private key must be 32 bytes (64 hex chars)");
|
|
132
|
+
return hexToBytes(k);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Sign EIP-712 typed data with a raw private key. Returns the 65-byte
|
|
136
|
+
* `r ‖ s ‖ v` signature (v = 27/28), the form Ethereum verifiers expect.
|
|
137
|
+
*/
|
|
138
|
+
export function signTypedData(def) {
|
|
139
|
+
const digest = hashTypedData(def);
|
|
140
|
+
const sig = secp256k1.sign(digest, normalizeKey(def.privateKey), { lowS: true });
|
|
141
|
+
const r = toBytes(sig.r, 32);
|
|
142
|
+
const s = toBytes(sig.s, 32);
|
|
143
|
+
const v = new Uint8Array([27 + sig.recovery]);
|
|
144
|
+
return bytesToHex(concatBytes(r, s, v));
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* A viem-"local account"-shaped signer backed by a raw private key — `{ address,
|
|
148
|
+
* signTypedData }`. Drops into SDKs that accept a viem account (e.g.
|
|
149
|
+
* @nktkas/hyperliquid's ExchangeClient) without depending on viem itself.
|
|
150
|
+
*/
|
|
151
|
+
export function localAccount(privateKey) {
|
|
152
|
+
const pub = secp256k1.getPublicKey(normalizeKey(privateKey), false).slice(1);
|
|
153
|
+
const address = toChecksumAddress(bytesToHex(keccak_256(pub).slice(-20)));
|
|
154
|
+
return {
|
|
155
|
+
address,
|
|
156
|
+
async signTypedData(params) {
|
|
157
|
+
return signTypedData({ ...params, privateKey });
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// EIP-712 correctness: our hashTypedData/signTypedData must match viem byte-for-byte
|
|
2
|
+
// (these signatures move real money on Hyperliquid/Polymarket). viem is the oracle.
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { hashTypedData as _viemHash } from "viem";
|
|
5
|
+
import { privateKeyToAccount as _pkToAccount } from "viem/accounts";
|
|
6
|
+
import { hashTypedData, signTypedData, localAccount } from "./eip712.js";
|
|
7
|
+
import { bytesToHex } from "./hex.js";
|
|
8
|
+
const PK = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
|
|
9
|
+
// A representative spread: domain with all fields, nested struct, array, bytes,
|
|
10
|
+
// uint, address, bool, string.
|
|
11
|
+
const PERSON_DEF = {
|
|
12
|
+
domain: {
|
|
13
|
+
name: "Ether Mail",
|
|
14
|
+
version: "1",
|
|
15
|
+
chainId: 1,
|
|
16
|
+
verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
|
|
17
|
+
},
|
|
18
|
+
types: {
|
|
19
|
+
Person: [
|
|
20
|
+
{ name: "name", type: "string" },
|
|
21
|
+
{ name: "wallet", type: "address" },
|
|
22
|
+
],
|
|
23
|
+
Mail: [
|
|
24
|
+
{ name: "from", type: "Person" },
|
|
25
|
+
{ name: "to", type: "Person" },
|
|
26
|
+
{ name: "contents", type: "string" },
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
primaryType: "Mail",
|
|
30
|
+
message: {
|
|
31
|
+
from: { name: "Cow", wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" },
|
|
32
|
+
to: { name: "Bob", wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" },
|
|
33
|
+
contents: "Hello, Bob!",
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
// Hyperliquid's Agent typed data (chainId 1337, the "Exchange" domain).
|
|
37
|
+
const HL_AGENT_DEF = {
|
|
38
|
+
domain: { name: "Exchange", version: "1", chainId: 1337, verifyingContract: "0x0000000000000000000000000000000000000000" },
|
|
39
|
+
types: { Agent: [{ name: "source", type: "string" }, { name: "connectionId", type: "bytes32" }] },
|
|
40
|
+
primaryType: "Agent",
|
|
41
|
+
message: { source: "a", connectionId: "0x" + "ab".repeat(32) },
|
|
42
|
+
};
|
|
43
|
+
// Polymarket CTF Exchange Order — the exact struct we post.
|
|
44
|
+
const PM_ORDER_DEF = {
|
|
45
|
+
domain: { name: "Polymarket CTF Exchange", version: "1", chainId: 137, verifyingContract: "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E" },
|
|
46
|
+
types: {
|
|
47
|
+
Order: [
|
|
48
|
+
{ name: "salt", type: "uint256" },
|
|
49
|
+
{ name: "maker", type: "address" },
|
|
50
|
+
{ name: "signer", type: "address" },
|
|
51
|
+
{ name: "taker", type: "address" },
|
|
52
|
+
{ name: "tokenId", type: "uint256" },
|
|
53
|
+
{ name: "makerAmount", type: "uint256" },
|
|
54
|
+
{ name: "takerAmount", type: "uint256" },
|
|
55
|
+
{ name: "expiration", type: "uint256" },
|
|
56
|
+
{ name: "nonce", type: "uint256" },
|
|
57
|
+
{ name: "feeRateBps", type: "uint256" },
|
|
58
|
+
{ name: "side", type: "uint8" },
|
|
59
|
+
{ name: "signatureType", type: "uint8" },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
primaryType: "Order",
|
|
63
|
+
message: {
|
|
64
|
+
salt: "123456789",
|
|
65
|
+
maker: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
|
|
66
|
+
signer: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
|
|
67
|
+
taker: "0x0000000000000000000000000000000000000000",
|
|
68
|
+
tokenId: "71321045679252212594626385532706912750332728571942532289631379312455583992563",
|
|
69
|
+
makerAmount: "1000000",
|
|
70
|
+
takerAmount: "2000000",
|
|
71
|
+
expiration: "0",
|
|
72
|
+
nonce: "0",
|
|
73
|
+
feeRateBps: "0",
|
|
74
|
+
side: 0,
|
|
75
|
+
signatureType: 0,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
describe("hashTypedData matches viem", () => {
|
|
79
|
+
for (const [label, def] of [
|
|
80
|
+
["EIP-712 example (nested structs)", PERSON_DEF],
|
|
81
|
+
["Hyperliquid Agent", HL_AGENT_DEF],
|
|
82
|
+
["Polymarket Order", PM_ORDER_DEF],
|
|
83
|
+
]) {
|
|
84
|
+
it(label, () => {
|
|
85
|
+
const ours = bytesToHex(hashTypedData(def));
|
|
86
|
+
const theirs = _viemHash(def);
|
|
87
|
+
expect(ours).toBe(theirs);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
describe("signTypedData matches viem", () => {
|
|
92
|
+
it("produces the same 65-byte signature as a viem local account", async () => {
|
|
93
|
+
const ours = signTypedData({ ...PM_ORDER_DEF, privateKey: PK });
|
|
94
|
+
const account = _pkToAccount(PK);
|
|
95
|
+
const theirs = await account.signTypedData(PM_ORDER_DEF);
|
|
96
|
+
expect(ours).toBe(theirs);
|
|
97
|
+
});
|
|
98
|
+
it("localAccount address + signature match viem", async () => {
|
|
99
|
+
const acct = localAccount(PK);
|
|
100
|
+
const viemAcct = _pkToAccount(PK);
|
|
101
|
+
expect(acct.address.toLowerCase()).toBe(viemAcct.address.toLowerCase());
|
|
102
|
+
const ours = await acct.signTypedData(HL_AGENT_DEF);
|
|
103
|
+
const theirs = await viemAcct.signTypedData(HL_AGENT_DEF);
|
|
104
|
+
expect(ours).toBe(theirs);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { ExchangeClient, InfoClient } from "@nktkas/hyperliquid";
|
|
2
|
+
import { type Hex } from "./hex.js";
|
|
3
|
+
export interface HyperliquidOptions {
|
|
4
|
+
/** Signing key. Default: env.HYPERLIQUID_PRIVATE_KEY, then env.RELAYER_PRIVATE_KEY. */
|
|
5
|
+
privateKey?: string;
|
|
6
|
+
/** Env var name to read the key from. Default: "HYPERLIQUID_PRIVATE_KEY". */
|
|
7
|
+
privateKeySecret?: string;
|
|
8
|
+
/** Use testnet endpoints. Default: truthy env.HYPERLIQUID_TESTNET. */
|
|
9
|
+
testnet?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Which HIP-3 builder dexes to resolve symbols for: an array of dex names, or
|
|
12
|
+
* true for all of them. Default true — so "xyz:TSLA" & friends just work.
|
|
13
|
+
*/
|
|
14
|
+
dexs?: string[] | boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Master secret for deriving per-user AGENT keys (see `forUser`/`agentAddress`).
|
|
17
|
+
* Default: env.HL_AGENT_SECRET, then env.RELAYER_PRIVATE_KEY. Keep it stable —
|
|
18
|
+
* changing it changes every user's agent address (they'd have to re-approve).
|
|
19
|
+
*/
|
|
20
|
+
agentSecret?: string;
|
|
21
|
+
}
|
|
22
|
+
export type Tif = "Gtc" | "Ioc" | "Alo";
|
|
23
|
+
export type CandleInterval = "1m" | "3m" | "5m" | "15m" | "30m" | "1h" | "2h" | "4h" | "8h" | "12h" | "1d" | "3d" | "1w" | "1M";
|
|
24
|
+
export interface CandlesOptions {
|
|
25
|
+
/** Window start (ms epoch). Default: endTime − lookbackMs. */
|
|
26
|
+
startTime?: number;
|
|
27
|
+
/** Window end (ms epoch). Default: now. */
|
|
28
|
+
endTime?: number;
|
|
29
|
+
/** How far back from endTime to start. Default: 24h. */
|
|
30
|
+
lookbackMs?: number;
|
|
31
|
+
}
|
|
32
|
+
/** Live per-coin context: prices, funding, and open interest. */
|
|
33
|
+
export interface AssetContext {
|
|
34
|
+
coin: string;
|
|
35
|
+
markPx: number;
|
|
36
|
+
midPx: number | null;
|
|
37
|
+
oraclePx: number;
|
|
38
|
+
/** Current hourly funding rate (fraction, e.g. 0.0000125). */
|
|
39
|
+
funding: number;
|
|
40
|
+
openInterest: number;
|
|
41
|
+
prevDayPx: number;
|
|
42
|
+
/** 24h notional volume (USDC). */
|
|
43
|
+
dayNtlVlm: number;
|
|
44
|
+
}
|
|
45
|
+
/** Perp account summary. */
|
|
46
|
+
export interface AccountBalance {
|
|
47
|
+
accountValue: number;
|
|
48
|
+
withdrawable: number;
|
|
49
|
+
totalMarginUsed: number;
|
|
50
|
+
totalNtlPos: number;
|
|
51
|
+
}
|
|
52
|
+
export interface PlaceOrderOptions {
|
|
53
|
+
/** Reduce-only (close, never flip). Default false. */
|
|
54
|
+
reduceOnly?: boolean;
|
|
55
|
+
/** Time-in-force for limit orders. Default "Gtc". */
|
|
56
|
+
tif?: Tif;
|
|
57
|
+
}
|
|
58
|
+
export interface MarketOrderOptions {
|
|
59
|
+
/** Reduce-only (close, never flip). Default false. */
|
|
60
|
+
reduceOnly?: boolean;
|
|
61
|
+
/** Max slippage from mid for the protective limit price (0.05 = 5%). Default 0.05. */
|
|
62
|
+
slippage?: number;
|
|
63
|
+
}
|
|
64
|
+
interface MinimalEnv {
|
|
65
|
+
[key: string]: unknown;
|
|
66
|
+
}
|
|
67
|
+
export declare class Hyperliquid {
|
|
68
|
+
/** True when pointed at testnet. */
|
|
69
|
+
readonly testnet: boolean;
|
|
70
|
+
/** The signer's address (null when no key is configured → read-only). */
|
|
71
|
+
readonly address: Hex | null;
|
|
72
|
+
private readonly env;
|
|
73
|
+
private readonly transport;
|
|
74
|
+
private readonly _info;
|
|
75
|
+
private readonly privateKey?;
|
|
76
|
+
private readonly dexOption;
|
|
77
|
+
private readonly agentSecret?;
|
|
78
|
+
private readAddressOverride?;
|
|
79
|
+
private _exchange?;
|
|
80
|
+
private _converter?;
|
|
81
|
+
private _meta?;
|
|
82
|
+
constructor(env: MinimalEnv | undefined, options?: HyperliquidOptions);
|
|
83
|
+
/** Raw @nktkas InfoClient (all read endpoints). */
|
|
84
|
+
get info(): InfoClient;
|
|
85
|
+
/** Raw @nktkas ExchangeClient (all signed endpoints). Throws if no key is set. */
|
|
86
|
+
get exchange(): ExchangeClient;
|
|
87
|
+
/**
|
|
88
|
+
* All mid prices for a dex, keyed by coin. With no arg → the main perp dex
|
|
89
|
+
* (e.g. { BTC: "95000.0" }). Pass a builder-dex name for equities/commodities
|
|
90
|
+
* (e.g. mids("xyz") → { "xyz:TSLA": "399.8", "xyz:GOLD": "4160.3", … }).
|
|
91
|
+
*/
|
|
92
|
+
mids(dex?: string): Promise<Record<string, string>>;
|
|
93
|
+
/**
|
|
94
|
+
* Mid price of one coin as a number. Accepts a main-dex perp ("BTC") or a
|
|
95
|
+
* builder-dex asset ("xyz:TSLA") — the right dex is queried automatically.
|
|
96
|
+
* Throws if the coin isn't listed (try `findAsset` to resolve a bare ticker).
|
|
97
|
+
*/
|
|
98
|
+
mid(coin: string): Promise<number>;
|
|
99
|
+
/** L2 order book for a coin (main-dex "BTC" or builder-dex "xyz:TSLA"). */
|
|
100
|
+
book(coin: string): Promise<import("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts").L2BookResponse>;
|
|
101
|
+
/** Perp account state (positions, margin, withdrawable) for an address (default: the signer). */
|
|
102
|
+
positions(user?: string): Promise<import("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts").ClearinghouseStateResponse>;
|
|
103
|
+
/** Resting open orders for an address (default: the signer). */
|
|
104
|
+
openOrders(user?: string): Promise<import("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts").OpenOrdersResponse>;
|
|
105
|
+
/** Main perp-dex metadata (universe of coins + szDecimals + maxLeverage). Cached. */
|
|
106
|
+
meta(): Promise<{
|
|
107
|
+
universe: Array<{
|
|
108
|
+
name: string;
|
|
109
|
+
szDecimals: number;
|
|
110
|
+
maxLeverage: number;
|
|
111
|
+
}>;
|
|
112
|
+
}>;
|
|
113
|
+
/** The HIP-3 builder-deployed perp dexes (name + fullName + deployer). */
|
|
114
|
+
dexs(): Promise<{
|
|
115
|
+
name: string;
|
|
116
|
+
fullName?: string;
|
|
117
|
+
}[]>;
|
|
118
|
+
/**
|
|
119
|
+
* Resolve a bare ticker to its full Hyperliquid coin name. "BTC" → "BTC" (main
|
|
120
|
+
* dex); "TSLA" → "xyz:TSLA" (a builder dex). Returns null if nothing matches.
|
|
121
|
+
* Use the result with mid/book/limit/market/etc.
|
|
122
|
+
*/
|
|
123
|
+
findAsset(symbol: string): Promise<string | null>;
|
|
124
|
+
/** The integer asset id Hyperliquid uses in orders for a coin (main, spot, or builder dex). */
|
|
125
|
+
assetId(coin: string): Promise<number>;
|
|
126
|
+
/** Perp account summary (value, withdrawable, margin) for an address (default: the signer). */
|
|
127
|
+
balance(user?: string): Promise<AccountBalance>;
|
|
128
|
+
/** Spot token balances for an address (default: the signer). */
|
|
129
|
+
spotBalances(user?: string): Promise<import("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts").SpotClearinghouseStateResponse>;
|
|
130
|
+
/** Recent fills (trade history) for an address (default: the signer). */
|
|
131
|
+
fills(user?: string): Promise<import("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts").UserFillsResponse>;
|
|
132
|
+
/** Live context for a coin — mark/mid/oracle price, funding rate, open interest, 24h volume. */
|
|
133
|
+
assetCtx(coin: string): Promise<AssetContext>;
|
|
134
|
+
/** OHLCV candles for charting. `interval` default "1h"; window defaults to the last 24h. */
|
|
135
|
+
candles(coin: string, interval?: CandleInterval, opts?: CandlesOptions): Promise<import("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts").CandleSnapshotResponse>;
|
|
136
|
+
/** Raw order passthrough (the @nktkas `order` shape) for full control. */
|
|
137
|
+
order(params: Parameters<ExchangeClient["order"]>[0]): Promise<{
|
|
138
|
+
status: "ok";
|
|
139
|
+
response: {
|
|
140
|
+
type: "order";
|
|
141
|
+
data: {
|
|
142
|
+
statuses: ({
|
|
143
|
+
resting: {
|
|
144
|
+
oid: number;
|
|
145
|
+
cloid?: `0x${string}`;
|
|
146
|
+
};
|
|
147
|
+
} | {
|
|
148
|
+
filled: {
|
|
149
|
+
totalSz: string;
|
|
150
|
+
avgPx: string;
|
|
151
|
+
oid: number;
|
|
152
|
+
cloid?: `0x${string}`;
|
|
153
|
+
};
|
|
154
|
+
} | "waitingForFill" | "waitingForTrigger")[];
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
}>;
|
|
158
|
+
/** Place a limit order. `price`/`size` are numbers; we format to HL's precision. */
|
|
159
|
+
limit(coin: string, isBuy: boolean, size: number, price: number, opts?: PlaceOrderOptions): Promise<{
|
|
160
|
+
status: "ok";
|
|
161
|
+
response: {
|
|
162
|
+
type: "order";
|
|
163
|
+
data: {
|
|
164
|
+
statuses: ({
|
|
165
|
+
resting: {
|
|
166
|
+
oid: number;
|
|
167
|
+
cloid?: `0x${string}`;
|
|
168
|
+
};
|
|
169
|
+
} | {
|
|
170
|
+
filled: {
|
|
171
|
+
totalSz: string;
|
|
172
|
+
avgPx: string;
|
|
173
|
+
oid: number;
|
|
174
|
+
cloid?: `0x${string}`;
|
|
175
|
+
};
|
|
176
|
+
} | "waitingForFill" | "waitingForTrigger")[];
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
}>;
|
|
180
|
+
limitBuy(coin: string, size: number, price: number, opts?: PlaceOrderOptions): Promise<{
|
|
181
|
+
status: "ok";
|
|
182
|
+
response: {
|
|
183
|
+
type: "order";
|
|
184
|
+
data: {
|
|
185
|
+
statuses: ({
|
|
186
|
+
resting: {
|
|
187
|
+
oid: number;
|
|
188
|
+
cloid?: `0x${string}`;
|
|
189
|
+
};
|
|
190
|
+
} | {
|
|
191
|
+
filled: {
|
|
192
|
+
totalSz: string;
|
|
193
|
+
avgPx: string;
|
|
194
|
+
oid: number;
|
|
195
|
+
cloid?: `0x${string}`;
|
|
196
|
+
};
|
|
197
|
+
} | "waitingForFill" | "waitingForTrigger")[];
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
}>;
|
|
201
|
+
limitSell(coin: string, size: number, price: number, opts?: PlaceOrderOptions): Promise<{
|
|
202
|
+
status: "ok";
|
|
203
|
+
response: {
|
|
204
|
+
type: "order";
|
|
205
|
+
data: {
|
|
206
|
+
statuses: ({
|
|
207
|
+
resting: {
|
|
208
|
+
oid: number;
|
|
209
|
+
cloid?: `0x${string}`;
|
|
210
|
+
};
|
|
211
|
+
} | {
|
|
212
|
+
filled: {
|
|
213
|
+
totalSz: string;
|
|
214
|
+
avgPx: string;
|
|
215
|
+
oid: number;
|
|
216
|
+
cloid?: `0x${string}`;
|
|
217
|
+
};
|
|
218
|
+
} | "waitingForFill" | "waitingForTrigger")[];
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
}>;
|
|
222
|
+
/**
|
|
223
|
+
* Market order — an IOC limit priced `slippage` past the current mid so it
|
|
224
|
+
* crosses immediately. Returns the @nktkas order response.
|
|
225
|
+
*/
|
|
226
|
+
market(coin: string, isBuy: boolean, size: number, opts?: MarketOrderOptions): Promise<{
|
|
227
|
+
status: "ok";
|
|
228
|
+
response: {
|
|
229
|
+
type: "order";
|
|
230
|
+
data: {
|
|
231
|
+
statuses: ({
|
|
232
|
+
resting: {
|
|
233
|
+
oid: number;
|
|
234
|
+
cloid?: `0x${string}`;
|
|
235
|
+
};
|
|
236
|
+
} | {
|
|
237
|
+
filled: {
|
|
238
|
+
totalSz: string;
|
|
239
|
+
avgPx: string;
|
|
240
|
+
oid: number;
|
|
241
|
+
cloid?: `0x${string}`;
|
|
242
|
+
};
|
|
243
|
+
} | "waitingForFill" | "waitingForTrigger")[];
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
}>;
|
|
247
|
+
marketBuy(coin: string, size: number, opts?: MarketOrderOptions): Promise<{
|
|
248
|
+
status: "ok";
|
|
249
|
+
response: {
|
|
250
|
+
type: "order";
|
|
251
|
+
data: {
|
|
252
|
+
statuses: ({
|
|
253
|
+
resting: {
|
|
254
|
+
oid: number;
|
|
255
|
+
cloid?: `0x${string}`;
|
|
256
|
+
};
|
|
257
|
+
} | {
|
|
258
|
+
filled: {
|
|
259
|
+
totalSz: string;
|
|
260
|
+
avgPx: string;
|
|
261
|
+
oid: number;
|
|
262
|
+
cloid?: `0x${string}`;
|
|
263
|
+
};
|
|
264
|
+
} | "waitingForFill" | "waitingForTrigger")[];
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
}>;
|
|
268
|
+
marketSell(coin: string, size: number, opts?: MarketOrderOptions): Promise<{
|
|
269
|
+
status: "ok";
|
|
270
|
+
response: {
|
|
271
|
+
type: "order";
|
|
272
|
+
data: {
|
|
273
|
+
statuses: ({
|
|
274
|
+
resting: {
|
|
275
|
+
oid: number;
|
|
276
|
+
cloid?: `0x${string}`;
|
|
277
|
+
};
|
|
278
|
+
} | {
|
|
279
|
+
filled: {
|
|
280
|
+
totalSz: string;
|
|
281
|
+
avgPx: string;
|
|
282
|
+
oid: number;
|
|
283
|
+
cloid?: `0x${string}`;
|
|
284
|
+
};
|
|
285
|
+
} | "waitingForFill" | "waitingForTrigger")[];
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
}>;
|
|
289
|
+
/**
|
|
290
|
+
* Market-close the entire open position in `coin` (reduce-only). Reads the
|
|
291
|
+
* current size and crosses the opposite side. No-op (returns closed:false) if flat.
|
|
292
|
+
*/
|
|
293
|
+
closePosition(coin: string, opts?: {
|
|
294
|
+
slippage?: number;
|
|
295
|
+
}): Promise<{
|
|
296
|
+
closed: false;
|
|
297
|
+
side?: undefined;
|
|
298
|
+
size?: undefined;
|
|
299
|
+
result?: undefined;
|
|
300
|
+
} | {
|
|
301
|
+
closed: true;
|
|
302
|
+
side: string;
|
|
303
|
+
size: number;
|
|
304
|
+
result: {
|
|
305
|
+
status: "ok";
|
|
306
|
+
response: {
|
|
307
|
+
type: "order";
|
|
308
|
+
data: {
|
|
309
|
+
statuses: ({
|
|
310
|
+
resting: {
|
|
311
|
+
oid: number;
|
|
312
|
+
cloid?: `0x${string}`;
|
|
313
|
+
};
|
|
314
|
+
} | {
|
|
315
|
+
filled: {
|
|
316
|
+
totalSz: string;
|
|
317
|
+
avgPx: string;
|
|
318
|
+
oid: number;
|
|
319
|
+
cloid?: `0x${string}`;
|
|
320
|
+
};
|
|
321
|
+
} | "waitingForFill" | "waitingForTrigger")[];
|
|
322
|
+
};
|
|
323
|
+
};
|
|
324
|
+
};
|
|
325
|
+
}>;
|
|
326
|
+
/** Cancel a resting order by its order id (oid). */
|
|
327
|
+
cancel(coin: string, oid: number): Promise<{
|
|
328
|
+
status: "ok";
|
|
329
|
+
response: {
|
|
330
|
+
type: "cancel";
|
|
331
|
+
data: {
|
|
332
|
+
statuses: "success"[];
|
|
333
|
+
};
|
|
334
|
+
};
|
|
335
|
+
}>;
|
|
336
|
+
/** Set leverage for a coin (cross by default). */
|
|
337
|
+
updateLeverage(coin: string, leverage: number, opts?: {
|
|
338
|
+
cross?: boolean;
|
|
339
|
+
}): Promise<{
|
|
340
|
+
status: "ok";
|
|
341
|
+
response: {
|
|
342
|
+
type: "default";
|
|
343
|
+
};
|
|
344
|
+
}>;
|
|
345
|
+
private symbols;
|
|
346
|
+
private lookup;
|
|
347
|
+
private requireUser;
|
|
348
|
+
/** The per-user agent PRIVATE key (deterministic from the agent secret + userId; never stored). */
|
|
349
|
+
agentKey(userId: string): Hex;
|
|
350
|
+
/** The agent ADDRESS the user must approve (show this in your connect/approve UI). */
|
|
351
|
+
agentAddress(userId: string): Hex;
|
|
352
|
+
/**
|
|
353
|
+
* A Hyperliquid bound to one end-user: it SIGNS orders with that user's agent key
|
|
354
|
+
* and READS/attributes to their `master` account. Use after the user has approved
|
|
355
|
+
* `agentAddress(userId)`. Trading methods (marketBuy/limit/closePosition/cancel/…)
|
|
356
|
+
* then act on the user's account; positions()/balance() default to `master`.
|
|
357
|
+
*/
|
|
358
|
+
forUser(userId: string, master: string): Hyperliquid;
|
|
359
|
+
}
|
|
360
|
+
export {};
|