@reefclaw/openclaw-plugin 0.1.2 → 0.1.4
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/bridge/bridge.js +46 -3
- package/bridge/utils/skill-version.d.ts +9 -0
- package/bridge/utils/skill-version.js +45 -1
- package/ccxt/binance-ban-gate.js +3 -1
- package/ccxt/binance-public.d.ts +17 -1
- package/ccxt/binance-public.js +34 -1
- package/ccxt/intel-public.d.ts +25 -0
- package/ccxt/intel-public.js +80 -0
- package/ccxt/public-market-data-api.d.ts +12 -0
- package/ccxt/public-market-data-api.js +9 -0
- package/config/plugin-config-io.d.ts +15 -0
- package/index.js +117 -7
- package/ingest/position-auto-capture.d.ts +5 -0
- package/ingest/position-auto-capture.js +8 -2
- package/ingest/position-decisions-client.d.ts +4 -0
- package/ingest/readiness-reporter.d.ts +28 -5
- package/ingest/readiness-reporter.js +40 -19
- package/live/bracket-id.d.ts +9 -0
- package/live/bracket-id.js +18 -0
- package/package.json +2 -2
- package/skills/reefclaw/SKILL.md +6 -0
- package/tools/close-position.d.ts +2 -2
- package/tools/create-order.d.ts +2 -2
- package/tools/fetch-ohlcv.d.ts +2 -2
- package/tools/fetch-ticker.d.ts +2 -2
- package/tools/get-crypto-metrics.d.ts +2 -2
- package/tools/get-market-structure.d.ts +2 -2
- package/tools/get-orderbook.d.ts +2 -2
- package/tools/get-volume-analysis.d.ts +2 -2
- package/tools/helpers.d.ts +5 -4
- package/tools/helpers.js +2 -1
- package/types.d.ts +20 -1
- package/venues/hyperliquid/hl-public.d.ts +52 -0
- package/venues/hyperliquid/hl-public.js +285 -0
- package/venues/registry.d.ts +19 -0
- package/venues/registry.js +40 -0
|
@@ -85,6 +85,7 @@ export async function onCreateOrderFilled(ctx, inputs, order) {
|
|
|
85
85
|
currentSize: filledQty,
|
|
86
86
|
avgEntryPrice: fillPrice,
|
|
87
87
|
mode: ctx.resolveMode?.(),
|
|
88
|
+
exchange: ctx.venue,
|
|
88
89
|
};
|
|
89
90
|
// postPosition is awaited — we need the UUID before posting the entry row.
|
|
90
91
|
const positionId = await ctx.decisionsClient.postPosition(ctx.userId, upsert);
|
|
@@ -253,6 +254,7 @@ export async function onWsFillObserved(ctx, fill) {
|
|
|
253
254
|
currentSize: fill.fillSize,
|
|
254
255
|
avgEntryPrice: fill.fillPrice,
|
|
255
256
|
mode: ctx.resolveMode?.(),
|
|
257
|
+
exchange: ctx.venue,
|
|
256
258
|
};
|
|
257
259
|
const positionId = await ctx.decisionsClient.postPosition(ctx.userId, upsert);
|
|
258
260
|
if (!positionId) {
|
|
@@ -332,8 +334,12 @@ async function handleReduceOnlyExit(ctx, fill) {
|
|
|
332
334
|
regimeConfidence: 0.5,
|
|
333
335
|
fillPrice: fill.fillPrice,
|
|
334
336
|
fillSize: Math.abs(fill.fillSize),
|
|
335
|
-
// realizedPnl is Binance-exact (summed across exit fills)
|
|
336
|
-
//
|
|
337
|
+
// realizedPnl is Binance-exact (summed across exit fills); the zeros below
|
|
338
|
+
// are filled in PER-METRIC by the webapp close route (price-based R from
|
|
339
|
+
// the pinned invalidation_price, mfe/give-back from the last review — see
|
|
340
|
+
// webapp lib/api/close-metrics.ts). The route's old all-four-zero gate
|
|
341
|
+
// meant this non-zero PnL used to suppress that fill-in entirely, persisting
|
|
342
|
+
// realized_r=0 on every bracket_fill close (the 2026-07-11 zero-hole).
|
|
337
343
|
realizedPnl,
|
|
338
344
|
realizedR: 0,
|
|
339
345
|
mfeRAtClose: 0,
|
|
@@ -17,6 +17,10 @@ export interface PositionUpsertPayload {
|
|
|
17
17
|
* journal row so paper and live positions can be segregated. Omitted only
|
|
18
18
|
* if the active adapter can't be resolved (treated as legacy/NULL). */
|
|
19
19
|
mode?: 'paper' | 'live';
|
|
20
|
+
/** Venue the position was opened on ('binance' | 'hyperliquid') — journal /
|
|
21
|
+
* analytics segmentation (fees + funding cadence differ per venue).
|
|
22
|
+
* Absent → NULL (legacy binance), mirroring `mode`. Migration 0058. */
|
|
23
|
+
exchange?: string;
|
|
20
24
|
}
|
|
21
25
|
export interface PositionEntryPayload {
|
|
22
26
|
positionId: string;
|
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
import { type ReadinessReport } from '@reefclaw/shared';
|
|
2
|
-
|
|
1
|
+
import { type ReadinessReport, type VenueId } from '@reefclaw/shared';
|
|
2
|
+
/** Venue-agnostic reachability probe — BinancePublicApi.probeReachability and
|
|
3
|
+
* HyperliquidPublicApi.probeReachability both return exactly this shape. */
|
|
4
|
+
export interface VenueReachabilityProbe {
|
|
5
|
+
probeReachability(): Promise<{
|
|
6
|
+
outcome: 'reachable' | 'geo_blocked' | 'unreachable' | 'unknown';
|
|
7
|
+
driftMs: number | null;
|
|
8
|
+
}>;
|
|
9
|
+
}
|
|
3
10
|
export interface ReadinessReporterOptions {
|
|
4
11
|
apiBaseUrl: string;
|
|
5
12
|
token: string;
|
|
6
|
-
|
|
13
|
+
/** Which venue this agent trades — decides the reachability check id
|
|
14
|
+
* (binance_reachable vs hyperliquid_reachable) and which host `publicApi`
|
|
15
|
+
* probes. The reporter probes ONLY the configured venue: on a
|
|
16
|
+
* Binance-451-blocked host trading Hyperliquid, a Binance probe would be
|
|
17
|
+
* a permanent false alarm (plan §5.11). */
|
|
18
|
+
venue: VenueId;
|
|
19
|
+
/** Public API of the CONFIGURED venue (probe only). */
|
|
20
|
+
publicApi: VenueReachabilityProbe;
|
|
7
21
|
/** Number of trading tools registered (a health signal). */
|
|
8
22
|
toolCount: number;
|
|
9
23
|
fetchImpl?: typeof fetch;
|
|
@@ -11,8 +25,17 @@ export interface ReadinessReporterOptions {
|
|
|
11
25
|
requestTimeoutMs?: number;
|
|
12
26
|
}
|
|
13
27
|
/** Run every connect-phase check once and assemble the report. Exported for
|
|
14
|
-
* unit tests.
|
|
15
|
-
|
|
28
|
+
* unit tests.
|
|
29
|
+
*
|
|
30
|
+
* `bootWarmup` marks the FIRST cycle, which fires during gateway boot while
|
|
31
|
+
* the event loop is congested (WS start, snapshot, seed all racing). That
|
|
32
|
+
* delay between the probe's server-time response and the local `Date.now()`
|
|
33
|
+
* read inflates the apparent clock drift — observed −4112ms at boot
|
|
34
|
+
* self-correcting to −108ms on the next 5-min cycle. During warm-up a
|
|
35
|
+
* warn/fail drift is reported 'unknown' (not amber/red) so the readiness
|
|
36
|
+
* banner doesn't cry-wolf for ~5 min after every restart; a genuinely
|
|
37
|
+
* skewed clock still surfaces on cycle 2. */
|
|
38
|
+
export declare function collectReadiness(opts: Pick<ReadinessReporterOptions, 'venue' | 'publicApi' | 'toolCount'>, bootWarmup?: boolean): Promise<ReadinessReport>;
|
|
16
39
|
/** Test-only — reset the singleton guard between unit tests. */
|
|
17
40
|
export declare function __resetReadinessReporterForTests(): void;
|
|
18
41
|
/** Fire the readiness report once at boot then on an unref'd interval. */
|
|
@@ -24,10 +24,24 @@ function resolveIntervalMs(explicit) {
|
|
|
24
24
|
return Math.max(MIN_INTERVAL_MS, raw);
|
|
25
25
|
}
|
|
26
26
|
/** Run every connect-phase check once and assemble the report. Exported for
|
|
27
|
-
* unit tests.
|
|
28
|
-
|
|
27
|
+
* unit tests.
|
|
28
|
+
*
|
|
29
|
+
* `bootWarmup` marks the FIRST cycle, which fires during gateway boot while
|
|
30
|
+
* the event loop is congested (WS start, snapshot, seed all racing). That
|
|
31
|
+
* delay between the probe's server-time response and the local `Date.now()`
|
|
32
|
+
* read inflates the apparent clock drift — observed −4112ms at boot
|
|
33
|
+
* self-correcting to −108ms on the next 5-min cycle. During warm-up a
|
|
34
|
+
* warn/fail drift is reported 'unknown' (not amber/red) so the readiness
|
|
35
|
+
* banner doesn't cry-wolf for ~5 min after every restart; a genuinely
|
|
36
|
+
* skewed clock still surfaces on cycle 2. */
|
|
37
|
+
export async function collectReadiness(opts, bootWarmup = false) {
|
|
29
38
|
const now = Date.now();
|
|
30
39
|
const checks = [];
|
|
40
|
+
// The venue decides which reachability check this report carries; the copy
|
|
41
|
+
// for both ids lives in shared/src/readiness.ts. Clock drift comes from the
|
|
42
|
+
// same probe on both venues (Binance fapi serverTime / Hyperliquid
|
|
43
|
+
// exchangeStatus.time — live-verified 2026-07-11).
|
|
44
|
+
const reachId = opts.venue === 'hyperliquid' ? 'hyperliquid_reachable' : 'binance_reachable';
|
|
31
45
|
// plugin_loaded — trivially true (this code runs inside the loaded plugin),
|
|
32
46
|
// but a positive row is what proves the report path is alive at all.
|
|
33
47
|
checks.push(makeReadinessCheck('plugin_loaded', 'pass', { checkedAt: now }));
|
|
@@ -36,37 +50,46 @@ export async function collectReadiness(opts) {
|
|
|
36
50
|
detail: `${opts.toolCount} tools`,
|
|
37
51
|
checkedAt: now,
|
|
38
52
|
}));
|
|
39
|
-
//
|
|
40
|
-
const probe = await opts.
|
|
53
|
+
// venue reachability (+ clock drift from the same probe response)
|
|
54
|
+
const probe = await opts.publicApi.probeReachability();
|
|
41
55
|
if (probe.outcome === 'reachable') {
|
|
42
|
-
checks.push(makeReadinessCheck(
|
|
56
|
+
checks.push(makeReadinessCheck(reachId, 'pass', { checkedAt: now }));
|
|
43
57
|
const drift = probe.driftMs;
|
|
44
58
|
if (drift == null) {
|
|
45
59
|
checks.push(makeReadinessCheck('clock_in_sync', 'unknown', { checkedAt: now }));
|
|
46
60
|
}
|
|
47
61
|
else {
|
|
48
62
|
const abs = Math.abs(drift);
|
|
49
|
-
const
|
|
63
|
+
const rawStatus = abs <= 1000 ? 'pass' : abs <= 5000 ? 'warn' : 'fail';
|
|
64
|
+
// Boot warm-up: a non-pass reading on the first cycle is untrustworthy
|
|
65
|
+
// (event-loop congestion inflates the measured drift) — report 'unknown'
|
|
66
|
+
// and let the next cycle confirm, rather than flash the banner amber/red.
|
|
67
|
+
const status = bootWarmup && rawStatus !== 'pass' ? 'unknown' : rawStatus;
|
|
50
68
|
checks.push(makeReadinessCheck('clock_in_sync', status, {
|
|
51
|
-
detail:
|
|
69
|
+
detail: bootWarmup && rawStatus !== 'pass'
|
|
70
|
+
? `drift ${Math.round(drift)}ms (boot warm-up — rechecking)`
|
|
71
|
+
: `drift ${Math.round(drift)}ms`,
|
|
52
72
|
checkedAt: now,
|
|
53
73
|
}));
|
|
54
74
|
}
|
|
55
75
|
}
|
|
56
76
|
else if (probe.outcome === 'geo_blocked') {
|
|
57
|
-
checks.push(makeReadinessCheck(
|
|
77
|
+
checks.push(makeReadinessCheck(reachId, 'fail', {
|
|
78
|
+
detail: opts.venue === 'binance' ? 'HTTP 451' : 'blocked (HTTP 451/403)',
|
|
79
|
+
checkedAt: now,
|
|
80
|
+
}));
|
|
58
81
|
checks.push(makeReadinessCheck('clock_in_sync', 'unknown', { checkedAt: now }));
|
|
59
82
|
}
|
|
60
83
|
else if (probe.outcome === 'unreachable') {
|
|
61
84
|
// Network/DNS/timeout — could be transient, so warn (amber) rather than
|
|
62
85
|
// asserting a definitive failure. A persistent problem stays amber across
|
|
63
|
-
// re-checks; only the
|
|
64
|
-
checks.push(makeReadinessCheck(
|
|
86
|
+
// re-checks; only the geo-block is a hard red.
|
|
87
|
+
checks.push(makeReadinessCheck(reachId, 'warn', { detail: 'unreachable', checkedAt: now }));
|
|
65
88
|
checks.push(makeReadinessCheck('clock_in_sync', 'unknown', { checkedAt: now }));
|
|
66
89
|
}
|
|
67
90
|
else {
|
|
68
91
|
// 'unknown' — the ban/weight gate paused the probe; don't assert anything.
|
|
69
|
-
checks.push(makeReadinessCheck(
|
|
92
|
+
checks.push(makeReadinessCheck(reachId, 'unknown', { checkedAt: now }));
|
|
70
93
|
checks.push(makeReadinessCheck('clock_in_sync', 'unknown', { checkedAt: now }));
|
|
71
94
|
}
|
|
72
95
|
return {
|
|
@@ -74,7 +97,7 @@ export async function collectReadiness(opts) {
|
|
|
74
97
|
generatedAt: now,
|
|
75
98
|
overall: deriveOverallReadiness(checks),
|
|
76
99
|
checks,
|
|
77
|
-
agent: { pluginVersion: PLUGIN_VERSION, toolCount: opts.toolCount },
|
|
100
|
+
agent: { pluginVersion: PLUGIN_VERSION, toolCount: opts.toolCount, venue: opts.venue },
|
|
78
101
|
};
|
|
79
102
|
}
|
|
80
103
|
async function postReadiness(apiBaseUrl, token, report, fetchImpl, timeoutMs) {
|
|
@@ -119,12 +142,9 @@ export function startReadinessReporter(opts) {
|
|
|
119
142
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
120
143
|
const intervalMs = resolveIntervalMs(opts.intervalMs);
|
|
121
144
|
const timeoutMs = opts.requestTimeoutMs ?? 10_000;
|
|
122
|
-
const cycle = async () => {
|
|
145
|
+
const cycle = async (bootWarmup) => {
|
|
123
146
|
try {
|
|
124
|
-
const report = await collectReadiness({
|
|
125
|
-
binanceApi: opts.binanceApi,
|
|
126
|
-
toolCount: opts.toolCount,
|
|
127
|
-
});
|
|
147
|
+
const report = await collectReadiness({ venue: opts.venue, publicApi: opts.publicApi, toolCount: opts.toolCount }, bootWarmup);
|
|
128
148
|
await postReadiness(opts.apiBaseUrl, opts.token, report, fetchImpl, timeoutMs);
|
|
129
149
|
if (report.overall === 'fail') {
|
|
130
150
|
const failing = report.checks.filter((c) => c.status === 'fail').map((c) => c.id).join(', ');
|
|
@@ -135,8 +155,9 @@ export function startReadinessReporter(opts) {
|
|
|
135
155
|
logger.warn(TAG, `readiness cycle failed: ${formatError(err)}`);
|
|
136
156
|
}
|
|
137
157
|
};
|
|
138
|
-
|
|
139
|
-
|
|
158
|
+
// First fire is the boot cycle (warm-up); the interval cycles are steady-state.
|
|
159
|
+
void cycle(true);
|
|
160
|
+
const timer = setInterval(() => void cycle(false), intervalMs);
|
|
140
161
|
timer.unref();
|
|
141
162
|
logger.info(TAG, `readiness reporter started (interval ${Math.round(intervalMs / 1000)}s)`);
|
|
142
163
|
}
|
package/live/bracket-id.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { VenueId } from '@reefclaw/shared';
|
|
1
2
|
import type { BracketId, BracketRole } from './bracket-types.js';
|
|
2
3
|
/** Generate a fresh 16-char hex bracketId. */
|
|
3
4
|
export declare function generateBracketId(): BracketId;
|
|
@@ -16,3 +17,11 @@ export declare function parseBracketCid(cid: string): {
|
|
|
16
17
|
/** Cheap type-guard for "is this a reefclaw-managed bracket clientOrderId"
|
|
17
18
|
* — true for both the current and legacy schemes. */
|
|
18
19
|
export declare function isBracketCid(cid: string): boolean;
|
|
20
|
+
/** Venue-aware parseBracketCid. Binance delegates to the existing dual-scheme
|
|
21
|
+
* parser; other venues return null until their scheme ships. */
|
|
22
|
+
export declare function parseBracketClientId(venue: VenueId, cid: string): {
|
|
23
|
+
bracketId: BracketId;
|
|
24
|
+
role: BracketRole;
|
|
25
|
+
} | null;
|
|
26
|
+
/** Venue-aware isBracketCid. */
|
|
27
|
+
export declare function isBracketClientId(venue: VenueId, cid: string): boolean;
|
package/live/bracket-id.js
CHANGED
|
@@ -79,3 +79,21 @@ export function parseBracketCid(cid) {
|
|
|
79
79
|
export function isBracketCid(cid) {
|
|
80
80
|
return CID_REGEX.test(cid) || LEGACY_CID_REGEX.test(cid);
|
|
81
81
|
}
|
|
82
|
+
// ---- Venue-dispatched recognition (multi-venue Phase 0 seam) ----
|
|
83
|
+
//
|
|
84
|
+
// ALL bracket client-order-id recognition stays centralized in THIS module —
|
|
85
|
+
// the brackets.md rule ("never hardcode an rc-/bkt regex elsewhere") extends
|
|
86
|
+
// per-venue. Binance = the bkt/rc- schemes above. Hyperliquid client order
|
|
87
|
+
// ids are 128-bit hex cloids with a different scheme (hl-cloid.ts, Phase 3 of
|
|
88
|
+
// docs/HYPERLIQUID_INTEGRATION_PLAN.md §5.7) and will be dispatched from
|
|
89
|
+
// here; until then the hyperliquid arm recognises nothing, which is correct —
|
|
90
|
+
// this build never emits an HL bracket order.
|
|
91
|
+
/** Venue-aware parseBracketCid. Binance delegates to the existing dual-scheme
|
|
92
|
+
* parser; other venues return null until their scheme ships. */
|
|
93
|
+
export function parseBracketClientId(venue, cid) {
|
|
94
|
+
return venue === 'binance' ? parseBracketCid(cid) : null;
|
|
95
|
+
}
|
|
96
|
+
/** Venue-aware isBracketCid. */
|
|
97
|
+
export function isBracketClientId(venue, cid) {
|
|
98
|
+
return venue === 'binance' ? isBracketCid(cid) : false;
|
|
99
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reefclaw/openclaw-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "ReefClaw trading plugin for OpenClaw \u2014 paper trading with real Binance market data, plus the ReefClaw dashboard connector (supervised by OpenClaw, no service manager needed). Install: /plugins install clawhub:@reefclaw/openclaw-plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"node": ">=20"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@reefclaw/shared": "0.1.
|
|
25
|
+
"@reefclaw/shared": "0.1.1",
|
|
26
26
|
"ccxt": "4.5.37",
|
|
27
27
|
"json5": "2.2.3",
|
|
28
28
|
"ws": "8.19.0"
|
package/skills/reefclaw/SKILL.md
CHANGED
|
@@ -39,6 +39,12 @@ You are being connected to **ReefClaw**, a supervised trading control room. Your
|
|
|
39
39
|
|
|
40
40
|
**Do not attempt to trade before the full instructions arrive.** If trading tools are visible but this file is still a bootstrap version (0.0.x), the connection has not completed — finish connecting first.
|
|
41
41
|
|
|
42
|
+
## Host requirements (check before connecting)
|
|
43
|
+
|
|
44
|
+
ReefClaw trades on Binance USD-M Futures, so **the machine running this OpenClaw must be able to reach Binance** (`fapi.binance.com`). Binance geo-restricts many locations: cheap US cloud regions — and some other jurisdictions — return **HTTP 451** ("restricted location"), and the agent then cannot fetch market data or trade. This applies even in **paper mode**, which uses live Binance prices.
|
|
45
|
+
|
|
46
|
+
If your market-data calls fail with `451`, this host is geo-blocked — move the agent to a Binance-permitted region (most EU and several Asia VPS regions work). This is a hosting requirement; ReefClaw cannot configure around it.
|
|
47
|
+
|
|
42
48
|
## Connecting (first run) — saving the connect message
|
|
43
49
|
|
|
44
50
|
The onboarding page at [reefclaw.com/onboarding](https://reefclaw.com/onboarding) gives the user a single **connect message** that carries THREE fields:
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PublicMarketDataApi } from '../ccxt/public-market-data-api.js';
|
|
2
2
|
import type { IExchangeAdapter } from '../exchange-adapter.js';
|
|
3
3
|
import type { CcxtOrder } from '../types.js';
|
|
4
4
|
import { type ClosePositionArgs } from './assessment-validation.js';
|
|
5
5
|
import { type AutoCaptureContext } from '../ingest/position-auto-capture.js';
|
|
6
6
|
import { type ExitGateMode } from '../config/position-review-config.js';
|
|
7
7
|
export declare function closePositionTool(args: ClosePositionArgs, deps: {
|
|
8
|
-
binanceApi:
|
|
8
|
+
binanceApi: PublicMarketDataApi;
|
|
9
9
|
adapter: IExchangeAdapter;
|
|
10
10
|
autoCapture?: AutoCaptureContext;
|
|
11
11
|
/** Override for tests — production reads from plugin-config.json on each
|
package/tools/create-order.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PublicMarketDataApi } from '../ccxt/public-market-data-api.js';
|
|
2
2
|
import type { IExchangeAdapter } from '../exchange-adapter.js';
|
|
3
3
|
import type { CcxtOrder } from '../types.js';
|
|
4
4
|
import { type AutoCaptureContext } from '../ingest/position-auto-capture.js';
|
|
@@ -21,7 +21,7 @@ export declare function createOrderTool(args: {
|
|
|
21
21
|
realization_rule?: unknown;
|
|
22
22
|
supersedes_id?: string;
|
|
23
23
|
}, deps: {
|
|
24
|
-
binanceApi:
|
|
24
|
+
binanceApi: PublicMarketDataApi;
|
|
25
25
|
adapter: IExchangeAdapter;
|
|
26
26
|
autoCapture?: AutoCaptureContext;
|
|
27
27
|
/** Approval-mode wiring. The same ProposalManager handles both modes;
|
package/tools/fetch-ohlcv.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PublicMarketDataApi } from '../ccxt/public-market-data-api.js';
|
|
2
2
|
import type { CcxtOHLCV } from '../types.js';
|
|
3
3
|
export declare function fetchOhlcvTool(args: {
|
|
4
4
|
symbol: string;
|
|
5
5
|
timeframe?: string;
|
|
6
6
|
limit?: number;
|
|
7
7
|
}, deps: {
|
|
8
|
-
binanceApi:
|
|
8
|
+
binanceApi: PublicMarketDataApi;
|
|
9
9
|
}): Promise<CcxtOHLCV[] | {
|
|
10
10
|
error: string;
|
|
11
11
|
}>;
|
package/tools/fetch-ticker.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PublicMarketDataApi } from '../ccxt/public-market-data-api.js';
|
|
2
2
|
import type { ExchangeSimulator } from '../simulator/exchange-simulator.js';
|
|
3
3
|
import type { CcxtTicker } from '../types.js';
|
|
4
4
|
export declare function fetchTickerTool(args: {
|
|
5
5
|
symbol: string;
|
|
6
6
|
}, deps: {
|
|
7
|
-
binanceApi:
|
|
7
|
+
binanceApi: PublicMarketDataApi;
|
|
8
8
|
simulator: ExchangeSimulator;
|
|
9
9
|
}): Promise<CcxtTicker | {
|
|
10
10
|
error: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PublicMarketDataApi } from '../ccxt/public-market-data-api.js';
|
|
2
2
|
export interface CryptoMetricsResult {
|
|
3
3
|
symbol: string;
|
|
4
4
|
fundingRate: number | null;
|
|
@@ -12,7 +12,7 @@ export interface CryptoMetricsResult {
|
|
|
12
12
|
export declare function getCryptoMetricsTool(args: {
|
|
13
13
|
symbol: string;
|
|
14
14
|
}, deps: {
|
|
15
|
-
binanceApi:
|
|
15
|
+
binanceApi: PublicMarketDataApi;
|
|
16
16
|
}): Promise<CryptoMetricsResult | {
|
|
17
17
|
error: string;
|
|
18
18
|
}>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PublicMarketDataApi } from '../ccxt/public-market-data-api.js';
|
|
2
2
|
/**
|
|
3
3
|
* Average True Range using Wilder's smoothing.
|
|
4
4
|
* Input: OHLCV candles array, period (default 14).
|
|
@@ -41,7 +41,7 @@ export declare function getMarketStructureTool(args: {
|
|
|
41
41
|
symbol: string;
|
|
42
42
|
timeframes?: string[];
|
|
43
43
|
}, deps: {
|
|
44
|
-
binanceApi:
|
|
44
|
+
binanceApi: PublicMarketDataApi;
|
|
45
45
|
}): Promise<MarketStructureResult | {
|
|
46
46
|
error: string;
|
|
47
47
|
}>;
|
package/tools/get-orderbook.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PublicMarketDataApi } from '../ccxt/public-market-data-api.js';
|
|
2
2
|
export interface OrderbookResult {
|
|
3
3
|
symbol: string;
|
|
4
4
|
bids: [number, number][];
|
|
@@ -15,7 +15,7 @@ export declare function getOrderbookTool(args: {
|
|
|
15
15
|
symbol: string;
|
|
16
16
|
depth?: number;
|
|
17
17
|
}, deps: {
|
|
18
|
-
binanceApi:
|
|
18
|
+
binanceApi: PublicMarketDataApi;
|
|
19
19
|
}): Promise<OrderbookResult | {
|
|
20
20
|
error: string;
|
|
21
21
|
}>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PublicMarketDataApi } from '../ccxt/public-market-data-api.js';
|
|
2
2
|
export interface VolumeAnalysisResult {
|
|
3
3
|
symbol: string;
|
|
4
4
|
timeframe: string;
|
|
@@ -15,7 +15,7 @@ export declare function getVolumeAnalysisTool(args: {
|
|
|
15
15
|
symbol: string;
|
|
16
16
|
timeframe?: string;
|
|
17
17
|
}, deps: {
|
|
18
|
-
binanceApi:
|
|
18
|
+
binanceApi: PublicMarketDataApi;
|
|
19
19
|
}): Promise<VolumeAnalysisResult | {
|
|
20
20
|
error: string;
|
|
21
21
|
}>;
|
package/tools/helpers.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PublicMarketDataApi } from '../ccxt/public-market-data-api.js';
|
|
2
2
|
import type { ExchangeSimulator } from '../simulator/exchange-simulator.js';
|
|
3
3
|
import type { CcxtTicker } from '../types.js';
|
|
4
4
|
/**
|
|
5
|
-
* Fetch the latest ticker from
|
|
5
|
+
* Fetch the latest ticker from the configured market-data source (Binance, or
|
|
6
|
+
* intel in paper mode on a geo-blocked host) and update the simulator.
|
|
6
7
|
* Returns the ticker on success, or an error object on failure.
|
|
7
8
|
*/
|
|
8
9
|
export declare function fetchCurrentPrice(symbol: string, deps: {
|
|
9
|
-
binanceApi:
|
|
10
|
+
binanceApi: PublicMarketDataApi;
|
|
10
11
|
simulator: ExchangeSimulator;
|
|
11
12
|
}): Promise<CcxtTicker | {
|
|
12
13
|
error: string;
|
|
@@ -16,7 +17,7 @@ export declare function fetchCurrentPrice(symbol: string, deps: {
|
|
|
16
17
|
* Best-effort: never throws. Skips fetch if cached book is fresh enough.
|
|
17
18
|
*/
|
|
18
19
|
export declare function fetchOrderBook(symbol: string, deps: {
|
|
19
|
-
binanceApi:
|
|
20
|
+
binanceApi: PublicMarketDataApi;
|
|
20
21
|
simulator: ExchangeSimulator;
|
|
21
22
|
}): Promise<void>;
|
|
22
23
|
/** Type guard: check if the result is an error object */
|
package/tools/helpers.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Shared helpers for tool implementations.
|
|
2
2
|
/**
|
|
3
|
-
* Fetch the latest ticker from
|
|
3
|
+
* Fetch the latest ticker from the configured market-data source (Binance, or
|
|
4
|
+
* intel in paper mode on a geo-blocked host) and update the simulator.
|
|
4
5
|
* Returns the ticker on success, or an error object on failure.
|
|
5
6
|
*/
|
|
6
7
|
export async function fetchCurrentPrice(symbol, deps) {
|
package/types.d.ts
CHANGED
|
@@ -102,9 +102,28 @@ export interface PluginConfig {
|
|
|
102
102
|
quoteCurrency: string;
|
|
103
103
|
}
|
|
104
104
|
export declare const DEFAULT_CONFIG: PluginConfig;
|
|
105
|
-
/** Exchange credentials for shadow/live modes. Read from
|
|
105
|
+
/** Exchange credentials for shadow/live modes. Read from plugin-config.json's
|
|
106
|
+
* `exchange` block (openclaw.json legacy fallback). Which fields matter
|
|
107
|
+
* depends on `venue` (docs/HYPERLIQUID_INTEGRATION_PLAN.md §5.2):
|
|
108
|
+
*
|
|
109
|
+
* binance (default, and the only live-supported venue until Phase 3) —
|
|
110
|
+
* apiKey + secret (HMAC pair) are required; walletAddress/agentPrivateKey
|
|
111
|
+
* are ignored.
|
|
112
|
+
* hyperliquid — walletAddress (MASTER wallet address, 0x…, used for
|
|
113
|
+
* queries; never its private key) + agentPrivateKey (an approved
|
|
114
|
+
* agent/API wallet's key — signs orders, cannot withdraw). apiKey/secret
|
|
115
|
+
* are meaningless. Boot falls back to PAPER for this venue until the
|
|
116
|
+
* Phase 3 adapter ships.
|
|
117
|
+
*
|
|
118
|
+
* apiKey/secret stay required at the type level because every constructed
|
|
119
|
+
* instance today feeds BinancePrivateApi; the on-disk JSON is parsed, not
|
|
120
|
+
* type-constructed, so an HL block without them is readable. Phase 3
|
|
121
|
+
* restructures this into a per-venue discriminated union. */
|
|
106
122
|
export interface ExchangeConfig {
|
|
123
|
+
venue?: 'binance' | 'hyperliquid';
|
|
107
124
|
apiKey: string;
|
|
108
125
|
secret: string;
|
|
109
126
|
testnet?: boolean;
|
|
127
|
+
walletAddress?: string;
|
|
128
|
+
agentPrivateKey?: string;
|
|
110
129
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CcxtTicker, CcxtOHLCV } from '../../types.js';
|
|
2
|
+
import type { OrderBookDepth } from '../../simulator/types.js';
|
|
3
|
+
import type { PublicMarketDataApi } from '../../ccxt/public-market-data-api.js';
|
|
4
|
+
export interface HyperliquidPublicApiOptions {
|
|
5
|
+
testnet?: boolean;
|
|
6
|
+
/** Test seam — injected ccxt exchange instance. */
|
|
7
|
+
exchange?: any;
|
|
8
|
+
fetchImpl?: typeof fetch;
|
|
9
|
+
}
|
|
10
|
+
export declare class HyperliquidPublicApi implements PublicMarketDataApi {
|
|
11
|
+
private exchange;
|
|
12
|
+
private readonly testnet;
|
|
13
|
+
private readonly fetchImpl;
|
|
14
|
+
private readonly tickerTtlMs;
|
|
15
|
+
/** One upstream fetchTickers call serves every symbol within the TTL. */
|
|
16
|
+
private tickersCache;
|
|
17
|
+
private tickersInflight;
|
|
18
|
+
constructor(opts?: HyperliquidPublicApiOptions);
|
|
19
|
+
private baseUrl;
|
|
20
|
+
/** Canonical/ccxt symbol → this venue's ccxt symbol, or null (logged) when
|
|
21
|
+
* the symbol isn't a Hyperliquid USDC perp — a caller bug we surface
|
|
22
|
+
* loudly rather than silently translating quote assets. */
|
|
23
|
+
private venueSymbol;
|
|
24
|
+
/** Fetch-all-tickers with a short TTL + inflight dedup. Returns a map keyed
|
|
25
|
+
* by ccxt symbol, or null on failure. */
|
|
26
|
+
private getTickers;
|
|
27
|
+
fetchTickerRaw(symbol: string): Promise<Record<string, any> | null>;
|
|
28
|
+
/** Ticker from the cached all-assets snapshot. Hyperliquid's asset contexts
|
|
29
|
+
* carry mark/mid rather than a trade-tape bid/ask; absent fields fall back
|
|
30
|
+
* to `last` with zero modeled spread — the paper fill engine models
|
|
31
|
+
* slippage itself (same convention as IntelPublicApi). */
|
|
32
|
+
fetchTicker(symbol: string): Promise<CcxtTicker | null>;
|
|
33
|
+
fetchFundingRate(symbol: string): Promise<Record<string, any> | null>;
|
|
34
|
+
fetchOpenInterest(symbol: string): Promise<Record<string, any> | null>;
|
|
35
|
+
/** l2Book — Hyperliquid serves at most 20 levels/side (plan §3.6). */
|
|
36
|
+
fetchOrderBook(symbol: string, limit?: number): Promise<OrderBookDepth | null>;
|
|
37
|
+
/** candleSnapshot — only the most recent 5000 candles exist per (coin,
|
|
38
|
+
* interval) (plan §3.6); requests inside that window behave like Binance. */
|
|
39
|
+
fetchOHLCV(symbol: string, timeframe?: string, limit?: number): Promise<CcxtOHLCV[] | null>;
|
|
40
|
+
/** Probe Hyperliquid reachability from this host — the readiness gate's
|
|
41
|
+
* venue signal, mirroring BinancePublicApi.probeReachability's outcome
|
|
42
|
+
* shape. Hand-rolled POST /info `exchangeStatus` (weight 2) so the probe
|
|
43
|
+
* has zero ccxt-method-shape dependence; the response's `time` field
|
|
44
|
+
* (verified live 2026-07-11: `{"specialStatuses":null,"time":…}`) doubles
|
|
45
|
+
* as the clock-drift source. Geo classification is best-effort — HL's
|
|
46
|
+
* API-level geo behavior is UNVERIFIED (plan §3.9); a 403/451 maps to
|
|
47
|
+
* geo_blocked, anything else non-2xx/network maps to unreachable. */
|
|
48
|
+
probeReachability(): Promise<{
|
|
49
|
+
outcome: 'reachable' | 'geo_blocked' | 'unreachable' | 'unknown';
|
|
50
|
+
driftMs: number | null;
|
|
51
|
+
}>;
|
|
52
|
+
}
|