@reefclaw/openclaw-plugin 0.1.6 → 0.1.8

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.
Files changed (56) hide show
  1. package/bridge/gateway/event-parser.d.ts +6 -1
  2. package/bridge/gateway/event-parser.js +19 -2
  3. package/bridge/gateway/poller.d.ts +1 -0
  4. package/bridge/gateway/poller.js +14 -2
  5. package/bridge/providers/gateway.d.ts +22 -2
  6. package/bridge/providers/gateway.js +67 -9
  7. package/ccxt/public-market-data-api.d.ts +14 -0
  8. package/ccxt/public-market-data-api.js +15 -1
  9. package/config/plugin-config-io.d.ts +7 -0
  10. package/config/plugin-config-io.js +15 -0
  11. package/index.js +116 -31
  12. package/ingest/position-auto-capture.d.ts +68 -0
  13. package/ingest/position-auto-capture.js +321 -23
  14. package/ingest/position-decisions-client.d.ts +7 -2
  15. package/ingest/position-decisions-client.js +13 -3
  16. package/ingest/reconcile-db-vs-exchange.d.ts +39 -1
  17. package/ingest/reconcile-db-vs-exchange.js +66 -10
  18. package/live/fill-price.d.ts +13 -0
  19. package/live/fill-price.js +37 -0
  20. package/live/live-adapter.d.ts +33 -1
  21. package/live/live-adapter.js +176 -47
  22. package/live/position-state-store.d.ts +4 -0
  23. package/live/stop-watcher.d.ts +8 -1
  24. package/live/stop-watcher.js +5 -2
  25. package/onboarding/runtime.d.ts +18 -0
  26. package/onboarding/runtime.js +45 -3
  27. package/openclaw.plugin.json +1 -1
  28. package/package.json +2 -2
  29. package/portfolio/reentry-tracker.d.ts +36 -0
  30. package/portfolio/reentry-tracker.js +127 -0
  31. package/scripts/assemble.mjs +18 -2
  32. package/signals/conditions/registry.js +11 -2
  33. package/signals/strategy-adapter.js +17 -7
  34. package/simulator/exchange-simulator.d.ts +12 -0
  35. package/simulator/exchange-simulator.js +73 -3
  36. package/simulator/types.d.ts +4 -0
  37. package/skills/reefclaw/SKILL.md +2 -0
  38. package/tools/assessment-validation.d.ts +21 -0
  39. package/tools/assessment-validation.js +58 -0
  40. package/tools/attach-brackets.js +165 -0
  41. package/tools/audit-bracket-protection.js +157 -1
  42. package/tools/bracket-control.d.ts +12 -0
  43. package/tools/bracket-control.js +35 -0
  44. package/tools/create-order.d.ts +7 -0
  45. package/tools/create-order.js +42 -3
  46. package/tools/get-setup-detail.js +12 -1
  47. package/tools/modify-stop.js +5 -5
  48. package/tools/modify-target.js +5 -5
  49. package/tools/scan-pairs.d.ts +4 -0
  50. package/tools/scan-pairs.js +4 -1
  51. package/tools/set-trading-mode.js +23 -6
  52. package/venues/hyperliquid/hl-bracket-coordinator.d.ts +123 -0
  53. package/venues/hyperliquid/hl-bracket-coordinator.js +533 -0
  54. package/venues/hyperliquid/hl-live-adapter.d.ts +61 -3
  55. package/venues/hyperliquid/hl-live-adapter.js +380 -5
  56. package/venues/hyperliquid/hl-public.js +8 -1
@@ -9,6 +9,7 @@
9
9
  // because the caller chain is currently controlled end-to-end by the skill.
10
10
  import { readPluginConfig } from '../config/plugin-config-io.js';
11
11
  import { validateModeTransition, modeRequiresCredentials } from '../onboarding/mode-ladder.js';
12
+ import { parseVenue } from '../venues/registry.js';
12
13
  import { logger } from '../logger.js';
13
14
  import { recordModeTransition } from '../audit/mode-transition-audit.js';
14
15
  const TAG = 'set-trading-mode';
@@ -49,12 +50,26 @@ export async function setTradingModeTool(args, deps) {
49
50
  readiness: deps.runtime.adapter.readiness,
50
51
  };
51
52
  }
52
- // 3. Credential requirement check.
53
+ // 3. Credential requirement check — PER VENUE (issue #217: this gate was
54
+ // Binance-only, so the go-live flip on the hyperliquid venue was rejected
55
+ // despite valid walletAddress+agentPrivateKey in plugin-config).
53
56
  let exchange = null;
57
+ let hlCredentials = null;
58
+ let venue = 'binance';
54
59
  if (modeRequiresCredentials(target)) {
55
60
  try {
56
61
  const cfg = readPluginConfig(deps.configPath);
57
- if (cfg.exchange?.apiKey && cfg.exchange?.secret) {
62
+ venue = parseVenue(cfg.exchange?.venue).venue;
63
+ if (venue === 'hyperliquid') {
64
+ if (cfg.exchange?.walletAddress && cfg.exchange?.agentPrivateKey) {
65
+ hlCredentials = {
66
+ walletAddress: String(cfg.exchange.walletAddress),
67
+ agentPrivateKey: String(cfg.exchange.agentPrivateKey),
68
+ testnet: cfg.exchange.testnet === true,
69
+ };
70
+ }
71
+ }
72
+ else if (cfg.exchange?.apiKey && cfg.exchange?.secret) {
58
73
  exchange = {
59
74
  apiKey: cfg.exchange.apiKey,
60
75
  secret: cfg.exchange.secret,
@@ -74,11 +89,13 @@ export async function setTradingModeTool(args, deps) {
74
89
  reason: 'config_read_error',
75
90
  };
76
91
  }
77
- if (!exchange) {
92
+ if (!exchange && !hlCredentials) {
78
93
  recordModeTransition({ previousMode, targetMode: target, acknowledged, ok: false, reason: 'missing_credentials' });
79
94
  return {
80
95
  ok: false,
81
- message: `${target} mode requires exchange credentials. Run set_exchange_credentials first.`,
96
+ message: venue === 'hyperliquid'
97
+ ? `${target} mode on Hyperliquid requires exchange.walletAddress (master) + exchange.agentPrivateKey (agent wallet) in plugin-config.`
98
+ : `${target} mode requires exchange credentials. Run set_exchange_credentials first.`,
82
99
  previousMode,
83
100
  mode: previousMode,
84
101
  readiness: deps.runtime.adapter.readiness,
@@ -116,14 +133,14 @@ export async function setTradingModeTool(args, deps) {
116
133
  };
117
134
  }
118
135
  // 6. Swap the adapter.
119
- await deps.runtime.reconnect({ mode: target, exchange }, { adapterDeps: deps.adapterDeps });
136
+ await deps.runtime.reconnect({ mode: target, exchange, venue, hlCredentials }, { adapterDeps: deps.adapterDeps });
120
137
  logger.info(TAG, `Transitioned ${previousMode} → ${target}`);
121
138
  recordModeTransition({
122
139
  previousMode,
123
140
  targetMode: target,
124
141
  acknowledged,
125
142
  ok: true,
126
- testnet: exchange?.testnet,
143
+ testnet: exchange?.testnet ?? hlCredentials?.testnet,
127
144
  });
128
145
  return {
129
146
  ok: true,
@@ -0,0 +1,123 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { CcxtOrder, CcxtPosition } from '../../types.js';
3
+ import type { CloseReason } from '../../simulator/types.js';
4
+ import type { BracketId, BracketRequest, BracketState } from '../../live/bracket-types.js';
5
+ import type { BracketLedger } from '../../live/bracket-ledger.js';
6
+ import type { HlOrderUpdateEvent } from './hl-user-stream.js';
7
+ /** Narrow execution surface the coordinator needs — the HyperliquidLiveAdapter
8
+ * implements it; tests mock it without a network. */
9
+ export interface HlBracketExecutor {
10
+ attachBrackets(args: {
11
+ bracketId: BracketId;
12
+ symbol: string;
13
+ positionSide: 'long' | 'short';
14
+ positionSize: number;
15
+ stopPrice?: number;
16
+ targetPrice?: number;
17
+ }): Promise<{
18
+ orders: CcxtOrder[];
19
+ slCid?: string;
20
+ tpCid?: string;
21
+ }>;
22
+ resizeBrackets(args: {
23
+ bracketId: BracketId;
24
+ symbol: string;
25
+ positionSide: 'long' | 'short';
26
+ positionSize: number;
27
+ }): Promise<{
28
+ resized: boolean;
29
+ slCid?: string;
30
+ tpCid?: string;
31
+ }>;
32
+ /** Cancel one leg by cloid. Idempotent — "already gone" must not throw. */
33
+ cancelBracketLeg(cloid: string, symbol: string): Promise<void>;
34
+ /** Coverage audit — THROWS when order state is unknown (null ≠ empty). */
35
+ auditBracketCoverage(symbol: string, positionSize: number): Promise<{
36
+ covered: boolean;
37
+ shortfall: {
38
+ stop: number;
39
+ target: number;
40
+ };
41
+ missing: ('stop' | 'target')[];
42
+ }>;
43
+ getPositionsOrNull(symbol?: string): Promise<CcxtPosition[] | null>;
44
+ closePosition(symbol: string, closeReason?: CloseReason): Promise<CcxtOrder>;
45
+ }
46
+ export interface HlAttachResult {
47
+ ok: boolean;
48
+ latencyMs: number;
49
+ slCid?: string;
50
+ tpCid?: string;
51
+ error?: string;
52
+ attempts: number;
53
+ }
54
+ export interface HlCoordinatorOpts {
55
+ maxAttempts?: number;
56
+ retryBackoffMs?: (attempt: number) => number;
57
+ now?: () => number;
58
+ sleep?: (ms: number) => Promise<void>;
59
+ }
60
+ export declare function isTerminalBracketState(state: BracketState): boolean;
61
+ export declare class HlBracketCoordinator extends EventEmitter {
62
+ private readonly executor;
63
+ private readonly ledger;
64
+ private readonly maxAttempts;
65
+ private readonly backoff;
66
+ private readonly now;
67
+ private readonly sleep;
68
+ /** Per-symbol in-flight guard so a WS fill + the createOrder return path
69
+ * can't both run attachOnFill concurrently (double-submit). */
70
+ private readonly inFlight;
71
+ /** Cloids WE deliberately cancelled (resize/modify supersede, retry cleanup,
72
+ * cancelBrackets). Their WS 'canceled' events are OUR OWN and must never
73
+ * trip the stripped-protection canary — the T-8 testnet run proved the WS
74
+ * event can arrive BEFORE the ledger's fresh-cid update lands, so a
75
+ * generation check alone loses the race. Entries pruned after 5 min. */
76
+ private readonly ownCancels;
77
+ constructor(executor: HlBracketExecutor, ledger: BracketLedger, opts?: HlCoordinatorOpts);
78
+ getLedger(): BracketLedger;
79
+ /** Record a leg cancel WE initiated (see `ownCancels`). The adapter calls
80
+ * this for every deliberate leg cancel, including the resize path's direct
81
+ * api cancels, BEFORE the cancel request goes out. */
82
+ noteOwnLegCancel(cloid: string): void;
83
+ /** Same anti-clobber contract as BracketManager.registerEntry: a duplicate
84
+ * fill signal against a non-terminal row is a warned NO-OP, never a fresh
85
+ * bracketId that orphans the live legs' ledger identity. */
86
+ registerEntry(req: BracketRequest, bracketId: BracketId, entryCid: string): void;
87
+ /** Attach both legs (ONE batched signed action) with retries. Idempotent on
88
+ * a non-pending row. On exhaustion: ledger 'failed' + attach_failed event —
89
+ * the ADAPTER escalates to auto-flatten (it owns closePosition). */
90
+ attachOnFill(symbol: string, filledSize: number): Promise<HlAttachResult>;
91
+ /** ★ T-2: bring the legs to the CURRENT position size (scale-in / partial
92
+ * fill growth). Submit-first-then-cancel inside the adapter. Updates the
93
+ * ledger's qty + fresh cids on success. */
94
+ resizeToPosition(symbol: string, newPositionSize: number): Promise<{
95
+ resized: boolean;
96
+ }>;
97
+ /** Move the stop. HL ordering: submit the NEW leg first, then cancel the old
98
+ * one — over-protection for a moment beats a naked window (see header). */
99
+ modifyStop(symbol: string, newStopPrice: number): Promise<void>;
100
+ modifyTarget(symbol: string, newTargetPrice: number): Promise<void>;
101
+ private modifyLeg;
102
+ /** Cancel both legs, mark cancelled. Risk-reducing; best-effort per leg. */
103
+ cancelBrackets(symbol: string, reason: string): Promise<void>;
104
+ /**
105
+ * The HL analog of the Binance ALGO_UPDATE handler — `orderUpdates` is
106
+ * authoritative for bracket-leg lifecycle. Returns the transition applied
107
+ * (for the adapter to emit drift/close-bypass signals on triggers).
108
+ */
109
+ handleOrderUpdate(update: HlOrderUpdateEvent): 'triggered_sl' | 'triggered_tp' | 'forced_close' | null;
110
+ /**
111
+ * ★ T-5 mandate: REST truth-check. Runs after every WS (re)connect AND on the
112
+ * periodic sweep. TRUSTED reads only — a null positions fetch skips the pass
113
+ * (never act on unknown). Self-heals: pending rows whose entry filled while
114
+ * we were blind get their legs attached; undersized legs get resized; flat
115
+ * positions get their rows closed out (T-1 already cancelled the legs).
116
+ * Returns symbols whose rows were closed out (close-bypass signals for the
117
+ * adapter's drift pipeline).
118
+ */
119
+ resyncAgainstExchange(reasonTag: string): Promise<{
120
+ closedSymbols: string[];
121
+ }>;
122
+ private emitEvent;
123
+ }