@reefclaw/openclaw-plugin 0.1.0
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/.gitignore +8 -0
- package/audit/mode-transition-audit.d.ts +11 -0
- package/audit/mode-transition-audit.js +29 -0
- package/balance-utils.d.ts +36 -0
- package/balance-utils.js +98 -0
- package/bridge/bridge.d.ts +109 -0
- package/bridge/bridge.js +1036 -0
- package/bridge/config.d.ts +43 -0
- package/bridge/config.js +176 -0
- package/bridge/connector.d.ts +52 -0
- package/bridge/connector.js +293 -0
- package/bridge/event-replay-buffer.d.ts +43 -0
- package/bridge/event-replay-buffer.js +109 -0
- package/bridge/gateway/event-parser.d.ts +209 -0
- package/bridge/gateway/event-parser.js +793 -0
- package/bridge/gateway/gateway-config.d.ts +39 -0
- package/bridge/gateway/gateway-config.js +86 -0
- package/bridge/gateway/gateway-http-client.d.ts +50 -0
- package/bridge/gateway/gateway-http-client.js +165 -0
- package/bridge/gateway/gateway-ws-client.d.ts +116 -0
- package/bridge/gateway/gateway-ws-client.js +417 -0
- package/bridge/gateway/poller.d.ts +146 -0
- package/bridge/gateway/poller.js +505 -0
- package/bridge/gateway/tool-discovery.d.ts +25 -0
- package/bridge/gateway/tool-discovery.js +198 -0
- package/bridge/index.d.ts +2 -0
- package/bridge/index.js +253 -0
- package/bridge/logger.d.ts +2 -0
- package/bridge/logger.js +2 -0
- package/bridge/provider.d.ts +156 -0
- package/bridge/provider.js +2 -0
- package/bridge/providers/emergency-commands.d.ts +54 -0
- package/bridge/providers/emergency-commands.js +235 -0
- package/bridge/providers/gateway.d.ts +322 -0
- package/bridge/providers/gateway.js +2302 -0
- package/bridge/providers/mock.d.ts +37 -0
- package/bridge/providers/mock.js +385 -0
- package/bridge/providers/onboarding-commands.d.ts +83 -0
- package/bridge/providers/onboarding-commands.js +213 -0
- package/bridge/providers/risk-calculator.d.ts +96 -0
- package/bridge/providers/risk-calculator.js +369 -0
- package/bridge/setup.d.ts +32 -0
- package/bridge/setup.js +226 -0
- package/bridge/types.d.ts +584 -0
- package/bridge/types.js +50 -0
- package/bridge/utils/reconnect.d.ts +6 -0
- package/bridge/utils/reconnect.js +6 -0
- package/bridge/utils/skill-signing.d.ts +51 -0
- package/bridge/utils/skill-signing.js +138 -0
- package/bridge/utils/skill-version.d.ts +17 -0
- package/bridge/utils/skill-version.js +74 -0
- package/ccxt/binance-ban-gate.d.ts +47 -0
- package/ccxt/binance-ban-gate.js +409 -0
- package/ccxt/binance-private.d.ts +325 -0
- package/ccxt/binance-private.js +1415 -0
- package/ccxt/binance-public.d.ts +18 -0
- package/ccxt/binance-public.js +147 -0
- package/config/agent-config-client.d.ts +55 -0
- package/config/agent-config-client.js +145 -0
- package/config/agent-config-poller.d.ts +25 -0
- package/config/agent-config-poller.js +100 -0
- package/config/brackets-config.d.ts +22 -0
- package/config/brackets-config.js +58 -0
- package/config/gate-store.d.ts +18 -0
- package/config/gate-store.js +61 -0
- package/config/plugin-config-io.d.ts +181 -0
- package/config/plugin-config-io.js +84 -0
- package/config/position-review-config.d.ts +35 -0
- package/config/position-review-config.js +105 -0
- package/config/tool-gate.d.ts +53 -0
- package/config/tool-gate.js +125 -0
- package/config/user-data-stream-config.d.ts +85 -0
- package/config/user-data-stream-config.js +224 -0
- package/connector-supervisor.d.ts +36 -0
- package/connector-supervisor.js +149 -0
- package/exchange-adapter.d.ts +49 -0
- package/exchange-adapter.js +4 -0
- package/index.d.ts +30 -0
- package/index.js +2030 -0
- package/ingest/pending-entry-metadata.d.ts +52 -0
- package/ingest/pending-entry-metadata.js +182 -0
- package/ingest/position-auto-capture.d.ts +98 -0
- package/ingest/position-auto-capture.js +394 -0
- package/ingest/position-decisions-client.d.ts +318 -0
- package/ingest/position-decisions-client.js +296 -0
- package/ingest/reconcile-db-vs-exchange.d.ts +13 -0
- package/ingest/reconcile-db-vs-exchange.js +114 -0
- package/ingest/reconciler-cleanup.d.ts +37 -0
- package/ingest/reconciler-cleanup.js +147 -0
- package/ingest/rest-gap-filler.d.ts +191 -0
- package/ingest/rest-gap-filler.js +565 -0
- package/ingest/touched-symbols-store.d.ts +25 -0
- package/ingest/touched-symbols-store.js +96 -0
- package/ingest/trade-store-client.d.ts +40 -0
- package/ingest/trade-store-client.js +125 -0
- package/ingest/ws-ingest.d.ts +43 -0
- package/ingest/ws-ingest.js +126 -0
- package/learning/setup-family.d.ts +21 -0
- package/learning/setup-family.js +103 -0
- package/lifecycle/install-signal-handlers.d.ts +33 -0
- package/lifecycle/install-signal-handlers.js +112 -0
- package/lifecycle/shutdown-coordinator.d.ts +43 -0
- package/lifecycle/shutdown-coordinator.js +131 -0
- package/live/bracket-id.d.ts +18 -0
- package/live/bracket-id.js +81 -0
- package/live/bracket-ledger.d.ts +54 -0
- package/live/bracket-ledger.js +267 -0
- package/live/bracket-manager.d.ts +82 -0
- package/live/bracket-manager.js +478 -0
- package/live/bracket-params.d.ts +22 -0
- package/live/bracket-params.js +124 -0
- package/live/bracket-reconciler.d.ts +95 -0
- package/live/bracket-reconciler.js +573 -0
- package/live/bracket-types.d.ts +102 -0
- package/live/bracket-types.js +8 -0
- package/live/deposit-tracker.d.ts +62 -0
- package/live/deposit-tracker.js +97 -0
- package/live/emergency-controls.d.ts +32 -0
- package/live/emergency-controls.js +226 -0
- package/live/exchange-errors.d.ts +12 -0
- package/live/exchange-errors.js +130 -0
- package/live/exchange-info-cache.d.ts +35 -0
- package/live/exchange-info-cache.js +119 -0
- package/live/fact-subscriber.d.ts +78 -0
- package/live/fact-subscriber.js +182 -0
- package/live/intent-journal.d.ts +42 -0
- package/live/intent-journal.js +122 -0
- package/live/listen-key-manager.d.ts +70 -0
- package/live/listen-key-manager.js +169 -0
- package/live/live-adapter.d.ts +264 -0
- package/live/live-adapter.js +1665 -0
- package/live/live-balance-enricher.d.ts +32 -0
- package/live/live-balance-enricher.js +104 -0
- package/live/live-bracket-api.d.ts +13 -0
- package/live/live-bracket-api.js +20 -0
- package/live/live-state-store.d.ts +194 -0
- package/live/live-state-store.js +450 -0
- package/live/local-signal-service.d.ts +57 -0
- package/live/local-signal-service.js +146 -0
- package/live/local-strategy-evaluator.d.ts +62 -0
- package/live/local-strategy-evaluator.js +127 -0
- package/live/microstructure-assembler.d.ts +54 -0
- package/live/microstructure-assembler.js +148 -0
- package/live/order-poller.d.ts +29 -0
- package/live/order-poller.js +125 -0
- package/live/position-state-store.d.ts +83 -0
- package/live/position-state-store.js +237 -0
- package/live/proposal-decision-listener.d.ts +64 -0
- package/live/proposal-decision-listener.js +288 -0
- package/live/proposal-manager.d.ts +76 -0
- package/live/proposal-manager.js +140 -0
- package/live/rate-limiter.d.ts +47 -0
- package/live/rate-limiter.js +159 -0
- package/live/reconciler.d.ts +39 -0
- package/live/reconciler.js +175 -0
- package/live/setup-buckets.d.ts +7 -0
- package/live/setup-buckets.js +33 -0
- package/live/slippage-tracker.d.ts +45 -0
- package/live/slippage-tracker.js +78 -0
- package/live/stop-watcher.d.ts +34 -0
- package/live/stop-watcher.js +158 -0
- package/live/user-data-active-probe.d.ts +54 -0
- package/live/user-data-active-probe.js +180 -0
- package/live/user-data-stream-controller.d.ts +200 -0
- package/live/user-data-stream-controller.js +579 -0
- package/live/user-data-stream-ws.d.ts +22 -0
- package/live/user-data-stream-ws.js +63 -0
- package/live/user-data-stream.d.ts +243 -0
- package/live/user-data-stream.js +704 -0
- package/logger.d.ts +2 -0
- package/logger.js +2 -0
- package/mfe.d.ts +21 -0
- package/mfe.js +68 -0
- package/onboarding/mode-ladder.d.ts +1 -0
- package/onboarding/mode-ladder.js +3 -0
- package/onboarding/runtime.d.ts +71 -0
- package/onboarding/runtime.js +153 -0
- package/openclaw.plugin.json +94 -0
- package/package.json +27 -0
- package/paper-adapter.d.ts +24 -0
- package/paper-adapter.js +91 -0
- package/persistence/state-manager.d.ts +42 -0
- package/persistence/state-manager.js +164 -0
- package/pinned-plan.d.ts +9 -0
- package/pinned-plan.js +23 -0
- package/risk/pre-trade-check.d.ts +38 -0
- package/risk/pre-trade-check.js +345 -0
- package/risk/pre-trade-types.d.ts +60 -0
- package/risk/pre-trade-types.js +3 -0
- package/scripts/assemble.mjs +114 -0
- package/shadow/shadow-tracker.d.ts +36 -0
- package/shadow/shadow-tracker.js +151 -0
- package/shadow/types.d.ts +42 -0
- package/shadow/types.js +20 -0
- package/shared/indicators-extended.d.ts +52 -0
- package/shared/indicators-extended.js +291 -0
- package/shared/indicators.d.ts +15 -0
- package/shared/indicators.js +114 -0
- package/signals/conditions/registry.d.ts +16 -0
- package/signals/conditions/registry.js +1274 -0
- package/signals/conditions/types.d.ts +1 -0
- package/signals/conditions/types.js +4 -0
- package/signals/direction-rules.d.ts +3 -0
- package/signals/direction-rules.js +24 -0
- package/signals/entry-rules.d.ts +6 -0
- package/signals/entry-rules.js +33 -0
- package/signals/serialize-context.d.ts +4 -0
- package/signals/serialize-context.js +39 -0
- package/signals/stop-rules.d.ts +3 -0
- package/signals/stop-rules.js +48 -0
- package/signals/strategy-adapter.d.ts +14 -0
- package/signals/strategy-adapter.js +122 -0
- package/signals/types.d.ts +1 -0
- package/signals/types.js +8 -0
- package/simulator/exchange-simulator.d.ts +93 -0
- package/simulator/exchange-simulator.js +684 -0
- package/simulator/fill-engine.d.ts +53 -0
- package/simulator/fill-engine.js +276 -0
- package/simulator/paper-market-feed.d.ts +26 -0
- package/simulator/paper-market-feed.js +104 -0
- package/simulator/realistic-fills.d.ts +59 -0
- package/simulator/realistic-fills.js +175 -0
- package/simulator/types.d.ts +219 -0
- package/simulator/types.js +43 -0
- package/skills/reefclaw/SKILL.md +97 -0
- package/strategy/builtin-strategies.d.ts +2 -0
- package/strategy/builtin-strategies.js +109 -0
- package/strategy/condition-registry.d.ts +3 -0
- package/strategy/condition-registry.js +153 -0
- package/strategy/evaluator.d.ts +67 -0
- package/strategy/evaluator.js +93 -0
- package/tools/assessment-validation.d.ts +118 -0
- package/tools/assessment-validation.js +415 -0
- package/tools/attach-brackets.d.ts +34 -0
- package/tools/attach-brackets.js +363 -0
- package/tools/audit-bracket-protection.d.ts +49 -0
- package/tools/audit-bracket-protection.js +527 -0
- package/tools/cancel-all-orders.d.ts +7 -0
- package/tools/cancel-all-orders.js +5 -0
- package/tools/cancel-order.d.ts +10 -0
- package/tools/cancel-order.js +14 -0
- package/tools/check-position-health.d.ts +46 -0
- package/tools/check-position-health.js +194 -0
- package/tools/clear-exchange-credentials.d.ts +24 -0
- package/tools/clear-exchange-credentials.js +70 -0
- package/tools/close-position.d.ts +22 -0
- package/tools/close-position.js +449 -0
- package/tools/create-order.d.ts +54 -0
- package/tools/create-order.js +338 -0
- package/tools/exit-gate.d.ts +58 -0
- package/tools/exit-gate.js +162 -0
- package/tools/fetch-balance.d.ts +5 -0
- package/tools/fetch-balance.js +4 -0
- package/tools/fetch-ohlcv.d.ts +11 -0
- package/tools/fetch-ohlcv.js +8 -0
- package/tools/fetch-open-orders.d.ts +7 -0
- package/tools/fetch-open-orders.js +4 -0
- package/tools/fetch-positions.d.ts +7 -0
- package/tools/fetch-positions.js +4 -0
- package/tools/fetch-ticker.d.ts +11 -0
- package/tools/fetch-ticker.js +5 -0
- package/tools/get-agent-profile.d.ts +4 -0
- package/tools/get-agent-profile.js +6 -0
- package/tools/get-analytics.d.ts +6 -0
- package/tools/get-analytics.js +7 -0
- package/tools/get-backtest.d.ts +12 -0
- package/tools/get-backtest.js +91 -0
- package/tools/get-basis.d.ts +7 -0
- package/tools/get-basis.js +7 -0
- package/tools/get-bracket-config.d.ts +11 -0
- package/tools/get-bracket-config.js +24 -0
- package/tools/get-cascade-risk.d.ts +7 -0
- package/tools/get-cascade-risk.js +8 -0
- package/tools/get-crypto-metrics.d.ts +18 -0
- package/tools/get-crypto-metrics.js +45 -0
- package/tools/get-cvd.d.ts +6 -0
- package/tools/get-cvd.js +6 -0
- package/tools/get-divergences.d.ts +6 -0
- package/tools/get-divergences.js +6 -0
- package/tools/get-funding-context.d.ts +6 -0
- package/tools/get-funding-context.js +16 -0
- package/tools/get-liquidation-levels.d.ts +7 -0
- package/tools/get-liquidation-levels.js +7 -0
- package/tools/get-liquidation-pulse.d.ts +9 -0
- package/tools/get-liquidation-pulse.js +22 -0
- package/tools/get-market-breadth.d.ts +6 -0
- package/tools/get-market-breadth.js +8 -0
- package/tools/get-market-intel.d.ts +19 -0
- package/tools/get-market-intel.js +116 -0
- package/tools/get-market-structure.d.ts +47 -0
- package/tools/get-market-structure.js +198 -0
- package/tools/get-my-mined-patterns.d.ts +20 -0
- package/tools/get-my-mined-patterns.js +61 -0
- package/tools/get-my-proposed-learnings.d.ts +20 -0
- package/tools/get-my-proposed-learnings.js +55 -0
- package/tools/get-my-recent-reviews.d.ts +22 -0
- package/tools/get-my-recent-reviews.js +66 -0
- package/tools/get-orderbook.d.ts +21 -0
- package/tools/get-orderbook.js +32 -0
- package/tools/get-pattern-scan.d.ts +7 -0
- package/tools/get-pattern-scan.js +8 -0
- package/tools/get-regime.d.ts +6 -0
- package/tools/get-regime.js +7 -0
- package/tools/get-relevant-learnings.d.ts +21 -0
- package/tools/get-relevant-learnings.js +65 -0
- package/tools/get-resting-liquidity.d.ts +6 -0
- package/tools/get-resting-liquidity.js +11 -0
- package/tools/get-risk-scenario.d.ts +29 -0
- package/tools/get-risk-scenario.js +47 -0
- package/tools/get-risk-summary.d.ts +51 -0
- package/tools/get-risk-summary.js +118 -0
- package/tools/get-sentiment.d.ts +4 -0
- package/tools/get-sentiment.js +6 -0
- package/tools/get-session-review.d.ts +7 -0
- package/tools/get-session-review.js +8 -0
- package/tools/get-setup-detail.d.ts +7 -0
- package/tools/get-setup-detail.js +303 -0
- package/tools/get-signals.d.ts +15 -0
- package/tools/get-signals.js +54 -0
- package/tools/get-sizing.d.ts +6 -0
- package/tools/get-sizing.js +6 -0
- package/tools/get-trade-feedback.d.ts +7 -0
- package/tools/get-trade-feedback.js +8 -0
- package/tools/get-trade-flow.d.ts +7 -0
- package/tools/get-trade-flow.js +7 -0
- package/tools/get-volume-analysis.d.ts +21 -0
- package/tools/get-volume-analysis.js +74 -0
- package/tools/get-volume-profile.d.ts +7 -0
- package/tools/get-volume-profile.js +7 -0
- package/tools/helpers.d.ts +25 -0
- package/tools/helpers.js +38 -0
- package/tools/intel-api.d.ts +14 -0
- package/tools/intel-api.js +52 -0
- package/tools/intel-cache.d.ts +25 -0
- package/tools/intel-cache.js +133 -0
- package/tools/list-strategies.d.ts +7 -0
- package/tools/list-strategies.js +6 -0
- package/tools/modify-stop.d.ts +17 -0
- package/tools/modify-stop.js +81 -0
- package/tools/modify-target.d.ts +17 -0
- package/tools/modify-target.js +71 -0
- package/tools/propose-learning.d.ts +22 -0
- package/tools/propose-learning.js +65 -0
- package/tools/query-review-outcomes.d.ts +30 -0
- package/tools/query-review-outcomes.js +64 -0
- package/tools/query-trades.d.ts +21 -0
- package/tools/query-trades.js +37 -0
- package/tools/record-position-reviews.d.ts +38 -0
- package/tools/record-position-reviews.js +147 -0
- package/tools/save-strategy.d.ts +16 -0
- package/tools/save-strategy.js +46 -0
- package/tools/scan-pairs.d.ts +18 -0
- package/tools/scan-pairs.js +220 -0
- package/tools/score-setup.d.ts +31 -0
- package/tools/score-setup.js +268 -0
- package/tools/set-bracket-requirement.d.ts +18 -0
- package/tools/set-bracket-requirement.js +81 -0
- package/tools/set-exchange-credentials.d.ts +25 -0
- package/tools/set-exchange-credentials.js +80 -0
- package/tools/set-trading-mode.d.ts +26 -0
- package/tools/set-trading-mode.js +135 -0
- package/tools/test-exchange-credentials.d.ts +16 -0
- package/tools/test-exchange-credentials.js +100 -0
- package/tools/toggle-strategy.d.ts +8 -0
- package/tools/toggle-strategy.js +8 -0
- package/trading-params-cache.d.ts +26 -0
- package/trading-params-cache.js +52 -0
- package/types.d.ts +110 -0
- package/types.js +7 -0
- package/util/plugin-paths.d.ts +3 -0
- package/util/plugin-paths.js +15 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Wires SIGTERM / SIGINT to the ShutdownCoordinator.
|
|
2
|
+
//
|
|
3
|
+
// Lives in its own file (separate from index.ts) so the compiled dist/index.js
|
|
4
|
+
// — which reads env vars for the ingest-token / user-id — does not also contain
|
|
5
|
+
// the network-send call site. OpenClaw's plugin-install heuristic flags the
|
|
6
|
+
// combination as possible credential harvesting (false positive, but worth
|
|
7
|
+
// avoiding by keeping each compiled file single-purpose).
|
|
8
|
+
//
|
|
9
|
+
// All env-var-bearing context is passed in by the caller via the typed
|
|
10
|
+
// argument shape, NOT read from process.env here.
|
|
11
|
+
import { LiveAdapter } from '../live/live-adapter.js';
|
|
12
|
+
import { logger, formatError } from '../logger.js';
|
|
13
|
+
import { initiateShutdown } from './shutdown-coordinator.js';
|
|
14
|
+
const TAG = 'shutdown';
|
|
15
|
+
/** The phase-2 drain body. Exported for direct unit-testing of the teardown
|
|
16
|
+
* ordering + the StateManager flush wiring (the signal handler itself can't
|
|
17
|
+
* be tested without process.exit). Every step is individually guarded so one
|
|
18
|
+
* failing teardown never aborts the rest. */
|
|
19
|
+
export async function performShutdownDrain(deps) {
|
|
20
|
+
// Stop the approval-mode listener BEFORE the adapter shuts down.
|
|
21
|
+
// The listener's tick may call createOrderTool which needs the
|
|
22
|
+
// adapter; once stop() resolves, no new tick will fire.
|
|
23
|
+
if (deps.proposalListener) {
|
|
24
|
+
try {
|
|
25
|
+
await deps.proposalListener.stop();
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
logger.warn(TAG, `proposalListener stop failed: ${formatError(err)}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Flush any pending audit-trail POSTs.
|
|
32
|
+
if (deps.tradeStore) {
|
|
33
|
+
try {
|
|
34
|
+
await deps.tradeStore.drain();
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
logger.warn(TAG, `tradeStore drain failed: ${formatError(err)}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Stop background loops + close WS + DELETE listenKey.
|
|
41
|
+
const live = deps.runtime.adapter;
|
|
42
|
+
if (live instanceof LiveAdapter) {
|
|
43
|
+
try {
|
|
44
|
+
live.shutdown();
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
logger.warn(TAG, `LiveAdapter.shutdown failed: ${formatError(err)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Stop the watcher last so it doesn't fire a stop after the
|
|
51
|
+
// adapter is gone.
|
|
52
|
+
const watcher = deps.runtime.stopWatcher;
|
|
53
|
+
if (watcher) {
|
|
54
|
+
try {
|
|
55
|
+
watcher.stop();
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
logger.warn(TAG, `stopWatcher.stop failed: ${formatError(err)}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Stop the paper market feed so its interval doesn't outlive the process.
|
|
62
|
+
const marketFeed = deps.runtime.marketFeed;
|
|
63
|
+
if (marketFeed) {
|
|
64
|
+
try {
|
|
65
|
+
marketFeed.stop();
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
logger.warn(TAG, `marketFeed.stop failed: ${formatError(err)}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Flush the paper StateManager AFTER all mutation sources are stopped, so
|
|
72
|
+
// the 1s debounce window doesn't drop the final fills/closes/PnL on restart.
|
|
73
|
+
// flush() clears the timer and awaits the atomic save; completes in ms
|
|
74
|
+
// inside the drain budget. Without this, every restart silently lost up to
|
|
75
|
+
// ~1s of paper state (a just-closed position would resurrect on boot).
|
|
76
|
+
if (deps.stateManager) {
|
|
77
|
+
try {
|
|
78
|
+
await deps.stateManager.flush();
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
logger.warn(TAG, `stateManager flush failed: ${formatError(err)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** Register graceful-shutdown handlers. Idempotent — repeat signals during
|
|
86
|
+
* the drain are logged + ignored. After the coordinator resolves we call
|
|
87
|
+
* process.exit(0) so systemd doesn't escalate to SIGKILL. */
|
|
88
|
+
export function installSignalHandlers(deps) {
|
|
89
|
+
let invoked = false;
|
|
90
|
+
const onSignal = (signal) => {
|
|
91
|
+
if (invoked) {
|
|
92
|
+
logger.info(TAG, `Received ${signal} during shutdown — ignoring`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
invoked = true;
|
|
96
|
+
logger.info(TAG, `Received ${signal} — initiating graceful shutdown`);
|
|
97
|
+
void initiateShutdown({
|
|
98
|
+
reason: signal,
|
|
99
|
+
callbacks: {
|
|
100
|
+
onDrainComplete: () => performShutdownDrain(deps),
|
|
101
|
+
},
|
|
102
|
+
}).then(() => {
|
|
103
|
+
logger.info(TAG, 'Shutdown drain complete — exit(0)');
|
|
104
|
+
process.exit(0);
|
|
105
|
+
}).catch((err) => {
|
|
106
|
+
logger.error(TAG, `Shutdown threw: ${formatError(err)} — exit(1)`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
process.on('SIGTERM', onSignal);
|
|
111
|
+
process.on('SIGINT', onSignal);
|
|
112
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare class ShutdownInProgressError extends Error {
|
|
2
|
+
constructor(opName: string);
|
|
3
|
+
}
|
|
4
|
+
interface InFlightOp {
|
|
5
|
+
name: string;
|
|
6
|
+
startedAt: number;
|
|
7
|
+
}
|
|
8
|
+
/** True once SIGTERM/SIGINT has been received. New risky ops should be refused. */
|
|
9
|
+
export declare function isShuttingDown(): boolean;
|
|
10
|
+
/** Throw ShutdownInProgressError if shutdown has begun. Use as a guard at the
|
|
11
|
+
* top of risky entry points (new bracket attach, modify_stop, new order). */
|
|
12
|
+
export declare function assertNotShuttingDown(opName: string): void;
|
|
13
|
+
/** Register an in-flight operation. The returned function MUST be called when
|
|
14
|
+
* the op ends. Prefer `trackOp` for typical async cases. */
|
|
15
|
+
export declare function registerOp(name: string): () => void;
|
|
16
|
+
/** Convenience: register + auto-deregister around a promise. */
|
|
17
|
+
export declare function trackOp<T>(name: string, fn: () => Promise<T>): Promise<T>;
|
|
18
|
+
/** Snapshot of currently in-flight ops. Mainly for diagnostics + tests. */
|
|
19
|
+
export declare function getInFlight(): InFlightOp[];
|
|
20
|
+
export interface ShutdownCallbacks {
|
|
21
|
+
/** Run after in-flight ops drain (or phase-1 deadline hits). Last chance to
|
|
22
|
+
* flush the trade ingest queue and tear down adapters. Errors are logged
|
|
23
|
+
* + swallowed. */
|
|
24
|
+
onDrainComplete?: () => Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export interface ShutdownOptions {
|
|
27
|
+
reason: string;
|
|
28
|
+
/** Phase 1 budget — wait for tracked ops to settle. Default 3000 ms. */
|
|
29
|
+
inFlightDeadlineMs?: number;
|
|
30
|
+
/** Phase 2 budget — onDrainComplete callback. Default 2000 ms. */
|
|
31
|
+
drainCallbackDeadlineMs?: number;
|
|
32
|
+
/** Hard wall-clock deadline across both phases. Default 5000 ms. */
|
|
33
|
+
totalDeadlineMs?: number;
|
|
34
|
+
callbacks?: ShutdownCallbacks;
|
|
35
|
+
}
|
|
36
|
+
/** Run the drain sequence. Idempotent — second call returns the first call's
|
|
37
|
+
* promise. Resolves when both phases are complete or the wall-clock deadline
|
|
38
|
+
* is hit (whichever is first). The signal handler should `process.exit` after
|
|
39
|
+
* this resolves. */
|
|
40
|
+
export declare function initiateShutdown(opts: ShutdownOptions): Promise<void>;
|
|
41
|
+
/** Test seam — reset module-level state. Do NOT call from production. */
|
|
42
|
+
export declare function __resetForTesting(): void;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Process-wide graceful shutdown coordinator.
|
|
2
|
+
//
|
|
3
|
+
// Two responsibilities:
|
|
4
|
+
// 1. Track in-flight risky operations (bracket attach/modify, new entries)
|
|
5
|
+
// so a SIGTERM-driven shutdown can wait for them before exiting.
|
|
6
|
+
// 2. Block NEW risky operations from starting once shutdown begins, while
|
|
7
|
+
// still allowing risk-reducing ops (closePosition, reduceOnly) through.
|
|
8
|
+
//
|
|
9
|
+
// Why a global module instead of a constructor-injected service:
|
|
10
|
+
// - Many files own the at-risk operations (BracketManager, LiveAdapter).
|
|
11
|
+
// Threading a coordinator through every constructor is mechanical noise
|
|
12
|
+
// and the caller graph is fixed for this process's lifetime.
|
|
13
|
+
// - Process-wide state is a fine match for a singleton — one shutdown event
|
|
14
|
+
// per process, ever.
|
|
15
|
+
// - Tests reset the singleton via `__resetForTesting()`.
|
|
16
|
+
import { logger } from '../logger.js';
|
|
17
|
+
const TAG = 'shutdown-coordinator';
|
|
18
|
+
export class ShutdownInProgressError extends Error {
|
|
19
|
+
constructor(opName) {
|
|
20
|
+
super(`shutting_down: refused to start ${opName}`);
|
|
21
|
+
this.name = 'ShutdownInProgressError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
let _shuttingDown = false;
|
|
25
|
+
const _inFlight = new Map();
|
|
26
|
+
/** True once SIGTERM/SIGINT has been received. New risky ops should be refused. */
|
|
27
|
+
export function isShuttingDown() {
|
|
28
|
+
return _shuttingDown;
|
|
29
|
+
}
|
|
30
|
+
/** Throw ShutdownInProgressError if shutdown has begun. Use as a guard at the
|
|
31
|
+
* top of risky entry points (new bracket attach, modify_stop, new order). */
|
|
32
|
+
export function assertNotShuttingDown(opName) {
|
|
33
|
+
if (_shuttingDown) {
|
|
34
|
+
throw new ShutdownInProgressError(opName);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Register an in-flight operation. The returned function MUST be called when
|
|
38
|
+
* the op ends. Prefer `trackOp` for typical async cases. */
|
|
39
|
+
export function registerOp(name) {
|
|
40
|
+
const handle = Symbol(name);
|
|
41
|
+
_inFlight.set(handle, { name, startedAt: Date.now() });
|
|
42
|
+
return () => { _inFlight.delete(handle); };
|
|
43
|
+
}
|
|
44
|
+
/** Convenience: register + auto-deregister around a promise. */
|
|
45
|
+
export async function trackOp(name, fn) {
|
|
46
|
+
const done = registerOp(name);
|
|
47
|
+
try {
|
|
48
|
+
return await fn();
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
done();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Snapshot of currently in-flight ops. Mainly for diagnostics + tests. */
|
|
55
|
+
export function getInFlight() {
|
|
56
|
+
return Array.from(_inFlight.values()).map(o => ({ ...o }));
|
|
57
|
+
}
|
|
58
|
+
/** Run the drain sequence. Idempotent — second call returns the first call's
|
|
59
|
+
* promise. Resolves when both phases are complete or the wall-clock deadline
|
|
60
|
+
* is hit (whichever is first). The signal handler should `process.exit` after
|
|
61
|
+
* this resolves. */
|
|
62
|
+
export function initiateShutdown(opts) {
|
|
63
|
+
if (_shuttingDownPromise)
|
|
64
|
+
return _shuttingDownPromise;
|
|
65
|
+
_shuttingDown = true;
|
|
66
|
+
_shuttingDownPromise = runShutdown(opts);
|
|
67
|
+
return _shuttingDownPromise;
|
|
68
|
+
}
|
|
69
|
+
let _shuttingDownPromise = null;
|
|
70
|
+
async function runShutdown(opts) {
|
|
71
|
+
// Phase-1 default sized for the worst-case bracket retry loop:
|
|
72
|
+
// 3 attempts × ~500ms API + 1s + 2s backoff = ~4.5s. 6s leaves margin.
|
|
73
|
+
// Phase-2 default covers TradeStoreClient.drain (network-bounded) + listen
|
|
74
|
+
// key teardown (one DELETE).
|
|
75
|
+
// Total wall-clock 10s sits well inside systemd's default TimeoutStopSec.
|
|
76
|
+
const inFlightDeadline = opts.inFlightDeadlineMs ?? 6_000;
|
|
77
|
+
const callbackDeadline = opts.drainCallbackDeadlineMs ?? 3_000;
|
|
78
|
+
const totalDeadline = opts.totalDeadlineMs ?? 10_000;
|
|
79
|
+
const t0 = Date.now();
|
|
80
|
+
const remaining = () => Math.max(0, totalDeadline - (Date.now() - t0));
|
|
81
|
+
const opsAtStart = Array.from(_inFlight.values()).map(o => o.name);
|
|
82
|
+
logger.info(TAG, `Shutdown initiated (reason=${opts.reason}). In-flight ops: ${opsAtStart.length}` +
|
|
83
|
+
(opsAtStart.length ? ` [${opsAtStart.join(', ')}]` : ''));
|
|
84
|
+
// Phase 1: wait for in-flight ops to drain.
|
|
85
|
+
const phase1Budget = Math.min(inFlightDeadline, remaining());
|
|
86
|
+
await waitForInFlightDrain(phase1Budget);
|
|
87
|
+
if (_inFlight.size > 0) {
|
|
88
|
+
const stuckOps = Array.from(_inFlight.values()).map(o => o.name);
|
|
89
|
+
logger.warn(TAG, `Phase 1 timed out after ${phase1Budget}ms — ${_inFlight.size} op(s) still in flight: [${stuckOps.join(', ')}]`);
|
|
90
|
+
}
|
|
91
|
+
else if (opsAtStart.length > 0) {
|
|
92
|
+
logger.info(TAG, `Phase 1 complete: in-flight ops drained in ${Date.now() - t0}ms`);
|
|
93
|
+
}
|
|
94
|
+
// Phase 2: callback drain (trade-store + adapter).
|
|
95
|
+
if (opts.callbacks?.onDrainComplete) {
|
|
96
|
+
const phase2Budget = Math.min(callbackDeadline, remaining());
|
|
97
|
+
if (phase2Budget <= 0) {
|
|
98
|
+
logger.warn(TAG, 'Phase 2 skipped — total deadline exhausted by phase 1');
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
try {
|
|
102
|
+
await Promise.race([
|
|
103
|
+
opts.callbacks.onDrainComplete(),
|
|
104
|
+
new Promise(resolve => setTimeout(resolve, phase2Budget)),
|
|
105
|
+
]);
|
|
106
|
+
logger.info(TAG, `Phase 2 complete (total elapsed ${Date.now() - t0}ms)`);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
110
|
+
logger.warn(TAG, `Phase 2 callback threw: ${msg}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
logger.info(TAG, `Shutdown coordinator finished in ${Date.now() - t0}ms`);
|
|
115
|
+
}
|
|
116
|
+
async function waitForInFlightDrain(budgetMs) {
|
|
117
|
+
if (_inFlight.size === 0)
|
|
118
|
+
return;
|
|
119
|
+
const start = Date.now();
|
|
120
|
+
// Poll-based; in-flight ops settle through their own promise chains and
|
|
121
|
+
// unregister themselves. Cheaper than wiring an emitter just for this.
|
|
122
|
+
while (_inFlight.size > 0 && Date.now() - start < budgetMs) {
|
|
123
|
+
await new Promise(r => setTimeout(r, 50));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Test seam — reset module-level state. Do NOT call from production. */
|
|
127
|
+
export function __resetForTesting() {
|
|
128
|
+
_shuttingDown = false;
|
|
129
|
+
_shuttingDownPromise = null;
|
|
130
|
+
_inFlight.clear();
|
|
131
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { BracketId, BracketRole } from './bracket-types.js';
|
|
2
|
+
/** Generate a fresh 16-char hex bracketId. */
|
|
3
|
+
export declare function generateBracketId(): BracketId;
|
|
4
|
+
/** Build a clientOrderId for a given bracket + role. Emits the CURRENT
|
|
5
|
+
* (`bkt…`) scheme only — never the legacy `rc-` scheme. */
|
|
6
|
+
export declare function buildBracketCid(bracketId: BracketId, role: BracketRole): string;
|
|
7
|
+
/** Parse a clientOrderId. Recognises BOTH the current `bkt…` scheme and
|
|
8
|
+
* the legacy `rc-…` scheme (so existing ledger rows / in-flight legs and
|
|
9
|
+
* the cancelAllOrders bracket-preserve filter keep working through the
|
|
10
|
+
* transition). Returns null for non-bracket orders (so this can be called
|
|
11
|
+
* on every order Binance returns during reconciliation). */
|
|
12
|
+
export declare function parseBracketCid(cid: string): {
|
|
13
|
+
bracketId: BracketId;
|
|
14
|
+
role: BracketRole;
|
|
15
|
+
} | null;
|
|
16
|
+
/** Cheap type-guard for "is this a reefclaw-managed bracket clientOrderId"
|
|
17
|
+
* — true for both the current and legacy schemes. */
|
|
18
|
+
export declare function isBracketCid(cid: string): boolean;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Deterministic clientOrderId scheme for bracket orders.
|
|
2
|
+
//
|
|
3
|
+
// CURRENT format: bkt<16hex><role> (20 chars, well under Binance's 36)
|
|
4
|
+
// - "bkt" fixed prefix (no dashes — does NOT look like a tagged pair)
|
|
5
|
+
// - <16hex> shared bracketId linking entry + SL + TP of the same trade
|
|
6
|
+
// - <role> 'e' (entry), 's' (stop-loss), 't' (take-profit)
|
|
7
|
+
//
|
|
8
|
+
// LEGACY format (still PARSED, never EMITTED): rc-<16hex>-<role>
|
|
9
|
+
//
|
|
10
|
+
// ROOT-CAUSE FIX 2026-05-16. Controlled prod reproduction proved the
|
|
11
|
+
// recurring ~20-45s bracket cancellation is keyed to the `rc-` cid
|
|
12
|
+
// pattern: a byte-identical-shape NON-rc closePosition pair survived
|
|
13
|
+
// 30+ min on the SAME symbol/window where every `rc-<16hex>-{s,t}` pair
|
|
14
|
+
// died in ~42s, `reason=none`, server-side — with the 8-surface raw-ccxt
|
|
15
|
+
// WIDE-CANCEL-TRACE silent, the skill ccxt-less, the agent issuing zero
|
|
16
|
+
// tool/exec calls, and OpenClaw having no native exchange. i.e. an
|
|
17
|
+
// actor with Binance API access, OFF this VPS and outside our software,
|
|
18
|
+
// cancels orders whose clientOrderId matches the `rc-` bracket pattern.
|
|
19
|
+
// Renaming the scheme makes our protective brackets invisible to it —
|
|
20
|
+
// exactly why every `ctp-`/`opguard-`/`mctest-` test order survived.
|
|
21
|
+
//
|
|
22
|
+
// Backward compat is load-bearing: in-flight brackets + the persisted
|
|
23
|
+
// bracket-ledger.json carry LEGACY `rc-*` cids. parseBracketCid /
|
|
24
|
+
// isBracketCid MUST keep recognising them so the reconciler still tracks
|
|
25
|
+
// them and — critically — `cancelAllOrders`'s bracket-preserve filter
|
|
26
|
+
// never strips a legacy leg off a live position during the transition.
|
|
27
|
+
// Only buildBracketCid changes (emits the new scheme); legacy ages out.
|
|
28
|
+
//
|
|
29
|
+
// Parseability is load-bearing: on restart, the reconciler fetches open
|
|
30
|
+
// orders from Binance, parses each clientOrderId, and reassembles brackets
|
|
31
|
+
// from the (bracketId, role) pairs. No reliance on local state to find them.
|
|
32
|
+
//
|
|
33
|
+
// Binance USDⓂ-Futures newClientOrderId charset is documented as
|
|
34
|
+
// ^[\.A-Z\:/a-z0-9_-]{1,36}$ — `bkt[0-9a-f]{16}[est]` (20 chars, lowercase
|
|
35
|
+
// alphanumeric) is well within it. Verified vs developers.binance.com
|
|
36
|
+
// USDⓂ New-Order clientOrderId filter, 2026-05-16.
|
|
37
|
+
import { randomBytes } from 'node:crypto';
|
|
38
|
+
// Emitted scheme (no `rc-`, no dashes).
|
|
39
|
+
const CID_REGEX = /^bkt([0-9a-f]{16})([est])$/;
|
|
40
|
+
// Legacy scheme — still parsed for backward compat, never emitted again.
|
|
41
|
+
const LEGACY_CID_REGEX = /^rc-([0-9a-f]{16})-([est])$/;
|
|
42
|
+
const ROLE_TO_TAG = {
|
|
43
|
+
entry: 'e',
|
|
44
|
+
stop: 's',
|
|
45
|
+
target: 't',
|
|
46
|
+
};
|
|
47
|
+
const TAG_TO_ROLE = {
|
|
48
|
+
e: 'entry',
|
|
49
|
+
s: 'stop',
|
|
50
|
+
t: 'target',
|
|
51
|
+
};
|
|
52
|
+
/** Generate a fresh 16-char hex bracketId. */
|
|
53
|
+
export function generateBracketId() {
|
|
54
|
+
return randomBytes(8).toString('hex');
|
|
55
|
+
}
|
|
56
|
+
/** Build a clientOrderId for a given bracket + role. Emits the CURRENT
|
|
57
|
+
* (`bkt…`) scheme only — never the legacy `rc-` scheme. */
|
|
58
|
+
export function buildBracketCid(bracketId, role) {
|
|
59
|
+
if (!/^[0-9a-f]{16}$/.test(bracketId)) {
|
|
60
|
+
throw new Error(`Invalid bracketId: ${bracketId} — expected 16 hex chars`);
|
|
61
|
+
}
|
|
62
|
+
return `bkt${bracketId}${ROLE_TO_TAG[role]}`;
|
|
63
|
+
}
|
|
64
|
+
/** Parse a clientOrderId. Recognises BOTH the current `bkt…` scheme and
|
|
65
|
+
* the legacy `rc-…` scheme (so existing ledger rows / in-flight legs and
|
|
66
|
+
* the cancelAllOrders bracket-preserve filter keep working through the
|
|
67
|
+
* transition). Returns null for non-bracket orders (so this can be called
|
|
68
|
+
* on every order Binance returns during reconciliation). */
|
|
69
|
+
export function parseBracketCid(cid) {
|
|
70
|
+
const match = CID_REGEX.exec(cid) ?? LEGACY_CID_REGEX.exec(cid);
|
|
71
|
+
if (!match)
|
|
72
|
+
return null;
|
|
73
|
+
const bracketId = match[1];
|
|
74
|
+
const tag = match[2];
|
|
75
|
+
return { bracketId, role: TAG_TO_ROLE[tag] };
|
|
76
|
+
}
|
|
77
|
+
/** Cheap type-guard for "is this a reefclaw-managed bracket clientOrderId"
|
|
78
|
+
* — true for both the current and legacy schemes. */
|
|
79
|
+
export function isBracketCid(cid) {
|
|
80
|
+
return CID_REGEX.test(cid) || LEGACY_CID_REGEX.test(cid);
|
|
81
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { BracketId, BracketLedgerEntry, BracketState } from './bracket-types.js';
|
|
2
|
+
/** Canonical form for a bracket-ledger symbol key.
|
|
3
|
+
*
|
|
4
|
+
* CCXT returns Binance Futures symbols as `BTC/USDT:USDT` (the `:USDT`
|
|
5
|
+
* suffix is the settlement asset); the skill + agent's tool args use the
|
|
6
|
+
* un-suffixed `BTC/USDT`. Historically we keyed the ledger by whatever the
|
|
7
|
+
* caller passed in — which produced doubled rows per symbol and caused the
|
|
8
|
+
* 2026-04-21 ADA audit-vs-attach deadlock (9 of 11 symbols had both keys;
|
|
9
|
+
* see `memory/feedback_symbol_normalize_boundary.md`). This helper picks a
|
|
10
|
+
* single canonical form (un-suffixed) and is applied at every public
|
|
11
|
+
* BracketLedger entry point.
|
|
12
|
+
*
|
|
13
|
+
* Matches `user-data-stream-controller.ts` and `webapp/src/lib/symbols.ts`
|
|
14
|
+
* `normalizeSymbol`, so the bracket path lines up with the WS store + UI. */
|
|
15
|
+
export declare function normalizeBracketSymbol(s: string): string;
|
|
16
|
+
/** Test seam — lets tests override the clock for deterministic timestamps. */
|
|
17
|
+
export type NowFn = () => string;
|
|
18
|
+
export declare class BracketLedger {
|
|
19
|
+
private readonly filePath;
|
|
20
|
+
private readonly dir;
|
|
21
|
+
private readonly now;
|
|
22
|
+
private entries;
|
|
23
|
+
constructor(pluginId?: string, opts?: {
|
|
24
|
+
now?: NowFn;
|
|
25
|
+
basePath?: string;
|
|
26
|
+
});
|
|
27
|
+
/** Insert or replace the entry for a symbol. Persists synchronously.
|
|
28
|
+
* Symbol is normalized; the stored entry carries the canonical form so
|
|
29
|
+
* later readers see it regardless of whether the caller passed suffixed
|
|
30
|
+
* or un-suffixed input. */
|
|
31
|
+
upsert(entry: Omit<BracketLedgerEntry, 'createdAt' | 'updatedAt'> & {
|
|
32
|
+
createdAt?: string;
|
|
33
|
+
updatedAt?: string;
|
|
34
|
+
}): BracketLedgerEntry;
|
|
35
|
+
/** Mutate the state of an existing entry. Throws if entry not found. */
|
|
36
|
+
markState(symbol: string, nextState: BracketState, extras?: Partial<Pick<BracketLedgerEntry, 'attachStartedAt' | 'attachCompletedAt' | 'attachLatencyMs' | 'closedAt' | 'closeReason' | 'lastError' | 'slCid' | 'tpCid' | 'qty'>>): BracketLedgerEntry;
|
|
37
|
+
getBySymbol(symbol: string): BracketLedgerEntry | undefined;
|
|
38
|
+
getByBracketId(bracketId: BracketId): BracketLedgerEntry | undefined;
|
|
39
|
+
/** All active (non-terminal) entries. Used by reconciler + telemetry. */
|
|
40
|
+
getActive(): BracketLedgerEntry[];
|
|
41
|
+
/** Every entry in the ledger, including terminal ones (kept for audit). */
|
|
42
|
+
getAll(): BracketLedgerEntry[];
|
|
43
|
+
/** Remove an entry — typically only called for very old terminal entries
|
|
44
|
+
* during periodic cleanup. Does NOT persist unless mutated. */
|
|
45
|
+
remove(symbol: string): boolean;
|
|
46
|
+
/** Number of entries currently in the ledger. */
|
|
47
|
+
size(): number;
|
|
48
|
+
/** Test seam — full ledger file path. */
|
|
49
|
+
getFilePath(): string;
|
|
50
|
+
private loadOrInit;
|
|
51
|
+
private persist;
|
|
52
|
+
private validateFile;
|
|
53
|
+
private validateEntry;
|
|
54
|
+
}
|