# @lucidly/sdk

viem-based TypeScript SDK for deploying and operating tokenized vaults on EVM chains, built on the
Se7en-Seas **Boring Vault** architecture (pinned commit `0e23e7f`, solc 0.8.21).

It mirrors the `lucidly` skill's documented surface so an agent or CLI can drive it programmatically:
deploy the full stack, whitelist strategies behind a Merkle root, grant strategists, and execute
leaf-verified strategy calls.

## Install

```bash
npm install @lucidly/sdk viem
```

## Quick start

```ts
import { LucidlyClient } from "@lucidly/sdk";
import { privateKeyToAccount } from "viem/accounts";

const client = new LucidlyClient({
  chain: "base",
  rpcUrl: process.env.RPC_URL!,
  signer: privateKeyToAccount(process.env.LUCIDLY_PK as `0x${string}`),
});

// Deploy the full Boring Vault stack + wire all roles
const d = await client.vault.deploy({
  name: "Lucidly Base USDC", symbol: "lucUSDC", decimals: 6,
  baseAsset: "USDC", allowedDepositAssets: ["USDC"], initialRate: 1_000_000n,
  strategist: me, rateUpdater: me, solver: me,
});

// Whitelist Aave (deploys decoder, builds leaves, sets the Merkle root)
await client.integrations.enable(["AaveV3"], { assets: ["USDC"] }, d.vault);

// Execute a leaf-verified strategy in one tx (batches approve + supply)
await client.strategy.aave.supply(d.vault, { asset: "USDC", amount: 1_000_000_000n });

// Read NAV for dashboards (no signer required)
const rate = await client.accountant.getRateInQuote("USDC");
```

## Modules

| Namespace            | Key methods                                                            |
|----------------------|------------------------------------------------------------------------|
| `client.vault`       | `deploy()`, `view()`, `aum()`, `setActive()`                           |
| `client.accountant`  | `getRate()`, `getRateInQuote()`, `updateRate()`                        |
| `client.teller`      | `deposit()`, `withdraw()`, `addAsset()`, `isAssetSupported()`          |
| `client.roles`       | `grantStrategist()`, `grant()`, `revoke()`, `hasRole()`, `setManageRoot()` |
| `client.integrations`| `enable()`, `rehydrate()`, `serialize()`, `leaves()`                   |
| `client.strategy`    | `execute()`, `aave.supply()`, `aave.withdraw()`, `hasLeaf()`           |
| `LucidlyAgent`       | `handle(prompt)` — NL routing with a mandatory leaf guard              |
| `client.researchTools` | 10 read-only tools for LLM-driven vault research (see below)         |
| `ResearchAgent`      | Streaming tool-use loop over Anthropic / Groq / DeepSeek / OpenAI      |

Pure Merkle helpers are exported too: `leafHash`, `leafHashWithPacked`, `buildTree`, `proofFor`, `verify`.

## Research agent

Drive an LLM through a read-only toolkit over your vault data + on-chain reads + indexer.
Four interchangeable providers — bring your own key, swap with one line:

```ts
import {
  LucidlyClient, ResearchAgent,
  AnthropicProvider, GroqProvider, DeepSeekProvider, OpenAIProvider,
} from "@lucidly/sdk";

const client = new LucidlyClient({ chain: "base", rpcUrl: process.env.BASE_RPC! });

// Pick a provider. All four implement the same `LLMProvider` interface.
const provider = new AnthropicProvider({ apiKey: process.env.ANTHROPIC_KEY! });
// const provider = new GroqProvider({ apiKey: process.env.GROQ_KEY! });           // free-tier capable
// const provider = new DeepSeekProvider({ apiKey: process.env.DEEPSEEK_KEY! });   // ~5-10× cheaper
// const provider = new OpenAIProvider({ apiKey: process.env.OPENAI_KEY!, model: "gpt-5-codex" });

const agent = new ResearchAgent(client, { provider });

// Non-streaming (CLI / batch)
const result = await agent.chat("How concentrated is ownership of vault 0xabc…?");

// Streaming (UI / live):
const ac = new AbortController();
await agent.chatStream("Show me delta-neutral policies", {
  onTextDelta: (chunk) => process.stdout.write(chunk),
  onToolCall: (call) => console.log(`\n[tool] ${call.name}`),
  onRateLimit: ({ waitSec }) => console.log(`(waiting ${waitSec}s for rate limit)`),
  abortSignal: ac.signal,                  // user-stop
  getStopped: () => false,                 // polled between iterations and tool calls
});
```

**Toolkit** (each tool's full description ships in `LUCIDLY_RESEARCH_TOOLS`):

| Tool | Purpose |
|---|---|
| `lucidly.policies`        | Per-protocol policy status for a vault (decoder/tvlAdapter/oracle clones + enabled leaves) |
| `lucidly.holders`         | Current share-token holders by balance descending |
| `lucidly.depositors`      | Recent deposit ledger entries |
| `lucidly.navHistory`      | NAV/rate update timeline |
| `lucidly.policyRequests`  | Open policy-whitelist requests in the registry |
| `morpho.market`           | Morpho Blue market state by 5-tuple market params |
| `morpho.position`         | Per-account supply/borrow/collateral shares |
| `aave.position`           | Aave V3 user account data (collateral, debt, HF) |
| `aave.market`             | Aave V3 reserve data (APYs, aToken, debt token) |
| `chain.tokenInfo`         | ERC20 metadata for formatting answers |

**Resilience**: every provider streams via SSE, auto-retries HTTP 429 (parses `Retry-After` + "try again in Xs" bodies), retries network-blip mid-stream IFF no content has been emitted yet, and honors `AbortSignal` for user-stop.

**Production keys**: for browser use, pass `dangerousDirectBrowserAccess: true` to `AnthropicProvider` AND use `apiBaseUrl` to route through your own proxy — otherwise the key is exposed in network requests.

## Merkle correctness (the critical invariant)

Leaves are `keccak256(abi.encodePacked(decoder, target, valueIsNonZero, selector, packedArgumentAddresses))`
and proofs use sorted-pair hashing, matching `ManagerWithMerkleVerification` + solmate `MerkleProofLib`
exactly. `test-solidity/MerkleParityTest.t.sol` proves byte-for-byte parity: it re-derives each leaf the
contract way and verifies the SDK's proofs through the real `MerkleProofLib`.

## Validation

All of the following are validated against an `anvil` fork of Base mainnet:

- `scripts/deploy-fork.mjs` — full deploy + role wiring + reads (14 checks)
- `scripts/strategy-fork.mjs` — enable AaveV3 → leaf-verified approve → real Aave supply/withdraw (7 checks)
- `test-solidity/MerkleParityTest.t.sol` — Solidity↔TS Merkle parity (run with `forge test`)

```bash
npm run build
BASE_RPC_URL=https://mainnet.base.org ./scripts/run-fork-tests.sh
```

## Notes

- Amounts are `bigint` base units (use `viem`'s `parseUnits`).
- Token params accept a symbol (resolved per active chain) or a raw `0x` address.
- The `vault.deploy()` contract artifacts are vendored in `src/artifacts.ts` from the pinned boring-vault build.
- Supported chains: Ethereum, Base, Arbitrum (see `src/chains.ts`). Verify addresses before mainnet use.
