3Branches0Tags
GL
glucryptoSync production with master template updates
4d3cf1112 days ago23Commits
typescript
import { clampHyperliquidInt, parseHyperliquidJson, resolveHyperliquidDcaSymbolEntries, } from "opentool/adapters/hyperliquid"; import { CONFIG_ENV, DEFAULT_AMOUNT_USD, DEFAULT_ASSET, DEFAULT_COUNT_BACK, DEFAULT_EXECUTION_ENV, DEFAULT_RESOLUTION, DEFAULT_SCHEDULE_CRON, DEFAULT_SLIPPAGE_BPS, TEMPLATE_CONFIG_DEFAULTS, TEMPLATE_CONFIG_ENV_VAR, TEMPLATE_CONFIG_VERSION, } from "./defaults"; import { configSchema, TEMPLATE_CONFIG_SCHEMA } from "./schema"; import type { DcaAgentConfig, ScheduleConfig, } from "./types"; export const DCA_AGENT_TEMPLATE_CONFIG = { version: TEMPLATE_CONFIG_VERSION, schema: TEMPLATE_CONFIG_SCHEMA, defaults: TEMPLATE_CONFIG_DEFAULTS, envVar: TEMPLATE_CONFIG_ENV_VAR, }; function sanitizeConfigCandidate(value: unknown): unknown { if (!value || typeof value !== "object" || Array.isArray(value)) { return value; } const record = value as Record<string, unknown>; const execution = record.execution && typeof record.execution === "object" && !Array.isArray(record.execution) ? (record.execution as Record<string, unknown>) : null; if (!execution) { return value; } const slippageBps = execution.slippageBps; if (typeof slippageBps !== "number" || !Number.isFinite(slippageBps)) { return value; } return { ...record, execution: { ...execution, // Keep starter-generated execution config within schema limits so the // rest of the template payload still parses. slippageBps: Math.max(0, Math.min(500, slippageBps)), }, }; } export function readConfig(): DcaAgentConfig { const envConfig = sanitizeConfigCandidate( parseHyperliquidJson(process.env[CONFIG_ENV] ?? null), ); const parsed = configSchema.safeParse(envConfig); const input = parsed.success ? parsed.data : {}; const amountUsd = input.amountUsd ?? DEFAULT_AMOUNT_USD; const base = { platform: "hyperliquid" as const, allocationMode: "fixed" as const, amountUsd, schedule: { cron: input.schedule?.cron ?? DEFAULT_SCHEDULE_CRON, enabled: input.schedule?.enabled ?? false, notifyEmail: input.schedule?.notifyEmail ?? false, }, resolution: input.resolution ?? DEFAULT_RESOLUTION, countBack: input.countBack ?? DEFAULT_COUNT_BACK, }; const execution = input.execution ? { enabled: input.execution.enabled ?? false, environment: input.execution.environment ?? DEFAULT_EXECUTION_ENV, slippageBps: clampHyperliquidInt( input.execution.slippageBps, 0, 500, DEFAULT_SLIPPAGE_BPS, ), ...(input.execution.symbol ? { symbol: input.execution.symbol } : {}), ...(input.execution.leverage ? { leverage: input.execution.leverage } : {}), } : undefined; const asset = (input.asset ?? DEFAULT_ASSET).toUpperCase(); const symbols = resolveHyperliquidDcaSymbolEntries( input.dca?.symbols as Array<{ symbol: string; weight?: number } | string> | undefined, asset, ); return { ...base, signalType: "dca", asset, dca: { preset: input.dca?.preset ?? asset, symbols, slippageBps: input.dca?.slippageBps ?? DEFAULT_SLIPPAGE_BPS, }, ...(execution ? { execution } : {}), }; } export function resolveScheduleConfig(config: DcaAgentConfig): ScheduleConfig { return config.schedule; } export function resolveProfileAssetSymbols(config: DcaAgentConfig): string[] { const symbols = config.dca?.symbols?.map((entry) => entry.symbol) ?? []; const normalized = symbols .filter((symbol): symbol is string => typeof symbol === "string") .map((symbol) => symbol.trim()) .filter((symbol) => symbol.length > 0); if (normalized.length > 0) { return Array.from(new Set(normalized)); } const fallback = config.execution?.symbol ?? config.asset ?? DEFAULT_ASSET; const normalizedFallback = fallback.trim(); return normalizedFallback ? [normalizedFallback] : []; } export function resolveProfileAssets(config: DcaAgentConfig): Array<{ venue: "hyperliquid"; chain: "hyperliquid" | "hyperliquid-testnet"; assetSymbols: string[]; pair?: string; leverage?: number; }> { const environment = (config.execution?.environment ?? "mainnet") as "mainnet" | "testnet"; const chain = environment === "testnet" ? "hyperliquid-testnet" : "hyperliquid"; const symbols = resolveProfileAssetSymbols(config) .map((symbol) => symbol.trim()) .filter((symbol) => symbol.length > 0); if (symbols.length === 0) { return []; } return [ { venue: "hyperliquid", chain, assetSymbols: symbols, ...(typeof config.execution?.leverage === "number" ? { leverage: config.execution.leverage } : {}), }, ]; }