/** * Strategy corpus — reference strategies used as nearest-neighbour examples by the decomposer * agent. Each entry is a real working strategy from lucidlylabs/strats, structured into a * shape the agent can reason over: * * • topology — homeChain (where shares are minted) vs positionChains (where capital lives), * with explicit bridgeEdges between chains. This matters because the four Lucidly factory * chains [1, 8453, 42161, 10] are NOT the same set as the chains where capital can land — * capital can flow anywhere bridge support + leaves exist. * • legs — per-chain leaf families used, with the rationale captured from source comments. * The decomposer doesn't try to invent leaf signatures; it picks from this catalog plus * the live policy registry via verify_policy_id. * • decisions — when does the strategy enter/rebalance/exit. Useful for the agent to surface * trigger conditions to the user. * • riskFactors — explicit risk register so the agent surfaces hazards the user must accept. * * The corpus is hand-curated, not auto-extracted, because LLM reasoning quality depends on the * rationale text more than on raw code listings. Adding a new strategy means appending a JSON * entry; no SDK change needed. * * Loading: callers can `import { STRATEGY_CORPUS } from "@lucidly/sdk"` to get the parsed array, * or `findStrategyByArchetype("delta_neutral")` for filtered lookups. */ import corpusJson from "./data/strategy-corpus.json" with { type: "json" }; // ─── Wire types ────────────────────────────────────────────────────────────── export type StrategyArchetype = | "leveraged_carry" | "lp_hedge" | "yield_basket" | "delta_neutral" | (string & { readonly _extensible: true }); export interface CorpusLeafFamily { /** Protocol the leaf belongs to — e.g. "Morpho Blue", "Aave V3", "Pendle", "ERC20". */ protocol: string; /** Function signature (e.g. "supplyCollateral((address,address,address,address,uint256),uint256,address,bytes)"). */ selector: string; /** Target contract or token symbol if it's an approve. */ target: string; /** For approves: who's being approved. */ spender?: string; /** Human-readable rationale captured from source comments — the decomposer reads this. */ rationale: string; } export interface CorpusLeg { /** EVM chain id where this leg executes. */ chain: number; /** Pretty chain name for the UI ("ethereum", "base", "hyperevm", etc.). */ chainName: string; /** What kind of leg — "onchain" (sent through the boring vault manager) or "api" (venue API). */ kind: "onchain" | "api"; /** Purpose tag — open_position, close_position, rebalance, fund_hl_margin, etc. */ purpose: string; /** Ordered leaf families used in this leg. */ leafFamilies: CorpusLeafFamily[]; /** Path back to the source Python file for the agent to cite. */ sourcePath: string; /** Human-readable rationale for the leg as a whole. */ rationale: string; } export interface CorpusBridgeEdge { /** Source chain id. */ from: number; /** Destination chain id. */ to: number; /** Bridge rail — "CCTP", "CCIP", "LZ", "HL_CORE_BRIDGE", etc. */ rail: string; /** Asset being bridged. */ asset: string; rationale: string; } export interface CorpusTopology { /** Where vault shares are minted. One of the Lucidly factory chains [1, 8453, 42161, 10]. */ homeChain: number; /** Every chain where capital actually lives. Can include non-factory chains (Hyperliquid, etc.). */ positionChains: number[]; /** Cross-chain capital flows. Empty for single-chain strategies. */ bridgeEdges: CorpusBridgeEdge[]; } export interface CorpusDecision { /** Stage name — open_position, rebalance, close, emergency_unwind, etc. */ trigger: string; /** When this fires (free-text condition in source-code style). */ condition: string; /** What happens. */ action: string; } export interface CorpusRiskFactors { leverage: string; oracle: string; custody: string; bridge: string; smartContract: string; [extraDimension: string]: string; } export interface CorpusReferenceSource { repo: string; branch: string; path: string; leafFile: string; } export interface CorpusStrategy { /** Stable id, e.g. "delta_neutral.pendle_pt_sena_hyperliquid_short". */ id: string; displayName: string; archetype: StrategyArchetype; summary: string; thesis: string; assets: { depositInputs: string[]; exitOutputs: string[]; intermediateAssets: string[]; }; topology: CorpusTopology; legs: CorpusLeg[]; decisions: CorpusDecision[]; riskFactors: CorpusRiskFactors; referenceSource: CorpusReferenceSource; } export interface StrategyCorpus { apiVersion: string; generatedAt: string; description: string; archetypeFamilies: Record; topologyNote: string; strategies: CorpusStrategy[]; } // ─── Loaded corpus ─────────────────────────────────────────────────────────── const corpus = corpusJson as unknown as StrategyCorpus; /** The full strategy corpus, statically imported. Safe to use at module-load time. */ export const STRATEGY_CORPUS: StrategyCorpus = corpus; /** Bare strategies array for callers who just want to iterate. */ export const STRATEGY_CORPUS_ENTRIES: CorpusStrategy[] = corpus.strategies; /** Lookup table by id for O(1) access. */ const byId: Map = new Map(corpus.strategies.map((s) => [s.id, s])); /** Get a strategy by id. Returns undefined if not found. */ export function findStrategyById(id: string): CorpusStrategy | undefined { return byId.get(id); } /** Filter strategies by archetype. */ export function findStrategiesByArchetype(archetype: StrategyArchetype): CorpusStrategy[] { return corpus.strategies.filter((s) => s.archetype === archetype); } /** Filter strategies that touch a given chain (in either positionChains or as a bridge edge endpoint). */ export function findStrategiesByChain(chainId: number): CorpusStrategy[] { return corpus.strategies.filter((s) => { if (s.topology.positionChains.includes(chainId)) return true; if (s.topology.bridgeEdges.some((e) => e.from === chainId || e.to === chainId)) return true; return false; }); } /** Filter strategies that touch a given protocol name (case-insensitive substring match on leafFamilies.protocol). */ export function findStrategiesByProtocol(protocol: string): CorpusStrategy[] { const needle = protocol.toLowerCase(); return corpus.strategies.filter((s) => s.legs.some((leg) => leg.leafFamilies.some((lf) => lf.protocol.toLowerCase().includes(needle))), ); } /** Strategies that involve bridging (positionChains.length > 1 or non-empty bridgeEdges). */ export function findCrossChainStrategies(): CorpusStrategy[] { return corpus.strategies.filter( (s) => s.topology.positionChains.length > 1 || s.topology.bridgeEdges.length > 0, ); } /** Stats — useful for diagnostic endpoints and UI. */ export function corpusStats(): { totalStrategies: number; archetypes: string[]; chains: number[]; crossChainCount: number; } { const archetypes = Array.from(new Set(corpus.strategies.map((s) => s.archetype))) as string[]; const chainSet = new Set(); for (const s of corpus.strategies) { s.topology.positionChains.forEach((c) => chainSet.add(c)); s.topology.bridgeEdges.forEach((e) => { chainSet.add(e.from); chainSet.add(e.to); }); } return { totalStrategies: corpus.strategies.length, archetypes, chains: Array.from(chainSet).sort((a, b) => a - b), crossChainCount: findCrossChainStrategies().length, }; }