import { encodeAbiParameters, parseAbiParameters, keccak256, stringToHex, type Address, type Hex, } from "viem"; import type { LucidlyClient } from "./client.js"; /** * LucidlyRegistry — chain-singleton (Arachnid CREATE2 → same address on every chain) source of truth * for v2.3 (current live): which protocols + components are audited, which policy templates * strategists may self-enable, and which assets can be a vault's base. The factory + PolicyManagers * only READ it. The registry address changed at the v2.3 cut (was 0xE604…7FB8 in v2.1/v2.2, now * 0xA621…F5be) so it had to be reseeded — currently Base + Ethereum carry the syUSD starter pack. * * This module covers the READ surface + the deterministic id helpers (so the SDK can compute the * exact `templateId` / `protocolId` / `argsHash` the on-chain registry would compute). Admin writes * (registerProtocol / addPolicyTemplate / setApprovedBaseAsset) are deliberately not surfaced — they * run from `forge script` with the registry-owner key, not from a general client. */ export const REGISTRY_V2_ADDRESS = "0xA6214AFe2a6a21f990e92257bA90A5Bf01F3F5be" as Address; /** Sentinel returned by `registry.NO_VAULT_ARG()` — also matches Solidity `type(uint16).max`. */ export const NO_VAULT_ARG = 65535; export enum PolicyStatus { Unknown = 0, Whitelisted = 1, Deprecated = 2, } export interface ProtocolEntry { /** Shared decoder/sanitizer address (used directly in leaves). */ decoder: Address; /** Per-vault TVL adapter implementation (0 = none yet). */ tvlAdapterImpl: Address; /** Per-vault oracle implementation (0 = none yet). */ oracleImpl: Address; active: boolean; } export interface PolicyTemplate { protocolId: Hex; target: Address; selector: Hex; valueIsNonZero: boolean; /** VAULT-BOUND offset, or NO_VAULT_ARG (65535) for FIXED-ARGS mode. */ vaultArgOffset: number; /** FIXED-ARGS: required keccak256(packedArgs); ignored in VAULT-BOUND. */ argsHash: Hex; status: PolicyStatus; } export const REGISTRY_V2_ABI = [ { type: "function", name: "templates", stateMutability: "view", inputs: [{ name: "templateId", type: "bytes32" }], outputs: [ { name: "protocolId", type: "bytes32" }, { name: "target", type: "address" }, { name: "selector", type: "bytes4" }, { name: "valueIsNonZero", type: "bool" }, { name: "vaultArgOffset", type: "uint16" }, { name: "argsHash", type: "bytes32" }, { name: "status", type: "uint8" }, ], }, { type: "function", name: "protocols", stateMutability: "view", inputs: [{ name: "protocolId", type: "bytes32" }], outputs: [ { name: "decoder", type: "address" }, { name: "tvlAdapterImpl", type: "address" }, { name: "oracleImpl", type: "address" }, { name: "active", type: "bool" }, ], }, { type: "function", name: "decoderFor", stateMutability: "view", inputs: [{ name: "protocolId", type: "bytes32" }], outputs: [{ type: "address" }], }, { type: "function", name: "componentImplsFor", stateMutability: "view", inputs: [{ name: "protocolId", type: "bytes32" }], outputs: [ { name: "decoder", type: "address" }, { name: "tvlAdapterImpl", type: "address" }, { name: "oracleImpl", type: "address" }, ], }, { type: "function", name: "isWhitelisted", stateMutability: "view", inputs: [{ name: "templateId", type: "bytes32" }], outputs: [{ type: "bool" }], }, { type: "function", name: "approvedBaseAssets", stateMutability: "view", inputs: [{ name: "asset", type: "address" }], outputs: [{ type: "bool" }], }, { type: "function", name: "computeTemplateId", stateMutability: "pure", inputs: [ { name: "protocolId", type: "bytes32" }, { name: "target", type: "address" }, { name: "selector", type: "bytes4" }, { name: "valueIsNonZero", type: "bool" }, { name: "vaultArgOffset", type: "uint16" }, { name: "argsHash", type: "bytes32" }, ], outputs: [{ type: "bytes32" }], }, ] as const; /** Solidity keccak256("name") — used for `protocolId`. */ export function protocolIdOf(name: string): Hex { return keccak256(stringToHex(name)); } /** keccak256 of a hex blob — used for `argsHash = keccak256(packedArgs)`. */ export function argsHashOf(packedArgsHex: Hex): Hex { return keccak256(packedArgsHex); } /** * Deterministic templateId derivation matching `LucidlyRegistry.computeTemplateId`: * keccak256(abi.encode(protocolId, target, selector, valueIsNonZero, vaultArgOffset, argsHash)). */ export function computeTemplateId(input: { protocolId: Hex; target: Address; selector: Hex; valueIsNonZero: boolean; vaultArgOffset: number; argsHash: Hex; }): Hex { const enc = encodeAbiParameters( parseAbiParameters("bytes32, address, bytes4, bool, uint16, bytes32"), [ input.protocolId, input.target, input.selector, input.valueIsNonZero, input.vaultArgOffset, input.argsHash ?? (("0x" + "0".repeat(64)) as Hex), ], ); return keccak256(enc); } export class RegistryModule { /** Chain-invariant registry address. Override with `setAddress()` for a non-standard deployment. */ private addr: Address = REGISTRY_V2_ADDRESS; constructor(private readonly client: LucidlyClient) {} address(): Address { return this.addr; } setAddress(a: Address): void { this.addr = a; } // ---------- pure id helpers (no RPC) ---------- protocolId = protocolIdOf; argsHash = argsHashOf; templateId = computeTemplateId; // ---------- chain reads ---------- async template(templateId: Hex): Promise { const tup = (await this.client.read({ address: this.addr, abi: REGISTRY_V2_ABI, functionName: "templates", args: [templateId], })) as readonly [Hex, Address, Hex, boolean, number, Hex, number]; return { protocolId: tup[0], target: tup[1], selector: tup[2], valueIsNonZero: tup[3], vaultArgOffset: tup[4], argsHash: tup[5], status: tup[6] as PolicyStatus, }; } async protocol(protocolId: Hex): Promise { const tup = (await this.client.read({ address: this.addr, abi: REGISTRY_V2_ABI, functionName: "protocols", args: [protocolId], })) as readonly [Address, Address, Address, boolean]; return { decoder: tup[0], tvlAdapterImpl: tup[1], oracleImpl: tup[2], active: tup[3] }; } async decoderFor(protocolId: Hex): Promise
{ return (await this.client.read({ address: this.addr, abi: REGISTRY_V2_ABI, functionName: "decoderFor", args: [protocolId], })) as Address; } async isWhitelisted(templateId: Hex): Promise { return (await this.client.read({ address: this.addr, abi: REGISTRY_V2_ABI, functionName: "isWhitelisted", args: [templateId], })) as boolean; } async isApprovedBaseAsset(asset: Address): Promise { return (await this.client.read({ address: this.addr, abi: REGISTRY_V2_ABI, functionName: "approvedBaseAssets", args: [asset], })) as boolean; } }