import type { Address, Hex } from "viem"; /** * Typed client for the `lucidlypro-indexer` GraphQL API. Mirrors the queries the Lucidly Pro UI runs * (`idxVaults`, `idxRateHistory`, `idxDeposits`, `idxShareTransfers`, `idxPolicyRequests`) so the CLI, * the agent, and downstream tooling speak to exactly the same shape the UI does. * * Set `url` to your Ponder endpoint (e.g. http://localhost:42069 or your prod URL); the client * appends `/graphql` if it isn't already there. Reads are stateless POSTs — no signer required. */ export interface IndexedVault { chainId: number; nonce: number; // per-chain deploy sequence: 1, 2, 3, … vault: Address; strategist: Address; baseAsset: Address; rolesAuthority: Address; // needed to reconstruct a Deployed for the CLI manager: Address; // ditto (ManagerWithMerkleVerification) policyManager: Address; accountant: Address; teller: Address; rate: string | null; // bigint as string (GraphQL renders bigint as string) policyCount: number; depositCount: number; createdAt: string; } export interface IndexedRateUpdate { at: string; newRate: string; oldRate: string; txHash: Hex; } export interface IndexedDeposit { nonce: string; // teller deposit nonce (bigint as string) receiver: Address; depositAsset: Address; depositAmount: string; shareAmount: string; at: string; txHash: Hex; } export interface IndexedShareTransfer { from: Address; to: Address; amount: string; at: string; txHash: Hex; } export interface IndexedPolicyRequest { chainId: number; protocolId: Hex; target: Address; selector: Hex; note: string; requestedBy: Address; requestedAt: string; txHash: Hex; } export class IndexerClient { private url: string; constructor(url: string) { this.url = url.replace(/\/+$/, ""); } /** GraphQL endpoint (auto-suffixes /graphql). */ endpoint(): string { return this.url.endsWith("/graphql") ? this.url : this.url + "/graphql"; } /** Liveness probe — returns true on HTTP 200 from /health. */ async health(): Promise { try { const r = await fetch(this.url.replace(/\/graphql$/, "") + "/health"); return r.ok; } catch { return false; } } /** Generic GraphQL POST. Returns `data` or null on failure (mirrors the UI's defensive style). */ async query(query: string, variables?: Record): Promise { try { const r = await fetch(this.endpoint(), { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ query, variables }), }); if (!r.ok) return null; const j = (await r.json()) as { data?: T }; return j.data ?? null; } catch { return null; } } // ---- typed queries (1:1 with the UI's idx* functions) ---- async vaults(limit = 200): Promise { const d = await this.query<{ vaults: { items: IndexedVault[] } }>( `query($l:Int!){ vaults(limit:$l, orderBy:"createdAt", orderDirection:"desc"){ items{ chainId nonce vault strategist baseAsset rolesAuthority manager policyManager accountant teller rate policyCount depositCount createdAt } } }`, { l: limit }, ); return d?.vaults?.items ?? null; } async rateHistory(vault: Address, limit = 1000): Promise { const d = await this.query<{ rateUpdates: { items: IndexedRateUpdate[] } }>( `query($v:String!, $l:Int!){ rateUpdates(where:{vault:$v}, orderBy:"at", orderDirection:"asc", limit:$l){ items{ at newRate oldRate txHash } } }`, { v: vault.toLowerCase(), l: limit }, ); return d?.rateUpdates?.items ?? null; } async deposits(vault: Address, limit = 200): Promise { const d = await this.query<{ deposits: { items: IndexedDeposit[] } }>( `query($v:String!, $l:Int!){ deposits(where:{vault:$v}, orderBy:"nonce", orderDirection:"desc", limit:$l){ items{ nonce receiver depositAsset depositAmount shareAmount at txHash } } }`, { v: vault.toLowerCase(), l: limit }, ); return d?.deposits?.items ?? null; } async shareTransfers(vault: Address, limit = 200): Promise { const d = await this.query<{ shareTransfers: { items: IndexedShareTransfer[] } }>( `query($v:String!, $l:Int!){ shareTransfers(where:{vault:$v}, orderBy:"blockNumber", orderDirection:"desc", limit:$l){ items{ from to amount at txHash } } }`, { v: vault.toLowerCase(), l: limit }, ); return d?.shareTransfers?.items ?? null; } async policyRequests(limit = 200): Promise { const d = await this.query<{ policyRequests: { items: IndexedPolicyRequest[] } }>( `query($l:Int!){ policyRequests(limit:$l, orderBy:"requestedAt", orderDirection:"desc"){ items{ chainId protocolId target selector note requestedBy requestedAt txHash } } }`, { l: limit }, ); return d?.policyRequests?.items ?? null; } }