@raintree-technology/perps 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/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/dist/adapters/aevo.d.ts +64 -0
- package/dist/adapters/aevo.js +899 -0
- package/dist/adapters/certification.d.ts +33 -0
- package/dist/adapters/certification.js +99 -0
- package/dist/adapters/decibel/order-manager.d.ts +45 -0
- package/dist/adapters/decibel/order-manager.js +140 -0
- package/dist/adapters/decibel/rest-client.d.ts +176 -0
- package/dist/adapters/decibel/rest-client.js +155 -0
- package/dist/adapters/decibel/ws-feed.d.ts +28 -0
- package/dist/adapters/decibel/ws-feed.js +166 -0
- package/dist/adapters/decibel.d.ts +108 -0
- package/dist/adapters/decibel.js +1377 -0
- package/dist/adapters/hyperliquid.d.ts +63 -0
- package/dist/adapters/hyperliquid.js +797 -0
- package/dist/adapters/index.d.ts +11 -0
- package/dist/adapters/index.js +12 -0
- package/dist/adapters/interface.d.ts +310 -0
- package/dist/adapters/interface.js +15 -0
- package/dist/adapters/orderly.d.ts +70 -0
- package/dist/adapters/orderly.js +936 -0
- package/dist/adapters/paradex.d.ts +69 -0
- package/dist/adapters/paradex.js +862 -0
- package/dist/adapters/utils.d.ts +17 -0
- package/dist/adapters/utils.js +122 -0
- package/dist/cli/command-metadata.d.ts +2 -0
- package/dist/cli/command-metadata.js +44 -0
- package/dist/cli/context.d.ts +14 -0
- package/dist/cli/context.js +59 -0
- package/dist/cli/experience.d.ts +48 -0
- package/dist/cli/experience.js +243 -0
- package/dist/cli/ink/app/AppShell.d.ts +12 -0
- package/dist/cli/ink/app/AppShell.js +32 -0
- package/dist/cli/ink/app/MetricStrip.d.ts +6 -0
- package/dist/cli/ink/app/MetricStrip.js +14 -0
- package/dist/cli/ink/app/Panel.d.ts +9 -0
- package/dist/cli/ink/app/Panel.js +7 -0
- package/dist/cli/ink/app/ascii.d.ts +2 -0
- package/dist/cli/ink/app/ascii.js +46 -0
- package/dist/cli/ink/app/index.d.ts +5 -0
- package/dist/cli/ink/app/index.js +4 -0
- package/dist/cli/ink/app/types.d.ts +15 -0
- package/dist/cli/ink/app/types.js +1 -0
- package/dist/cli/ink/components/PnL.d.ts +12 -0
- package/dist/cli/ink/components/PnL.js +23 -0
- package/dist/cli/ink/components/Spinner.d.ts +13 -0
- package/dist/cli/ink/components/Spinner.js +13 -0
- package/dist/cli/ink/components/Table.d.ts +14 -0
- package/dist/cli/ink/components/Table.js +42 -0
- package/dist/cli/ink/components/WatchHeader.d.ts +10 -0
- package/dist/cli/ink/components/WatchHeader.js +18 -0
- package/dist/cli/ink/components/index.d.ts +4 -0
- package/dist/cli/ink/components/index.js +4 -0
- package/dist/cli/ink/index.d.ts +4 -0
- package/dist/cli/ink/index.js +4 -0
- package/dist/cli/ink/render.d.ts +12 -0
- package/dist/cli/ink/render.js +21 -0
- package/dist/cli/ink/theme.d.ts +29 -0
- package/dist/cli/ink/theme.js +40 -0
- package/dist/cli/network-defaults.d.ts +10 -0
- package/dist/cli/network-defaults.js +35 -0
- package/dist/cli/output.d.ts +11 -0
- package/dist/cli/output.js +115 -0
- package/dist/cli/program.d.ts +18 -0
- package/dist/cli/program.js +164 -0
- package/dist/cli/watch.d.ts +19 -0
- package/dist/cli/watch.js +35 -0
- package/dist/client/index.d.ts +55 -0
- package/dist/client/index.js +157 -0
- package/dist/commands/account/add.d.ts +2 -0
- package/dist/commands/account/add.js +510 -0
- package/dist/commands/account/balances-simple.d.ts +5 -0
- package/dist/commands/account/balances-simple.js +63 -0
- package/dist/commands/account/index.d.ts +2 -0
- package/dist/commands/account/index.js +17 -0
- package/dist/commands/account/ls.d.ts +2 -0
- package/dist/commands/account/ls.js +95 -0
- package/dist/commands/account/positions-simple.d.ts +5 -0
- package/dist/commands/account/positions-simple.js +77 -0
- package/dist/commands/account/remove.d.ts +2 -0
- package/dist/commands/account/remove.js +47 -0
- package/dist/commands/account/set-default.d.ts +2 -0
- package/dist/commands/account/set-default.js +47 -0
- package/dist/commands/agent/index.d.ts +2 -0
- package/dist/commands/agent/index.js +126 -0
- package/dist/commands/arb/alert.d.ts +6 -0
- package/dist/commands/arb/alert.js +88 -0
- package/dist/commands/arb/basis-execute.d.ts +6 -0
- package/dist/commands/arb/basis-execute.js +332 -0
- package/dist/commands/arb/basis.d.ts +6 -0
- package/dist/commands/arb/basis.js +181 -0
- package/dist/commands/arb/compare.d.ts +6 -0
- package/dist/commands/arb/compare.js +216 -0
- package/dist/commands/arb/execute.d.ts +6 -0
- package/dist/commands/arb/execute.js +467 -0
- package/dist/commands/arb/funding.d.ts +6 -0
- package/dist/commands/arb/funding.js +201 -0
- package/dist/commands/arb/history.d.ts +6 -0
- package/dist/commands/arb/history.js +153 -0
- package/dist/commands/arb/index.d.ts +6 -0
- package/dist/commands/arb/index.js +29 -0
- package/dist/commands/arb/positions.d.ts +6 -0
- package/dist/commands/arb/positions.js +158 -0
- package/dist/commands/arb/spread.d.ts +6 -0
- package/dist/commands/arb/spread.js +253 -0
- package/dist/commands/arb/track.d.ts +6 -0
- package/dist/commands/arb/track.js +259 -0
- package/dist/commands/asset/book-simple.d.ts +5 -0
- package/dist/commands/asset/book-simple.js +77 -0
- package/dist/commands/asset/index.d.ts +2 -0
- package/dist/commands/asset/index.js +5 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/completion.js +161 -0
- package/dist/commands/config/index.d.ts +5 -0
- package/dist/commands/config/index.js +109 -0
- package/dist/commands/data/index.d.ts +31 -0
- package/dist/commands/data/index.js +1466 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +201 -0
- package/dist/commands/exchange/index.d.ts +2 -0
- package/dist/commands/exchange/index.js +107 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +48 -0
- package/dist/commands/markets/index.d.ts +2 -0
- package/dist/commands/markets/index.js +5 -0
- package/dist/commands/markets/ls-simple.d.ts +7 -0
- package/dist/commands/markets/ls-simple.js +277 -0
- package/dist/commands/operator/index.d.ts +2 -0
- package/dist/commands/operator/index.js +146 -0
- package/dist/commands/order/cancel-simple.d.ts +5 -0
- package/dist/commands/order/cancel-simple.js +104 -0
- package/dist/commands/order/index.d.ts +2 -0
- package/dist/commands/order/index.js +13 -0
- package/dist/commands/order/limit-simple.d.ts +5 -0
- package/dist/commands/order/limit-simple.js +195 -0
- package/dist/commands/order/market-simple.d.ts +5 -0
- package/dist/commands/order/market-simple.js +190 -0
- package/dist/commands/order/shared.d.ts +17 -0
- package/dist/commands/order/shared.js +51 -0
- package/dist/commands/order/trigger-simple.d.ts +5 -0
- package/dist/commands/order/trigger-simple.js +246 -0
- package/dist/commands/referral/index.d.ts +2 -0
- package/dist/commands/referral/index.js +7 -0
- package/dist/commands/referral/set.d.ts +2 -0
- package/dist/commands/referral/set.js +26 -0
- package/dist/commands/referral/status.d.ts +2 -0
- package/dist/commands/referral/status.js +31 -0
- package/dist/commands/replay/index.d.ts +2 -0
- package/dist/commands/replay/index.js +152 -0
- package/dist/commands/risk/analytics.d.ts +2 -0
- package/dist/commands/risk/analytics.js +64 -0
- package/dist/commands/risk/audit.d.ts +2 -0
- package/dist/commands/risk/audit.js +52 -0
- package/dist/commands/risk/index.d.ts +2 -0
- package/dist/commands/risk/index.js +9 -0
- package/dist/commands/risk/rules.d.ts +2 -0
- package/dist/commands/risk/rules.js +102 -0
- package/dist/commands/server.d.ts +2 -0
- package/dist/commands/server.js +208 -0
- package/dist/commands/setup/index.d.ts +2 -0
- package/dist/commands/setup/index.js +478 -0
- package/dist/commands/signal/index.d.ts +2 -0
- package/dist/commands/signal/index.js +129 -0
- package/dist/commands/state/index.d.ts +2 -0
- package/dist/commands/state/index.js +5 -0
- package/dist/commands/state/show.d.ts +2 -0
- package/dist/commands/state/show.js +105 -0
- package/dist/commands/strategy/index.d.ts +4 -0
- package/dist/commands/strategy/index.js +73 -0
- package/dist/commands/traces/index.d.ts +2 -0
- package/dist/commands/traces/index.js +76 -0
- package/dist/commands/ui/demo.d.ts +9 -0
- package/dist/commands/ui/demo.js +195 -0
- package/dist/commands/ui/index.d.ts +2 -0
- package/dist/commands/ui/index.js +7 -0
- package/dist/commands/ui/terminal.d.ts +2 -0
- package/dist/commands/ui/terminal.js +255 -0
- package/dist/commands/upgrade.d.ts +2 -0
- package/dist/commands/upgrade.js +98 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/lib/agent/audit.d.ts +12 -0
- package/dist/lib/agent/audit.js +13 -0
- package/dist/lib/agent/gateway.d.ts +13 -0
- package/dist/lib/agent/gateway.js +598 -0
- package/dist/lib/agent/metrics.d.ts +33 -0
- package/dist/lib/agent/metrics.js +175 -0
- package/dist/lib/agent/signature.d.ts +8 -0
- package/dist/lib/agent/signature.js +28 -0
- package/dist/lib/agent/tools.d.ts +28 -0
- package/dist/lib/agent/tools.js +453 -0
- package/dist/lib/agent/x402.d.ts +23 -0
- package/dist/lib/agent/x402.js +62 -0
- package/dist/lib/api-wallet.d.ts +69 -0
- package/dist/lib/api-wallet.js +101 -0
- package/dist/lib/balance-watcher.d.ts +25 -0
- package/dist/lib/balance-watcher.js +83 -0
- package/dist/lib/book-watcher.d.ts +25 -0
- package/dist/lib/book-watcher.js +48 -0
- package/dist/lib/config.d.ts +88 -0
- package/dist/lib/config.js +427 -0
- package/dist/lib/constants.d.ts +50 -0
- package/dist/lib/constants.js +84 -0
- package/dist/lib/contracts.d.ts +7 -0
- package/dist/lib/contracts.js +8 -0
- package/dist/lib/credential-vault.d.ts +22 -0
- package/dist/lib/credential-vault.js +109 -0
- package/dist/lib/db/accounts.d.ts +83 -0
- package/dist/lib/db/accounts.js +203 -0
- package/dist/lib/db/funding-history.d.ts +69 -0
- package/dist/lib/db/funding-history.js +183 -0
- package/dist/lib/db/index.d.ts +11 -0
- package/dist/lib/db/index.js +272 -0
- package/dist/lib/events/bus.d.ts +10 -0
- package/dist/lib/events/bus.js +17 -0
- package/dist/lib/events/types.d.ts +51 -0
- package/dist/lib/events/types.js +1 -0
- package/dist/lib/exchange.d.ts +30 -0
- package/dist/lib/exchange.js +84 -0
- package/dist/lib/execution/journal.d.ts +25 -0
- package/dist/lib/execution/journal.js +158 -0
- package/dist/lib/execution/safety.d.ts +34 -0
- package/dist/lib/execution/safety.js +197 -0
- package/dist/lib/exit-codes.d.ts +18 -0
- package/dist/lib/exit-codes.js +60 -0
- package/dist/lib/fetch.d.ts +18 -0
- package/dist/lib/fetch.js +66 -0
- package/dist/lib/fs-security.d.ts +10 -0
- package/dist/lib/fs-security.js +26 -0
- package/dist/lib/funding-tracker.d.ts +40 -0
- package/dist/lib/funding-tracker.js +118 -0
- package/dist/lib/logger.d.ts +27 -0
- package/dist/lib/logger.js +82 -0
- package/dist/lib/network-model.d.ts +13 -0
- package/dist/lib/network-model.js +30 -0
- package/dist/lib/onboarding.d.ts +133 -0
- package/dist/lib/onboarding.js +1459 -0
- package/dist/lib/operator-state.d.ts +25 -0
- package/dist/lib/operator-state.js +82 -0
- package/dist/lib/orders-watcher.d.ts +24 -0
- package/dist/lib/orders-watcher.js +74 -0
- package/dist/lib/paths.d.ts +20 -0
- package/dist/lib/paths.js +23 -0
- package/dist/lib/portfolio-watcher.d.ts +33 -0
- package/dist/lib/portfolio-watcher.js +95 -0
- package/dist/lib/position-watcher.d.ts +16 -0
- package/dist/lib/position-watcher.js +44 -0
- package/dist/lib/price-watcher.d.ts +15 -0
- package/dist/lib/price-watcher.js +84 -0
- package/dist/lib/prompts.d.ts +32 -0
- package/dist/lib/prompts.js +105 -0
- package/dist/lib/rate-limit.d.ts +32 -0
- package/dist/lib/rate-limit.js +88 -0
- package/dist/lib/risk/analytics.d.ts +39 -0
- package/dist/lib/risk/analytics.js +98 -0
- package/dist/lib/risk/drawdown.d.ts +18 -0
- package/dist/lib/risk/drawdown.js +49 -0
- package/dist/lib/risk/evaluation-log.d.ts +29 -0
- package/dist/lib/risk/evaluation-log.js +61 -0
- package/dist/lib/risk/index.d.ts +4 -0
- package/dist/lib/risk/index.js +4 -0
- package/dist/lib/risk/limits.d.ts +23 -0
- package/dist/lib/risk/limits.js +27 -0
- package/dist/lib/risk/manager.d.ts +32 -0
- package/dist/lib/risk/manager.js +85 -0
- package/dist/lib/risk/policy-middleware.d.ts +33 -0
- package/dist/lib/risk/policy-middleware.js +267 -0
- package/dist/lib/risk/position-sizer.d.ts +9 -0
- package/dist/lib/risk/position-sizer.js +14 -0
- package/dist/lib/risk/rules-store.d.ts +16 -0
- package/dist/lib/risk/rules-store.js +47 -0
- package/dist/lib/schema.d.ts +254 -0
- package/dist/lib/schema.js +199 -0
- package/dist/lib/secrets.d.ts +3 -0
- package/dist/lib/secrets.js +62 -0
- package/dist/lib/settings.d.ts +24 -0
- package/dist/lib/settings.js +86 -0
- package/dist/lib/signals.d.ts +73 -0
- package/dist/lib/signals.js +136 -0
- package/dist/lib/stable-stringify.d.ts +6 -0
- package/dist/lib/stable-stringify.js +17 -0
- package/dist/lib/state-context.d.ts +44 -0
- package/dist/lib/state-context.js +133 -0
- package/dist/lib/strategy/basis-trade.d.ts +2 -0
- package/dist/lib/strategy/basis-trade.js +24 -0
- package/dist/lib/strategy/funding-arb.d.ts +2 -0
- package/dist/lib/strategy/funding-arb.js +23 -0
- package/dist/lib/strategy/interface.d.ts +23 -0
- package/dist/lib/strategy/interface.js +1 -0
- package/dist/lib/strategy/registry.d.ts +4 -0
- package/dist/lib/strategy/registry.js +10 -0
- package/dist/lib/telemetry.d.ts +25 -0
- package/dist/lib/telemetry.js +101 -0
- package/dist/lib/trace-queries.d.ts +20 -0
- package/dist/lib/trace-queries.js +133 -0
- package/dist/lib/trace.d.ts +1 -0
- package/dist/lib/trace.js +4 -0
- package/dist/lib/trade-reputation.d.ts +6 -0
- package/dist/lib/trade-reputation.js +99 -0
- package/dist/lib/ui-tokens.d.ts +21 -0
- package/dist/lib/ui-tokens.js +26 -0
- package/dist/lib/validate.d.ts +39 -0
- package/dist/lib/validate.js +108 -0
- package/dist/lib/validation.d.ts +9 -0
- package/dist/lib/validation.js +64 -0
- package/dist/server/cache.d.ts +38 -0
- package/dist/server/cache.js +56 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +89 -0
- package/dist/server/ipc.d.ts +18 -0
- package/dist/server/ipc.js +159 -0
- package/dist/server/subscriptions.d.ts +18 -0
- package/dist/server/subscriptions.js +114 -0
- package/package.json +124 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { eventBus } from "../events/bus.js";
|
|
2
|
+
import { createLogger } from "../logger.js";
|
|
3
|
+
import { assertTradeAllowed } from "../trade-reputation.js";
|
|
4
|
+
const log = createLogger("execution:safety");
|
|
5
|
+
export async function executeOrderWithSafety(adapter, params, options = {}) {
|
|
6
|
+
assertTradeAllowed({
|
|
7
|
+
exchangeId: adapter.info.id,
|
|
8
|
+
market: params.market,
|
|
9
|
+
command: "order.execute",
|
|
10
|
+
});
|
|
11
|
+
let closedOppositePosition = false;
|
|
12
|
+
let orderParams = { ...params };
|
|
13
|
+
let pricing;
|
|
14
|
+
if (options.closeThenFlip && !params.reduceOnly) {
|
|
15
|
+
closedOppositePosition = await closeThenFlipIfNeeded(adapter, params.market, params.side);
|
|
16
|
+
}
|
|
17
|
+
if (options.spreadAwarePricing) {
|
|
18
|
+
const spreadAware = await resolveSpreadAwarePrice(adapter, params.market, params.side, options.spreadOffset ?? 0.3);
|
|
19
|
+
pricing = spreadAware;
|
|
20
|
+
// Decibel uses this pattern for safer marketable execution.
|
|
21
|
+
if (params.type === "market" && adapter.info.id === "decibel") {
|
|
22
|
+
orderParams = {
|
|
23
|
+
...orderParams,
|
|
24
|
+
type: "limit",
|
|
25
|
+
price: spreadAware.referencePrice.toFixed(8),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const order = await adapter.placeOrder(orderParams);
|
|
30
|
+
eventBus.emit("order.filled", {
|
|
31
|
+
exchange: "",
|
|
32
|
+
market: orderParams.market,
|
|
33
|
+
orderId: order.id,
|
|
34
|
+
side: orderParams.side,
|
|
35
|
+
size: parseFloat(orderParams.size),
|
|
36
|
+
price: order.price ? parseFloat(order.price) : 0,
|
|
37
|
+
timestamp: Date.now(),
|
|
38
|
+
});
|
|
39
|
+
const tpSlOrderIds = [];
|
|
40
|
+
if (options.attachTpSl &&
|
|
41
|
+
options.tpSlConfig &&
|
|
42
|
+
!params.reduceOnly &&
|
|
43
|
+
params.side !== undefined) {
|
|
44
|
+
const entryPrice = parseFloat(order.price ?? "");
|
|
45
|
+
const fallbackPrice = pricing?.referencePrice;
|
|
46
|
+
const resolvedEntry = Number.isFinite(entryPrice) && entryPrice > 0 ? entryPrice : fallbackPrice;
|
|
47
|
+
if (resolvedEntry && resolvedEntry > 0) {
|
|
48
|
+
const ids = await attachTpSlForPosition(adapter, {
|
|
49
|
+
market: params.market,
|
|
50
|
+
side: params.side,
|
|
51
|
+
size: params.size,
|
|
52
|
+
entryPrice: resolvedEntry,
|
|
53
|
+
}, options.tpSlConfig);
|
|
54
|
+
tpSlOrderIds.push(...ids);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
order,
|
|
59
|
+
closedOppositePosition,
|
|
60
|
+
tpSlOrderIds,
|
|
61
|
+
pricing,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export async function closeThenFlipIfNeeded(adapter, market, desiredSide) {
|
|
65
|
+
let position = null;
|
|
66
|
+
try {
|
|
67
|
+
position = await adapter.getPosition(market);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
if (!position) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if (position.side === desiredSide) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const closeSide = position.side === "long" ? "short" : "long";
|
|
79
|
+
const size = parseFloat(position.size);
|
|
80
|
+
if (!Number.isFinite(size) || size <= 0) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
log.info("Closing opposite position before flip", {
|
|
84
|
+
market,
|
|
85
|
+
existingSide: position.side,
|
|
86
|
+
desiredSide,
|
|
87
|
+
size: position.size,
|
|
88
|
+
});
|
|
89
|
+
await adapter.placeOrder({
|
|
90
|
+
market,
|
|
91
|
+
side: closeSide,
|
|
92
|
+
type: "market",
|
|
93
|
+
size: position.size,
|
|
94
|
+
reduceOnly: true,
|
|
95
|
+
});
|
|
96
|
+
eventBus.emit("position.closed", {
|
|
97
|
+
exchange: "",
|
|
98
|
+
market: position.market,
|
|
99
|
+
side: position.side,
|
|
100
|
+
size: Math.abs(parseFloat(position.size)),
|
|
101
|
+
entryPrice: parseFloat(position.entryPrice),
|
|
102
|
+
exitPrice: parseFloat(position.markPrice),
|
|
103
|
+
pnl: parseFloat(position.unrealizedPnl),
|
|
104
|
+
timestamp: Date.now(),
|
|
105
|
+
});
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
export async function attachTpSlForPosition(adapter, position, config) {
|
|
109
|
+
const ids = [];
|
|
110
|
+
if (!adapter.info.features.stopOrders && !adapter.info.features.takeProfitOrders) {
|
|
111
|
+
return ids;
|
|
112
|
+
}
|
|
113
|
+
const isLong = position.side === "long";
|
|
114
|
+
const tpTriggerPrice = isLong
|
|
115
|
+
? position.entryPrice * (1 + config.takeProfitPct / 100)
|
|
116
|
+
: position.entryPrice * (1 - config.takeProfitPct / 100);
|
|
117
|
+
const slTriggerPrice = isLong
|
|
118
|
+
? position.entryPrice * (1 - config.stopLossPct / 100)
|
|
119
|
+
: position.entryPrice * (1 + config.stopLossPct / 100);
|
|
120
|
+
const tpLimitPrice = isLong ? tpTriggerPrice * 0.999 : tpTriggerPrice * 1.001;
|
|
121
|
+
const slLimitPrice = isLong ? slTriggerPrice * 0.999 : slTriggerPrice * 1.001;
|
|
122
|
+
if (adapter.info.features.takeProfitOrders) {
|
|
123
|
+
try {
|
|
124
|
+
const tpOrder = await adapter.placeOrder({
|
|
125
|
+
market: position.market,
|
|
126
|
+
side: isLong ? "short" : "long",
|
|
127
|
+
type: "take_profit",
|
|
128
|
+
size: position.size,
|
|
129
|
+
reduceOnly: true,
|
|
130
|
+
triggerPrice: tpTriggerPrice.toFixed(8),
|
|
131
|
+
price: tpLimitPrice.toFixed(8),
|
|
132
|
+
});
|
|
133
|
+
ids.push(tpOrder.id);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
log.warn("Failed to attach take-profit", { market: position.market, err });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (adapter.info.features.stopOrders) {
|
|
140
|
+
try {
|
|
141
|
+
const slOrder = await adapter.placeOrder({
|
|
142
|
+
market: position.market,
|
|
143
|
+
side: isLong ? "short" : "long",
|
|
144
|
+
type: "stop_limit",
|
|
145
|
+
size: position.size,
|
|
146
|
+
reduceOnly: true,
|
|
147
|
+
triggerPrice: slTriggerPrice.toFixed(8),
|
|
148
|
+
price: slLimitPrice.toFixed(8),
|
|
149
|
+
});
|
|
150
|
+
ids.push(slOrder.id);
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
log.warn("Failed to attach stop-loss", { market: position.market, err });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return ids;
|
|
157
|
+
}
|
|
158
|
+
export async function resolveSpreadAwarePrice(adapter, market, side, spreadOffset = 0.3) {
|
|
159
|
+
try {
|
|
160
|
+
const book = await adapter.getOrderBook(market, 1);
|
|
161
|
+
const bestBid = parseFloat(book.bids[0]?.price ?? "");
|
|
162
|
+
const bestAsk = parseFloat(book.asks[0]?.price ?? "");
|
|
163
|
+
if (Number.isFinite(bestBid) && Number.isFinite(bestAsk) && bestBid > 0 && bestAsk > 0) {
|
|
164
|
+
const mid = (bestBid + bestAsk) / 2;
|
|
165
|
+
const spread = Math.max(0, bestAsk - bestBid);
|
|
166
|
+
const offset = spread * spreadOffset;
|
|
167
|
+
const rawPrice = side === "long" ? mid + offset : mid - offset;
|
|
168
|
+
const clampedPrice = clamp(rawPrice, bestBid, bestAsk);
|
|
169
|
+
return {
|
|
170
|
+
source: "orderbook",
|
|
171
|
+
referencePrice: clampedPrice,
|
|
172
|
+
spread,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// fallback below
|
|
178
|
+
}
|
|
179
|
+
const ticker = await adapter.getTicker(market);
|
|
180
|
+
const mark = parseFloat(ticker.markPrice);
|
|
181
|
+
const last = parseFloat(ticker.lastPrice);
|
|
182
|
+
const referencePrice = Number.isFinite(mark) && mark > 0 ? mark : last;
|
|
183
|
+
if (!Number.isFinite(referencePrice) || referencePrice <= 0) {
|
|
184
|
+
throw new Error(`Unable to resolve spread-aware price for ${market}`);
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
source: "ticker",
|
|
188
|
+
referencePrice,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function clamp(value, min, max) {
|
|
192
|
+
if (value < min)
|
|
193
|
+
return min;
|
|
194
|
+
if (value > max)
|
|
195
|
+
return max;
|
|
196
|
+
return value;
|
|
197
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const EXIT_CODES: {
|
|
2
|
+
readonly OK: 0;
|
|
3
|
+
readonly GENERAL_ERROR: 1;
|
|
4
|
+
readonly VALIDATION_ERROR: 2;
|
|
5
|
+
readonly CONFIG_ERROR: 3;
|
|
6
|
+
readonly AUTH_ERROR: 4;
|
|
7
|
+
readonly DATA_UNAVAILABLE: 5;
|
|
8
|
+
readonly NO_OPPORTUNITY: 6;
|
|
9
|
+
readonly EXECUTION_ERROR: 7;
|
|
10
|
+
readonly PARTIAL_EXECUTION: 8;
|
|
11
|
+
readonly CANCELLED: 9;
|
|
12
|
+
};
|
|
13
|
+
export type ExitCode = (typeof EXIT_CODES)[keyof typeof EXIT_CODES];
|
|
14
|
+
export declare class CLIError extends Error {
|
|
15
|
+
readonly exitCode: ExitCode;
|
|
16
|
+
constructor(message: string, exitCode: ExitCode);
|
|
17
|
+
}
|
|
18
|
+
export declare function inferExitCode(err: unknown): ExitCode;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export const EXIT_CODES = {
|
|
2
|
+
OK: 0,
|
|
3
|
+
GENERAL_ERROR: 1,
|
|
4
|
+
VALIDATION_ERROR: 2,
|
|
5
|
+
CONFIG_ERROR: 3,
|
|
6
|
+
AUTH_ERROR: 4,
|
|
7
|
+
DATA_UNAVAILABLE: 5,
|
|
8
|
+
NO_OPPORTUNITY: 6,
|
|
9
|
+
EXECUTION_ERROR: 7,
|
|
10
|
+
PARTIAL_EXECUTION: 8,
|
|
11
|
+
CANCELLED: 9,
|
|
12
|
+
};
|
|
13
|
+
export class CLIError extends Error {
|
|
14
|
+
exitCode;
|
|
15
|
+
constructor(message, exitCode) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "CLIError";
|
|
18
|
+
this.exitCode = exitCode;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function inferExitCode(err) {
|
|
22
|
+
if (err instanceof CLIError) {
|
|
23
|
+
return err.exitCode;
|
|
24
|
+
}
|
|
25
|
+
const message = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();
|
|
26
|
+
if (message.includes("must be") ||
|
|
27
|
+
message.includes("invalid") ||
|
|
28
|
+
message.includes("use --yes with --json")) {
|
|
29
|
+
return EXIT_CODES.VALIDATION_ERROR;
|
|
30
|
+
}
|
|
31
|
+
if (message.includes("not configured") ||
|
|
32
|
+
message.includes("no account") ||
|
|
33
|
+
message.includes("credentials") ||
|
|
34
|
+
message.includes("read-only") ||
|
|
35
|
+
(message.includes("requires") &&
|
|
36
|
+
(message.includes("private_key") ||
|
|
37
|
+
message.includes("private key") ||
|
|
38
|
+
message.includes("api-wallet") ||
|
|
39
|
+
message.includes("api wallet")))) {
|
|
40
|
+
return EXIT_CODES.AUTH_ERROR;
|
|
41
|
+
}
|
|
42
|
+
if (message.includes("unknown exchange") ||
|
|
43
|
+
message.includes("config") ||
|
|
44
|
+
message.includes("failed to load")) {
|
|
45
|
+
return EXIT_CODES.CONFIG_ERROR;
|
|
46
|
+
}
|
|
47
|
+
if (message.includes("risk policy") ||
|
|
48
|
+
message.includes("reputation policy") ||
|
|
49
|
+
message.includes("idempotency key") ||
|
|
50
|
+
message.includes("execution failed") ||
|
|
51
|
+
message.includes("unable to fetch")) {
|
|
52
|
+
return EXIT_CODES.EXECUTION_ERROR;
|
|
53
|
+
}
|
|
54
|
+
if (message.includes("not enough exchange data") ||
|
|
55
|
+
message.includes("coin not found") ||
|
|
56
|
+
message.includes("not found")) {
|
|
57
|
+
return EXIT_CODES.DATA_UNAVAILABLE;
|
|
58
|
+
}
|
|
59
|
+
return EXIT_CODES.GENERAL_ERROR;
|
|
60
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Fetch Utilities
|
|
3
|
+
* Provides fetch with timeout, retry, and error handling
|
|
4
|
+
*/
|
|
5
|
+
export interface FetchOptions extends RequestInit {
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
retries?: number;
|
|
8
|
+
retryDelayMs?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare class FetchError extends Error {
|
|
11
|
+
readonly status?: number | undefined;
|
|
12
|
+
readonly url?: string | undefined;
|
|
13
|
+
constructor(message: string, status?: number | undefined, url?: string | undefined);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Fetch with timeout, retry, and proper error handling
|
|
17
|
+
*/
|
|
18
|
+
export declare function fetchWithTimeout<T>(url: string, options?: FetchOptions): Promise<T>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Fetch Utilities
|
|
3
|
+
* Provides fetch with timeout, retry, and error handling
|
|
4
|
+
*/
|
|
5
|
+
export class FetchError extends Error {
|
|
6
|
+
status;
|
|
7
|
+
url;
|
|
8
|
+
constructor(message, status, url) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.url = url;
|
|
12
|
+
this.name = "FetchError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
import { DEFAULT_TIMEOUT_MS, DEFAULT_RETRIES, DEFAULT_RETRY_DELAY_MS } from "./constants.js";
|
|
16
|
+
/**
|
|
17
|
+
* Fetch with timeout, retry, and proper error handling
|
|
18
|
+
*/
|
|
19
|
+
export async function fetchWithTimeout(url, options = {}) {
|
|
20
|
+
const { timeoutMs = DEFAULT_TIMEOUT_MS, retries = DEFAULT_RETRIES, retryDelayMs = DEFAULT_RETRY_DELAY_MS, ...fetchOptions } = options;
|
|
21
|
+
let lastError = null;
|
|
22
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
23
|
+
let timeoutId = null;
|
|
24
|
+
try {
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
27
|
+
const response = await fetch(url, {
|
|
28
|
+
...fetchOptions,
|
|
29
|
+
signal: controller.signal,
|
|
30
|
+
headers: {
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
...fetchOptions.headers,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
const text = await response.text().catch(() => "Unknown error");
|
|
37
|
+
throw new FetchError(`HTTP ${response.status}: ${text.slice(0, 200)}`, response.status, url);
|
|
38
|
+
}
|
|
39
|
+
return await response.json();
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
43
|
+
// Don't retry on client errors (4xx)
|
|
44
|
+
if (err instanceof FetchError && err.status && err.status >= 400 && err.status < 500) {
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
// Don't retry on abort (timeout)
|
|
48
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
49
|
+
throw new FetchError(`Request timeout after ${timeoutMs}ms`, undefined, url);
|
|
50
|
+
}
|
|
51
|
+
// Retry on other errors
|
|
52
|
+
if (attempt < retries) {
|
|
53
|
+
await sleep(retryDelayMs * (attempt + 1)); // Exponential backoff
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
if (timeoutId) {
|
|
58
|
+
clearTimeout(timeoutId);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
throw lastError || new FetchError("Request failed", undefined, url);
|
|
63
|
+
}
|
|
64
|
+
function sleep(ms) {
|
|
65
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
66
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const PRIVATE_DIR_MODE = 448;
|
|
2
|
+
export declare const PRIVATE_FILE_MODE = 384;
|
|
3
|
+
/**
|
|
4
|
+
* Ensure a runtime directory is owner-only where supported.
|
|
5
|
+
*/
|
|
6
|
+
export declare function ensurePrivateDir(path: string): void;
|
|
7
|
+
/**
|
|
8
|
+
* Enforce owner-only permissions after writes.
|
|
9
|
+
*/
|
|
10
|
+
export declare function hardenPrivateFile(path: string): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { chmodSync, mkdirSync } from "node:fs";
|
|
2
|
+
export const PRIVATE_DIR_MODE = 0o700;
|
|
3
|
+
export const PRIVATE_FILE_MODE = 0o600;
|
|
4
|
+
/**
|
|
5
|
+
* Ensure a runtime directory is owner-only where supported.
|
|
6
|
+
*/
|
|
7
|
+
export function ensurePrivateDir(path) {
|
|
8
|
+
mkdirSync(path, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
9
|
+
try {
|
|
10
|
+
chmodSync(path, PRIVATE_DIR_MODE);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
// Best effort on non-POSIX filesystems.
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Enforce owner-only permissions after writes.
|
|
18
|
+
*/
|
|
19
|
+
export function hardenPrivateFile(path) {
|
|
20
|
+
try {
|
|
21
|
+
chmodSync(path, PRIVATE_FILE_MODE);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Best effort on non-POSIX filesystems.
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Funding Rate Tracker
|
|
3
|
+
* Polls funding rates across exchanges and stores history
|
|
4
|
+
*/
|
|
5
|
+
import { type FundingRecord } from "./db/funding-history.js";
|
|
6
|
+
interface TrackerOptions {
|
|
7
|
+
assets: string[];
|
|
8
|
+
exchanges?: string[];
|
|
9
|
+
testnet?: boolean;
|
|
10
|
+
intervalMs?: number;
|
|
11
|
+
onUpdate?: (records: FundingRecord[]) => void;
|
|
12
|
+
onError?: (error: Error, exchange: string) => void;
|
|
13
|
+
}
|
|
14
|
+
interface TrackerState {
|
|
15
|
+
running: boolean;
|
|
16
|
+
lastPoll: number;
|
|
17
|
+
recordCount: number;
|
|
18
|
+
errors: Map<string, string>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Poll funding rates for all assets across all exchanges
|
|
22
|
+
*/
|
|
23
|
+
export declare function pollFundingRates(assets: string[], exchanges?: string[], onError?: (error: Error, exchange: string) => void, testnet?: boolean): Promise<FundingRecord[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Start the funding tracker
|
|
26
|
+
*/
|
|
27
|
+
export declare function startTracker(options: TrackerOptions): void;
|
|
28
|
+
/**
|
|
29
|
+
* Stop the funding tracker
|
|
30
|
+
*/
|
|
31
|
+
export declare function stopTracker(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Get tracker status
|
|
34
|
+
*/
|
|
35
|
+
export declare function getTrackerState(): TrackerState;
|
|
36
|
+
/**
|
|
37
|
+
* Check if tracker is running
|
|
38
|
+
*/
|
|
39
|
+
export declare function isTrackerRunning(): boolean;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Funding Rate Tracker
|
|
3
|
+
* Polls funding rates across exchanges and stores history
|
|
4
|
+
*/
|
|
5
|
+
import { getExchangeAdapterById, getAvailableExchanges } from "./exchange.js";
|
|
6
|
+
import { FUNDING_INTERVAL_HOURS } from "./constants.js";
|
|
7
|
+
import { storeFundingRates } from "./db/funding-history.js";
|
|
8
|
+
const PERIODS_PER_YEAR = (24 / FUNDING_INTERVAL_HOURS) * 365;
|
|
9
|
+
let trackerInterval = null;
|
|
10
|
+
let trackerState = {
|
|
11
|
+
running: false,
|
|
12
|
+
lastPoll: 0,
|
|
13
|
+
recordCount: 0,
|
|
14
|
+
errors: new Map(),
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Poll funding rates for all assets across all exchanges
|
|
18
|
+
*/
|
|
19
|
+
export async function pollFundingRates(assets, exchanges, onError, testnet = false) {
|
|
20
|
+
const exchangeIds = exchanges || getAvailableExchanges();
|
|
21
|
+
const records = [];
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
for (const exchangeId of exchangeIds) {
|
|
24
|
+
const adapter = getExchangeAdapterById(exchangeId);
|
|
25
|
+
let connected = false;
|
|
26
|
+
try {
|
|
27
|
+
await adapter.connect({ testnet });
|
|
28
|
+
connected = true;
|
|
29
|
+
for (const asset of assets) {
|
|
30
|
+
try {
|
|
31
|
+
const symbol = `${asset}-PERP`;
|
|
32
|
+
const funding = await adapter.getFundingRate(symbol);
|
|
33
|
+
const rate = parseFloat(funding.rate);
|
|
34
|
+
records.push({
|
|
35
|
+
exchange: adapter.info.name,
|
|
36
|
+
market: `${asset}-PERP`,
|
|
37
|
+
rate,
|
|
38
|
+
annualized: rate * PERIODS_PER_YEAR * 100,
|
|
39
|
+
nextFunding: funding.nextFundingTime,
|
|
40
|
+
recordedAt: now,
|
|
41
|
+
});
|
|
42
|
+
trackerState.errors.delete(`${exchangeId}:${asset}`);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
const key = `${exchangeId}:${asset}`;
|
|
46
|
+
trackerState.errors.set(key, err instanceof Error ? err.message : "Unknown error");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
trackerState.errors.set(exchangeId, err instanceof Error ? err.message : "Unknown error");
|
|
52
|
+
onError?.(err instanceof Error ? err : new Error(String(err)), exchangeId);
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
if (connected) {
|
|
56
|
+
await adapter.disconnect().catch(() => undefined);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Store in database
|
|
61
|
+
if (records.length > 0) {
|
|
62
|
+
storeFundingRates(records);
|
|
63
|
+
trackerState.recordCount += records.length;
|
|
64
|
+
}
|
|
65
|
+
trackerState.lastPoll = now;
|
|
66
|
+
return records;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Start the funding tracker
|
|
70
|
+
*/
|
|
71
|
+
export function startTracker(options) {
|
|
72
|
+
if (trackerInterval) {
|
|
73
|
+
throw new Error("Tracker already running");
|
|
74
|
+
}
|
|
75
|
+
const intervalMs = options.intervalMs || 5 * 60 * 1000; // 5 minutes
|
|
76
|
+
trackerState = {
|
|
77
|
+
running: true,
|
|
78
|
+
lastPoll: 0,
|
|
79
|
+
recordCount: 0,
|
|
80
|
+
errors: new Map(),
|
|
81
|
+
};
|
|
82
|
+
// Poll immediately
|
|
83
|
+
pollFundingRates(options.assets, options.exchanges, options.onError, options.testnet ?? false)
|
|
84
|
+
.then(records => options.onUpdate?.(records))
|
|
85
|
+
.catch(err => console.error("Initial poll failed:", err));
|
|
86
|
+
// Then poll on interval
|
|
87
|
+
trackerInterval = setInterval(async () => {
|
|
88
|
+
try {
|
|
89
|
+
const records = await pollFundingRates(options.assets, options.exchanges, options.onError, options.testnet ?? false);
|
|
90
|
+
options.onUpdate?.(records);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
console.error("Poll failed:", err);
|
|
94
|
+
}
|
|
95
|
+
}, intervalMs);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Stop the funding tracker
|
|
99
|
+
*/
|
|
100
|
+
export function stopTracker() {
|
|
101
|
+
if (trackerInterval) {
|
|
102
|
+
clearInterval(trackerInterval);
|
|
103
|
+
trackerInterval = null;
|
|
104
|
+
}
|
|
105
|
+
trackerState.running = false;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get tracker status
|
|
109
|
+
*/
|
|
110
|
+
export function getTrackerState() {
|
|
111
|
+
return { ...trackerState, errors: new Map(trackerState.errors) };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Check if tracker is running
|
|
115
|
+
*/
|
|
116
|
+
export function isTrackerRunning() {
|
|
117
|
+
return trackerState.running;
|
|
118
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Utility
|
|
3
|
+
* Centralized logging with levels and formatting
|
|
4
|
+
*/
|
|
5
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
6
|
+
interface LoggerOptions {
|
|
7
|
+
level?: LogLevel;
|
|
8
|
+
prefix?: string;
|
|
9
|
+
timestamps?: boolean;
|
|
10
|
+
}
|
|
11
|
+
declare class Logger {
|
|
12
|
+
private level;
|
|
13
|
+
private prefix;
|
|
14
|
+
private timestamps;
|
|
15
|
+
constructor(options?: LoggerOptions);
|
|
16
|
+
private shouldLog;
|
|
17
|
+
private format;
|
|
18
|
+
debug(message: string, ...args: unknown[]): void;
|
|
19
|
+
info(message: string, ...args: unknown[]): void;
|
|
20
|
+
warn(message: string, ...args: unknown[]): void;
|
|
21
|
+
error(message: string, ...args: unknown[]): void;
|
|
22
|
+
child(prefix: string): Logger;
|
|
23
|
+
setLevel(level: LogLevel): void;
|
|
24
|
+
}
|
|
25
|
+
export declare const logger: Logger;
|
|
26
|
+
export declare function createLogger(name: string, options?: LoggerOptions): Logger;
|
|
27
|
+
export default logger;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Utility
|
|
3
|
+
* Centralized logging with levels and formatting
|
|
4
|
+
*/
|
|
5
|
+
const LEVEL_PRIORITY = {
|
|
6
|
+
debug: 0,
|
|
7
|
+
info: 1,
|
|
8
|
+
warn: 2,
|
|
9
|
+
error: 3,
|
|
10
|
+
};
|
|
11
|
+
const LEVEL_COLORS = {
|
|
12
|
+
debug: "\x1b[90m", // gray
|
|
13
|
+
info: "\x1b[36m", // cyan
|
|
14
|
+
warn: "\x1b[33m", // yellow
|
|
15
|
+
error: "\x1b[31m", // red
|
|
16
|
+
};
|
|
17
|
+
const RESET = "\x1b[0m";
|
|
18
|
+
class Logger {
|
|
19
|
+
level;
|
|
20
|
+
prefix;
|
|
21
|
+
timestamps;
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.level = options.level || (process.env.DEBUG ? "debug" : "info");
|
|
24
|
+
this.prefix = options.prefix || "";
|
|
25
|
+
this.timestamps = options.timestamps ?? false;
|
|
26
|
+
}
|
|
27
|
+
shouldLog(level) {
|
|
28
|
+
return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[this.level];
|
|
29
|
+
}
|
|
30
|
+
format(level, message, ...args) {
|
|
31
|
+
const parts = [];
|
|
32
|
+
if (this.timestamps) {
|
|
33
|
+
parts.push(`[${new Date().toISOString()}]`);
|
|
34
|
+
}
|
|
35
|
+
parts.push(`${LEVEL_COLORS[level]}[${level.toUpperCase()}]${RESET}`);
|
|
36
|
+
if (this.prefix) {
|
|
37
|
+
parts.push(`[${this.prefix}]`);
|
|
38
|
+
}
|
|
39
|
+
parts.push(message);
|
|
40
|
+
if (args.length > 0) {
|
|
41
|
+
parts.push(...args.map(a => typeof a === "object" ? JSON.stringify(a) : String(a)));
|
|
42
|
+
}
|
|
43
|
+
return parts.join(" ");
|
|
44
|
+
}
|
|
45
|
+
debug(message, ...args) {
|
|
46
|
+
if (this.shouldLog("debug")) {
|
|
47
|
+
console.debug(this.format("debug", message, ...args));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
info(message, ...args) {
|
|
51
|
+
if (this.shouldLog("info")) {
|
|
52
|
+
console.info(this.format("info", message, ...args));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
warn(message, ...args) {
|
|
56
|
+
if (this.shouldLog("warn")) {
|
|
57
|
+
console.warn(this.format("warn", message, ...args));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
error(message, ...args) {
|
|
61
|
+
if (this.shouldLog("error")) {
|
|
62
|
+
console.error(this.format("error", message, ...args));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
child(prefix) {
|
|
66
|
+
return new Logger({
|
|
67
|
+
level: this.level,
|
|
68
|
+
prefix: this.prefix ? `${this.prefix}:${prefix}` : prefix,
|
|
69
|
+
timestamps: this.timestamps,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
setLevel(level) {
|
|
73
|
+
this.level = level;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Default logger instance
|
|
77
|
+
export const logger = new Logger();
|
|
78
|
+
// Factory for creating named loggers
|
|
79
|
+
export function createLogger(name, options) {
|
|
80
|
+
return new Logger({ ...options, prefix: name });
|
|
81
|
+
}
|
|
82
|
+
export default logger;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type CommandIntent = "execution" | "data" | "neutral";
|
|
2
|
+
export type DeploymentNetwork = "mainnet" | "testnet";
|
|
3
|
+
export interface NetworkDecisionInput {
|
|
4
|
+
intent: CommandIntent;
|
|
5
|
+
cliOverride?: DeploymentNetwork;
|
|
6
|
+
exchangeOverride?: DeploymentNetwork;
|
|
7
|
+
fallbackNetwork?: DeploymentNetwork;
|
|
8
|
+
}
|
|
9
|
+
export interface NetworkDecision {
|
|
10
|
+
network: DeploymentNetwork;
|
|
11
|
+
source: "cli-override" | "exchange-override" | "intent-default" | "fallback";
|
|
12
|
+
}
|
|
13
|
+
export declare function resolveNetworkDecision(input: NetworkDecisionInput): NetworkDecision;
|