@zodiac-os/sdk 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/config.d.mts +21 -1
- package/dist/cli/config.mjs.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli/config.d.mts
CHANGED
|
@@ -24,7 +24,27 @@ interface ZodiacConfig {
|
|
|
24
24
|
*/
|
|
25
25
|
typesDir?: string;
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Loose base used as the *inference* constraint for `defineConfig`.
|
|
29
|
+
* `contracts` is `Record<string, unknown>` here so `const T` can preserve the
|
|
30
|
+
* caller's exact address literals rather than collapsing them into the
|
|
31
|
+
* recursive `ContractsNode` union.
|
|
32
|
+
*/
|
|
33
|
+
type DefineConfigInput = {
|
|
34
|
+
apiKey: `zodiac_${string}`;
|
|
35
|
+
contracts?: Record<string, unknown>;
|
|
36
|
+
abisDir?: string;
|
|
37
|
+
typesDir?: string;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Recursive leaf-level check: every leaf in `contracts` must be
|
|
41
|
+
* `` `0x${string}` ``; any other value collapses the branch to `never`,
|
|
42
|
+
* which surfaces as a type error at the call site.
|
|
43
|
+
*/
|
|
44
|
+
type ValidateContracts<C> = { [K in keyof C]: C[K] extends `0x${string}` ? C[K] : C[K] extends object ? ValidateContracts<C[K]> : never };
|
|
45
|
+
declare const defineConfig: <const T extends DefineConfigInput>(config: T & {
|
|
46
|
+
contracts?: ValidateContracts<NonNullable<T["contracts"]>>;
|
|
47
|
+
}) => T;
|
|
28
48
|
declare function loadConfig(configPath?: string): Promise<ZodiacConfig>;
|
|
29
49
|
declare const DEFAULT_ABIS_DIR = "abis";
|
|
30
50
|
declare const DEFAULT_TYPES_DIR = ".zodiac-os";
|
package/dist/cli/config.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.mjs","names":[],"sources":["../../src/cli/config.ts"],"sourcesContent":["import { pathToFileURL } from 'url'\nimport { resolve } from 'path'\n\nexport type Contracts = {\n [chain: string]: ContractsNode\n}\nexport type ContractsNode = `0x${string}` | { [name: string]: ContractsNode }\n\nexport interface ZodiacConfig {\n apiKey: `zodiac_${string}`\n /**\n * Contracts the `allow` kit should know about, keyed by chain prefix.\n * Nested objects are allowed for grouping related addresses.\n */\n contracts?: Contracts\n /**\n * Directory where fetched ABIs are stored and read from.\n * Resolved relative to the project root (cwd). Defaults to `./abis`.\n */\n abisDir?: string\n /**\n * Directory where generated type declarations (e.g. `allow.d.ts`) are\n * written. Resolved relative to the project root (cwd).\n * Defaults to `./.zodiac-os`.\n */\n typesDir?: string\n}\n\nexport const defineConfig = (config:
|
|
1
|
+
{"version":3,"file":"config.mjs","names":[],"sources":["../../src/cli/config.ts"],"sourcesContent":["import { pathToFileURL } from 'url'\nimport { resolve } from 'path'\n\nexport type Contracts = {\n [chain: string]: ContractsNode\n}\nexport type ContractsNode = `0x${string}` | { [name: string]: ContractsNode }\n\nexport interface ZodiacConfig {\n apiKey: `zodiac_${string}`\n /**\n * Contracts the `allow` kit should know about, keyed by chain prefix.\n * Nested objects are allowed for grouping related addresses.\n */\n contracts?: Contracts\n /**\n * Directory where fetched ABIs are stored and read from.\n * Resolved relative to the project root (cwd). Defaults to `./abis`.\n */\n abisDir?: string\n /**\n * Directory where generated type declarations (e.g. `allow.d.ts`) are\n * written. Resolved relative to the project root (cwd).\n * Defaults to `./.zodiac-os`.\n */\n typesDir?: string\n}\n\n/**\n * Loose base used as the *inference* constraint for `defineConfig`.\n * `contracts` is `Record<string, unknown>` here so `const T` can preserve the\n * caller's exact address literals rather than collapsing them into the\n * recursive `ContractsNode` union.\n */\ntype DefineConfigInput = {\n apiKey: `zodiac_${string}`\n contracts?: Record<string, unknown>\n abisDir?: string\n typesDir?: string\n}\n\n/**\n * Recursive leaf-level check: every leaf in `contracts` must be\n * `` `0x${string}` ``; any other value collapses the branch to `never`,\n * which surfaces as a type error at the call site.\n */\ntype ValidateContracts<C> = {\n [K in keyof C]: C[K] extends `0x${string}`\n ? C[K]\n : C[K] extends object\n ? ValidateContracts<C[K]>\n : never\n}\n\nexport const defineConfig = <const T extends DefineConfigInput>(\n config: T & {\n contracts?: ValidateContracts<NonNullable<T['contracts']>>\n }\n): T => config\n\nconst DEFAULT_CONFIG_PATH = 'zodiac.config.ts'\n\nexport async function loadConfig(\n configPath: string = DEFAULT_CONFIG_PATH\n): Promise<ZodiacConfig> {\n const absolutePath = resolve(process.cwd(), configPath)\n\n let mod: Record<string, unknown>\n try {\n mod = await import(pathToFileURL(absolutePath).href)\n } catch (error: any) {\n if (error?.code === 'ERR_MODULE_NOT_FOUND' || error?.code === 'ENOENT') {\n throw new Error(`Config file not found: ${absolutePath}`)\n }\n throw error\n }\n\n const config = (mod.default ?? mod.config) as ZodiacConfig | undefined\n if (!config) {\n throw new Error(\n `Config file must export a default value or a named \"config\" export: ${absolutePath}`\n )\n }\n\n if (!config.apiKey) {\n throw new Error(`Config is missing required field \"apiKey\"`)\n }\n\n return config\n}\n\nexport const DEFAULT_ABIS_DIR = 'abis'\nexport const DEFAULT_TYPES_DIR = '.zodiac-os'\n\nexport function resolveAbisDir(config: ZodiacConfig): string {\n return resolve(process.cwd(), config.abisDir ?? DEFAULT_ABIS_DIR)\n}\n\nexport function resolveTypesDir(config: ZodiacConfig): string {\n return resolve(process.cwd(), config.typesDir ?? DEFAULT_TYPES_DIR)\n}\n"],"mappings":";;;AAsDA,MAAa,gBACX,WAGM;AAER,MAAM,sBAAsB;AAE5B,eAAsB,WACpB,aAAqB,qBACE;CACvB,MAAM,eAAe,QAAQ,QAAQ,KAAK,EAAE,WAAW;CAEvD,IAAI;AACJ,KAAI;AACF,QAAM,MAAM,OAAO,cAAc,aAAa,CAAC;UACxC,OAAY;AACnB,MAAI,OAAO,SAAS,0BAA0B,OAAO,SAAS,SAC5D,OAAM,IAAI,MAAM,0BAA0B,eAAe;AAE3D,QAAM;;CAGR,MAAM,SAAU,IAAI,WAAW,IAAI;AACnC,KAAI,CAAC,OACH,OAAM,IAAI,MACR,uEAAuE,eACxE;AAGH,KAAI,CAAC,OAAO,OACV,OAAM,IAAI,MAAM,4CAA4C;AAG9D,QAAO;;AAGT,MAAa,mBAAmB;AAChC,MAAa,oBAAoB;AAEjC,SAAgB,eAAe,QAA8B;AAC3D,QAAO,QAAQ,QAAQ,KAAK,EAAE,OAAO,WAAA,OAA4B;;AAGnE,SAAgB,gBAAgB,QAA8B;AAC5D,QAAO,QAAQ,QAAQ,KAAK,EAAE,OAAO,YAAA,aAA8B"}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/constellation.ts","../src/apply.ts"],"sourcesContent":["/// <reference path=\"./zodiac-os-codegen.d.ts\" />\nimport type { Address, ChainId } from '@zodiac-os/api-types'\nimport type { AllowanceSpec } from './types'\nimport type { Annotation, Permission, PermissionSet } from 'zodiac-roles-sdk'\nimport { createRequire } from 'module'\nimport type * as ZodiacOsCodegen from '.zodiac-os'\nimport { UUID } from 'crypto'\n\n/**\n * A role definition keyed by role name. Permissions are expanded into\n * `{ targets, annotations }` via `processPermissions` at `apply()` time.\n */\nexport type RoleDef = {\n members: readonly AddressOrRef[]\n permissions: readonly (Permission | PermissionSet | Promise<PermissionSet>)[]\n annotations?: readonly Annotation[]\n}\n\ntype User = {\n id: UUID\n fullName: string\n personalSafes: Record<\n number,\n { address: Lowercase<Address>; active: boolean }\n >\n}\n\ntype Vault = {\n id: UUID\n label: string\n address: Lowercase<Address>\n chain: ChainId\n threshold: number\n owners: readonly string[]\n modules: readonly string[]\n}\n\ntype WorkspaceVaults = {\n workspaceId: UUID\n workspaceName: string\n vaults: Readonly<Record<string, Vault>>\n}\n\n/** Shape of the codegen data produced by `zodiac-os pull-org`. */\nexport type CodegenData = {\n users: Readonly<Record<string, User>>\n vaults: Readonly<Record<string, WorkspaceVaults>>\n}\n\ntype GeneratedCodegen = {\n users: typeof ZodiacOsCodegen.users\n vaults: typeof ZodiacOsCodegen.vaults\n}\n\ntype ConstellationOpts<C extends CodegenData> = {\n /** Workspace to scope vaults and roles to. */\n workspace: keyof C['vaults'] & string\n /** Human-readable label for this constellation. */\n label: string\n /** Target chain for all nodes in this constellation. */\n chain: ChainId\n}\n\ntype ConstellationInternalOpts<C extends CodegenData> = {\n /** Injected codegen data (used for testing). */\n codegen?: C\n}\n\ntype Prettify<T> = { readonly [K in keyof T]: T[K] } & {}\n\ntype WorkspaceVaultEntries<\n C extends CodegenData,\n W extends keyof C['vaults'],\n> = C['vaults'][W]['vaults']\n\ntype NodeType = 'SAFE' | 'ROLES' | 'DELAY'\n\n/** A reference to a node used in `owners`, `modules`, `target`, etc. */\ntype NodeRef = Readonly<{ type: NodeType; label: string; chain: ChainId }>\n\n/** A blockchain address (checksummed or lowercase) or a reference to another\n * node in the constellation. Values are normalized to lowercase before being\n * sent to the API. */\ntype AddressOrRef = Address | NodeRef\n\ntype NodeBase = Readonly<{\n /** Human-readable identifier, unique within the constellation. */\n label: string\n /** Chain the node is deployed on. */\n chain: ChainId\n /** Set for existing nodes from codegen, absent for new nodes. */\n address?: Lowercase<Address>\n /** Deployment nonce — required for new nodes, optional for existing. */\n nonce?: bigint\n}>\n\n/** A safe node spec — existing vault ref or new safe with required config. */\nexport type SafeNode = NodeBase &\n Readonly<{\n /** Discriminator identifying this node as a Safe. */\n type: 'SAFE'\n /** Number of owner signatures required to execute a transaction. */\n threshold: number\n /** Safe owner addresses or node references. */\n owners: readonly (string | NodeRef)[]\n /** Module addresses or node references enabled on the safe. */\n modules?: readonly (string | NodeRef)[]\n /** Whether this safe shall appear as a vault in the workspace. @default false */\n vault?: boolean\n }>\n\n/** A roles modifier node spec — existing vault ref or new roles with modifier config. */\nexport type RolesNode = NodeBase &\n Readonly<{\n /** Discriminator identifying this node as a Roles modifier. */\n type: 'ROLES'\n /** The safe that this roles modifier controls. */\n target?: AddressOrRef\n /** The account that is allowed to update the configuration of the Roles mod. */\n owner?: AddressOrRef\n /** The account that calls will be executed from. */\n avatar?: AddressOrRef\n /** MultiSend contract addresses for batched transactions. */\n multisend?: readonly Address[]\n /** Role definitions configured on this modifier. */\n roles?: Record<string, RoleDef>\n /** Spending allowances configured on this modifier. */\n allowances?: readonly AllowanceSpec[]\n }>\n\n/** Any complete node that can be passed to `apply()`. */\nexport type ConstellationNode = SafeNode | RolesNode\nexport type ConstellationNodeInternal = ConstellationNode & {\n _constellation: ConstellationMeta\n}\n\ntype NewSafeProps = {\n /** Deployment nonce for CREATE2 address derivation. */\n nonce: bigint\n /** Number of owner signatures required to execute a transaction. */\n threshold: number\n /** Safe owner addresses or node references. */\n owners: readonly AddressOrRef[]\n /** Module addresses or node references to enable on the safe. */\n modules?: readonly AddressOrRef[]\n /** Whether this safe is a workspace vault. @default false */\n vault?: boolean\n}\n\ntype NewRolesProps = {\n /** Deployment nonce for CREATE2 address derivation. Defaults to `0n` when omitted. */\n nonce?: bigint\n /** The safe that this roles modifier controls. Defaults to the new safe with the same label, when one exists. */\n target?: AddressOrRef\n /** The account that calls will be executed from. Defaults to `target` value */\n avatar?: AddressOrRef\n /** The account that is allowed to update the configuration of the Roles Mod. Defaults to `target` value */\n owner?: AddressOrRef\n /** MultiSend contract addresses for batched transactions. Defaults to `['0x38869bf66a61cf6bdb996a6ae40d5853fd43b526', '0x9641d764fc13c8b624c04430c7356c1c7c8102e2']` */\n multisend?: readonly Address[]\n /** Role definitions to configure on this modifier. */\n roles?: Record<string, RoleDef>\n /** Spending allowances to configure on this modifier. */\n allowances?: readonly AllowanceSpec[]\n}\n\ntype EntityAccessor<\n Type extends string,\n Entries extends Record<string, any>,\n Ch extends ChainId = ChainId,\n NP extends Record<string, any> = Record<string, any>,\n> = {\n readonly [K in\n | (keyof Entries & string)\n | (string & {})]: K extends keyof Entries & string\n ? Readonly<Prettify<Entries[K] & { type: Type; label: K; chain: Ch }>> &\n (<\n O extends {\n [P in Exclude<keyof Entries[K] & string, 'id' | 'label'>]?: any\n } & Partial<NP> = {},\n >(\n overrides?: {\n [P in Exclude<keyof Entries[K] & string, 'id' | 'label'>]?: any\n } & Partial<NP> &\n O\n ) => Readonly<\n Prettify<\n Omit<Entries[K], keyof O> &\n O &\n Partial<NP> & { type: Type; label: K; chain: Ch }\n >\n >)\n : Readonly<Prettify<{ type: Type; label: string; chain: Ch }>> &\n ((\n props: NP\n ) => Readonly<Prettify<NP & { type: Type; label: string; chain: Ch }>>)\n}\n\ntype UserAccessor<C extends CodegenData, Ch extends number> = {\n readonly [K in keyof C['users'] &\n string]: C['users'][K]['personalSafes'][Ch]['address']\n}\n\ntype ConstellationResult<\n C extends CodegenData,\n W extends keyof C['vaults'] = keyof C['vaults'],\n Ch extends ChainId = ChainId,\n> = {\n /** Access existing safes by label or create new ones with a new label. */\n safe: EntityAccessor<'SAFE', WorkspaceVaultEntries<C, W>, Ch, NewSafeProps>\n /** Access existing roles modifiers by label or create new ones with a new label. */\n roles: EntityAccessor<'ROLES', WorkspaceVaultEntries<C, W>, Ch, NewRolesProps>\n /** Resolve a user's personal safe address on the constellation's chain. */\n user: UserAccessor<C, Ch>\n}\n\n/** @internal */\nexport type ConstellationMeta = {\n label: string\n chain: ChainId\n workspaceId: UUID\n}\n\nfunction loadCodegen(): CodegenData {\n const require = createRequire(import.meta.url)\n return require('.zodiac-os') as CodegenData\n}\n\n/**\n * Creates a constellation scoped to a workspace and chain.\n *\n * Use bracket access to reference existing vaults or define new nodes:\n * ```ts\n * const eth = constellation({ workspace: 'GG', label: 'my constellation', chain: 1 })\n *\n * const dao = eth.safe['GG DAO'] // existing vault ref\n * const roles = eth.roles['GG DAO'] // existing roles ref\n * const newSafe = eth.safe['New Safe']({ nonce: 0n, threshold: 2, owners: [...], modules: [...] })\n * ```\n */\nexport function constellation<\n const C extends CodegenData = GeneratedCodegen,\n const W extends keyof C['vaults'] & string = keyof C['vaults'] & string,\n const Ch extends ChainId = ChainId,\n>(\n opts: ConstellationOpts<C> & { workspace: W; chain: Ch },\n internal?: ConstellationInternalOpts<C>\n): ConstellationResult<C, W, Ch> {\n const codegen: CodegenData = internal?.codegen ?? loadCodegen()\n\n const ws = codegen.vaults[opts.workspace]\n const vaultsByLabel: Record<string, Vault> = {}\n if (ws) {\n for (const [label, vault] of Object.entries(ws.vaults)) {\n vaultsByLabel[label] = vault\n }\n }\n\n const meta: ConstellationMeta = {\n label: opts.label,\n chain: opts.chain,\n workspaceId: (ws?.workspaceId ?? '') as UUID,\n }\n\n const newSafes = new Map<string, Readonly<Record<string, any>>>()\n\n function makeNodeRef(\n data: Record<string, any>\n ): Readonly<Record<string, any>> {\n const ref: Record<string, any> = Object.freeze({\n ...data,\n chain: opts.chain,\n _constellation: meta,\n })\n if (ref.type === 'SAFE' && typeof ref.label === 'string') {\n newSafes.set(ref.label, ref)\n }\n return ref\n }\n\n function entityAccessor(\n registry: Record<string, Record<string, any>>,\n type: string,\n resolveCanonicalSafe?: (name: string) => Record<string, any> | undefined\n ) {\n const cache = new Map<string, Record<string, any>>()\n return new Proxy({} as Record<string, any>, {\n get(_target: any, name: string) {\n if (typeof name !== 'string') return undefined\n const cached = cache.get(name)\n if (cached) return cached\n const existing = registry[name]\n const fn = (overrides?: Record<string, any>) => {\n const canonicalSafe =\n resolveCanonicalSafe && !overrides?.target\n ? resolveCanonicalSafe(name)\n : undefined\n if (canonicalSafe) {\n return makeNodeRef({\n type,\n nonce: 0n,\n target: canonicalSafe,\n owner: canonicalSafe,\n avatar: canonicalSafe,\n ...overrides,\n label: name,\n })\n }\n return makeNodeRef({\n type,\n ...(existing || {}),\n ...overrides,\n label: name,\n })\n }\n Object.assign(fn, {\n type,\n ...(existing || {}),\n label: name,\n chain: opts.chain,\n _constellation: meta,\n })\n cache.set(name, fn)\n return fn\n },\n })\n }\n\n function userAccessor() {\n return new Proxy(\n {},\n {\n get(_target: any, name: string) {\n const user = codegen.users[name]\n if (!user) throw new Error(`Unknown user: ${name}`)\n const personalSafe = user.personalSafes[opts.chain]\n if (!personalSafe) {\n throw new Error(\n `User ${name} has no personal safe on chain ${opts.chain}`\n )\n }\n return personalSafe.address\n },\n }\n )\n }\n\n const safe = entityAccessor(vaultsByLabel, 'SAFE')\n const roles = entityAccessor(vaultsByLabel, 'ROLES', (name) => {\n const invoked = newSafes.get(name)\n if (invoked) return invoked\n if (name in vaultsByLabel) return safe[name]\n return undefined\n })\n\n return {\n safe,\n roles,\n user: userAccessor(),\n } as ConstellationResult<C, W, Ch>\n}\n","import type {\n ApplyConstellationPayload,\n ApplyConstellationResult,\n} from '@zodiac-os/api-types'\nimport { invariant } from '@epic-web/invariant'\nimport { processPermissions } from 'zodiac-roles-sdk'\nimport type { PermissionSet } from 'zodiac-roles-sdk'\nimport { ApiClient } from './api'\nimport type {\n ConstellationMeta,\n ConstellationNode,\n ConstellationNodeInternal,\n RoleDef,\n} from './constellation'\n\ntype ApplyOpts = {\n /** API client instance. Defaults to a client configured from environment variables. */\n api?: ApiClient\n}\n\n/**\n * Resolves node references and applies the constellation specification via the API.\n *\n *\n * ```ts\n * const eth = constellation({ workspace: 'GG', label: 'my constellation', chain: 1 })\n * const dao = eth.safe['GG DAO']\n * const roles = eth.roles['New Roles']({ nonce: 0n, target: dao, owner: dao, avatar: dao })\n *\n * await apply([dao, roles])\n * ```\n */\nexport async function apply(\n nodes: ConstellationNode[] | { [key: string]: ConstellationNode },\n opts?: ApplyOpts\n): Promise<ApplyConstellationResult[]> {\n const api = opts?.api ?? new ApiClient()\n const refs = deriveRefs(nodes)\n\n // Group nodes by constellation (multiple constellations can be applied with a single call)\n const groups = new Map<\n string,\n { meta: ConstellationMeta; nodes: ConstellationNodeInternal[] }\n >()\n\n for (const node of refs.byIdentity.keys()) {\n const meta = node._constellation\n invariant(\n meta,\n `Node \"${node.label}\" is not associated with a constellation`\n )\n const key = `${meta.workspaceId}:${meta.chain}:${meta.label}`\n let group = groups.get(key)\n if (!group) {\n group = { meta, nodes: [] }\n groups.set(key, group)\n }\n group.nodes.push(node)\n }\n\n const results: ApplyConstellationResult[] = []\n for (const { meta, nodes: groupNodes } of groups.values()) {\n const specification = await Promise.all(\n groupNodes.map((n) => nodeToSpec(n, refs))\n )\n const result = await api.applyConstellation(meta.workspaceId, {\n label: meta.label,\n chain: meta.chain,\n specification,\n })\n results.push(result)\n }\n\n return results\n}\n\ntype RefsIndex = {\n byIdentity: Map<ConstellationNodeInternal, string>\n byLabel: Map<string, string>\n}\n\nfunction labelKey(node: ConstellationNodeInternal): string {\n return `${node._constellation.workspaceId}:${node.type}:${node.label}`\n}\n\nfunction deriveRefs(\n nodes: ConstellationNode[] | { [key: string]: ConstellationNode }\n): RefsIndex {\n const byIdentity = new Map<ConstellationNodeInternal, string>()\n const byLabel = new Map<string, string>()\n\n const register = (node: ConstellationNodeInternal, ref: string) => {\n byIdentity.set(node, ref)\n byLabel.set(labelKey(node), ref)\n }\n\n if (Array.isArray(nodes)) {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]\n invariant(\n isConstellationNode(node),\n `unexpected node input at index: ${i}`\n )\n register(node, `${i}`)\n }\n } else {\n for (const [key, node] of Object.entries(nodes)) {\n invariant(\n isConstellationNode(node),\n `unexpected node input under key: ${key}`\n )\n register(node, key)\n }\n }\n\n return { byIdentity, byLabel }\n}\n\nasync function nodeToSpec(\n node: ConstellationNodeInternal,\n refs: RefsIndex\n): Promise<ApplyConstellationPayload['specification'][number]> {\n const { id, _constellation, ...rest } = node as Record<string, any>\n const spec: Record<string, any> = {}\n\n for (const [key, value] of Object.entries(rest)) {\n if (node.type === 'ROLES' && key === 'roles' && value != null) {\n spec.roles = await expandRoles(value as Record<string, RoleDef>, refs)\n continue\n }\n spec[key] = resolveRefs(value, refs)\n }\n\n spec.ref = refs.byIdentity.get(node)\n invariant(spec.ref != null, 'ref not found')\n\n return stringifyBigints(\n spec\n ) as ApplyConstellationPayload['specification'][number]\n}\n\nasync function expandRoles(\n roles: Record<string, RoleDef>,\n refs: RefsIndex\n): Promise<unknown[]> {\n return Promise.all(\n Object.entries(roles).map(async ([key, def]) => {\n const resolvedPermissions = (await Promise.all(\n def.permissions.map((p) => Promise.resolve(p))\n )) as Parameters<typeof processPermissions>[0][number][]\n const { targets, annotations } = processPermissions(resolvedPermissions)\n return {\n key,\n members: resolveRefs(def.members, refs),\n targets,\n annotations: [...(def.annotations ?? []), ...annotations],\n }\n })\n )\n}\n\nfunction resolveRefs(value: unknown, refs: RefsIndex): unknown {\n if (isConstellationNode(value)) {\n const ref = refs.byIdentity.get(value) ?? refs.byLabel.get(labelKey(value))\n invariant(\n ref != null,\n `Node \"${value.label}\" is referenced not included in the apply() call`\n )\n return `$${ref}`\n }\n\n if (Array.isArray(value)) {\n return value.map((v) => resolveRefs(v, refs))\n }\n\n if (typeof value === 'object' && value !== null) {\n const resolved: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value)) {\n resolved[k] = resolveRefs(v, refs)\n }\n return resolved\n }\n\n // Normalize checksummed addresses to lowercase — the API stores addresses\n // lowercase, and accepting checksummed input at the type level would\n // otherwise smuggle mixed-case values into the payload.\n if (typeof value === 'string' && /^0x[0-9a-fA-F]{40}$/.test(value)) {\n return value.toLowerCase()\n }\n\n return value\n}\n\nfunction stringifyBigints(value: unknown): unknown {\n if (typeof value === 'bigint') {\n return value.toString()\n }\n\n if (Array.isArray(value)) {\n return value.map(stringifyBigints)\n }\n\n if (typeof value === 'object' && value !== null) {\n const result: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value)) {\n result[k] = stringifyBigints(v)\n }\n return result\n }\n\n return value\n}\n\nfunction isConstellationNode(\n value: unknown\n): value is ConstellationNodeInternal {\n if (typeof value === 'function' || typeof value === 'object') {\n const obj = value as any\n return (\n obj != null &&\n typeof obj.type === 'string' &&\n typeof obj.chain === 'number' &&\n typeof obj._constellation === 'object'\n )\n }\n return false\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/constellation.ts","../src/apply.ts"],"sourcesContent":["/// <reference path=\"./zodiac-os-codegen.d.ts\" />\nimport type { Address, ChainId } from '@zodiac-os/api-types'\nimport type { AllowanceSpec } from './types'\nimport type { Annotation, Permission, PermissionSet } from 'zodiac-roles-sdk'\nimport { createRequire } from 'module'\nimport type * as ZodiacOsCodegen from '.zodiac-os'\nimport { UUID } from 'crypto'\n\n/**\n * A role definition keyed by role name. Permissions are expanded into\n * `{ targets, annotations }` via `processPermissions` at `apply()` time.\n */\nexport type RoleDef = {\n members: readonly AddressOrRef[]\n permissions: readonly (Permission | PermissionSet | Promise<PermissionSet>)[]\n annotations?: readonly Annotation[]\n}\n\ntype User = {\n id: UUID\n fullName: string\n personalSafes: Record<\n number,\n { address: Lowercase<Address>; active: boolean }\n >\n}\n\ntype Vault = {\n id: UUID\n label: string\n address: Lowercase<Address>\n chain: ChainId\n threshold: number\n owners: readonly string[]\n modules: readonly string[]\n}\n\ntype WorkspaceVaults = {\n workspaceId: UUID\n workspaceName: string\n vaults: Readonly<Record<string, Vault>>\n}\n\n/** Shape of the codegen data produced by `zodiac-os pull-org`. */\nexport type CodegenData = {\n users: Readonly<Record<string, User>>\n vaults: Readonly<Record<string, WorkspaceVaults>>\n}\n\ntype GeneratedCodegen = {\n users: typeof ZodiacOsCodegen.users\n vaults: typeof ZodiacOsCodegen.vaults\n}\n\ntype ConstellationOpts<C extends CodegenData> = {\n /** Workspace to scope vaults and roles to. */\n workspace: keyof C['vaults'] & string\n /** Human-readable label for this constellation. */\n label: string\n /** Target chain for all nodes in this constellation. */\n chain: ChainId\n}\n\ntype ConstellationInternalOpts<C extends CodegenData> = {\n /** Injected codegen data (used for testing). */\n codegen?: C\n}\n\ntype Prettify<T> = { readonly [K in keyof T]: T[K] } & {}\n\ntype WorkspaceVaultEntries<\n C extends CodegenData,\n W extends keyof C['vaults'],\n> = C['vaults'][W]['vaults']\n\ntype NodeType = 'SAFE' | 'ROLES' | 'DELAY'\n\n/** A reference to a node used in `owners`, `modules`, `target`, etc. */\ntype NodeRef = Readonly<{ type: NodeType; label: string; chain: ChainId }>\n\n/** A blockchain address (checksummed or lowercase) or a reference to another\n * node in the constellation. Values are normalized to lowercase before being\n * sent to the API. */\ntype AddressOrRef = Address | NodeRef\n\ntype NodeBase = Readonly<{\n /** Human-readable identifier, unique within the constellation. */\n label: string\n /** Chain the node is deployed on. */\n chain: ChainId\n /** Set for existing nodes from codegen, absent for new nodes. */\n address?: Lowercase<Address>\n /** Deployment nonce — required for new nodes, optional for existing. */\n nonce?: bigint\n}>\n\n/** A safe node spec — existing vault ref or new safe with required config. */\nexport type SafeNode = NodeBase &\n Readonly<{\n /** Discriminator identifying this node as a Safe. */\n type: 'SAFE'\n /** Number of owner signatures required to execute a transaction. */\n threshold: number\n /** Safe owner addresses or node references. */\n owners: readonly (string | NodeRef)[]\n /** Module addresses or node references enabled on the safe. */\n modules?: readonly (string | NodeRef)[]\n /** Whether this safe shall appear as a vault in the workspace. @default false */\n vault?: boolean\n }>\n\n/** A roles modifier node spec — existing vault ref or new roles with modifier config. */\nexport type RolesNode = NodeBase &\n Readonly<{\n /** Discriminator identifying this node as a Roles modifier. */\n type: 'ROLES'\n /** The safe that this roles modifier controls. */\n target?: AddressOrRef\n /** The account that is allowed to update the configuration of the Roles mod. */\n owner?: AddressOrRef\n /** The account that calls will be executed from. */\n avatar?: AddressOrRef\n /** MultiSend contract addresses for batched transactions. */\n multisend?: readonly Address[]\n /** Role definitions configured on this modifier. */\n roles?: Record<string, RoleDef>\n /** Spending allowances configured on this modifier. */\n allowances?: readonly AllowanceSpec[]\n }>\n\n/** Any complete node that can be passed to `apply()`. */\nexport type ConstellationNode = SafeNode | RolesNode\nexport type ConstellationNodeInternal = ConstellationNode & {\n _constellation: ConstellationMeta\n}\n\ntype NewSafeProps = {\n /** Deployment nonce for CREATE2 address derivation. */\n nonce: bigint\n /** Number of owner signatures required to execute a transaction. */\n threshold: number\n /** Safe owner addresses or node references. */\n owners: readonly AddressOrRef[]\n /** Module addresses or node references to enable on the safe. */\n modules?: readonly AddressOrRef[]\n /** Whether this safe is a workspace vault. @default false */\n vault?: boolean\n}\n\ntype NewRolesProps = {\n /** Deployment nonce for CREATE2 address derivation. Defaults to `0n` when omitted. */\n nonce?: bigint\n /** The safe that this roles modifier controls. Defaults to the new safe with the same label, when one exists. */\n target?: AddressOrRef\n /** The account that calls will be executed from. Defaults to `target` value */\n avatar?: AddressOrRef\n /** The account that is allowed to update the configuration of the Roles Mod. Defaults to `target` value */\n owner?: AddressOrRef\n /** MultiSend contract addresses for batched transactions. Defaults to `['0x38869bf66a61cf6bdb996a6ae40d5853fd43b526', '0x9641d764fc13c8b624c04430c7356c1c7c8102e2']` */\n multisend?: readonly Address[]\n /** Role definitions to configure on this modifier. */\n roles?: Record<string, RoleDef>\n /** Spending allowances to configure on this modifier. */\n allowances?: readonly AllowanceSpec[]\n}\n\ntype EntityAccessor<\n Type extends string,\n Entries extends Record<string, any>,\n Ch extends ChainId = ChainId,\n NP extends Record<string, any> = Record<string, any>,\n> = {\n readonly [K in\n | (keyof Entries & string)\n | (string & {})]: K extends keyof Entries & string\n ? Readonly<Prettify<Entries[K] & { type: Type; label: K; chain: Ch }>> &\n (<\n O extends {\n [P in Exclude<keyof Entries[K] & string, 'id' | 'label'>]?: any\n } & Partial<NP> = {},\n >(\n overrides?: {\n [P in Exclude<keyof Entries[K] & string, 'id' | 'label'>]?: any\n } & Partial<NP> &\n O\n ) => Readonly<\n Prettify<\n Omit<Entries[K], keyof O> &\n O &\n Partial<NP> & { type: Type; label: K; chain: Ch }\n >\n >)\n : Readonly<Prettify<{ type: Type; label: string; chain: Ch }>> &\n ((\n props: NP\n ) => Readonly<Prettify<NP & { type: Type; label: string; chain: Ch }>>)\n}\n\ntype UserAccessor<C extends CodegenData, Ch extends number> = {\n readonly [K in keyof C['users'] &\n string]: C['users'][K]['personalSafes'][Ch]['address']\n}\n\ntype ConstellationResult<\n C extends CodegenData,\n W extends keyof C['vaults'] = keyof C['vaults'],\n Ch extends ChainId = ChainId,\n> = {\n /** Access existing safes by label or create new ones with a new label. */\n safe: EntityAccessor<'SAFE', WorkspaceVaultEntries<C, W>, Ch, NewSafeProps>\n /** Access existing roles modifiers by label or create new ones with a new label. */\n roles: EntityAccessor<'ROLES', WorkspaceVaultEntries<C, W>, Ch, NewRolesProps>\n /** Resolve a user's personal safe address on the constellation's chain. */\n user: UserAccessor<C, Ch>\n}\n\n/** @internal */\nexport type ConstellationMeta = {\n label: string\n chain: ChainId\n workspaceId: UUID\n}\n\nfunction loadCodegen(): CodegenData {\n const require = createRequire(import.meta.url)\n return require('.zodiac-os') as CodegenData\n}\n\n/**\n * Creates a constellation scoped to a workspace and chain.\n *\n * Use bracket access to reference existing vaults or define new nodes:\n * ```ts\n * const eth = constellation({ workspace: 'GG', label: 'my constellation', chain: 1 })\n *\n * const dao = eth.safe['GG DAO'] // existing vault ref\n * const roles = eth.roles['GG DAO'] // existing roles ref\n * const newSafe = eth.safe['New Safe']({ nonce: 0n, threshold: 2, owners: [...], modules: [...] })\n * ```\n */\nexport function constellation<\n const C extends CodegenData = GeneratedCodegen,\n const W extends keyof C['vaults'] & string = keyof C['vaults'] & string,\n const Ch extends ChainId = ChainId,\n>(\n opts: ConstellationOpts<C> & { workspace: W; chain: Ch },\n internal?: ConstellationInternalOpts<C>\n): ConstellationResult<C, W, Ch> {\n const codegen: CodegenData = internal?.codegen ?? loadCodegen()\n\n const ws = codegen.vaults[opts.workspace]\n const vaultsByLabel: Record<string, Vault> = {}\n if (ws) {\n for (const [label, vault] of Object.entries(ws.vaults)) {\n vaultsByLabel[label] = vault\n }\n }\n\n const meta: ConstellationMeta = {\n label: opts.label,\n chain: opts.chain,\n workspaceId: (ws?.workspaceId ?? '') as UUID,\n }\n\n const newSafes = new Map<string, Readonly<Record<string, any>>>()\n\n function makeNodeRef(\n data: Record<string, any>\n ): Readonly<Record<string, any>> {\n const ref: Record<string, any> = Object.freeze({\n ...data,\n chain: opts.chain,\n _constellation: meta,\n })\n if (ref.type === 'SAFE' && typeof ref.label === 'string') {\n newSafes.set(ref.label, ref)\n }\n return ref\n }\n\n function entityAccessor(\n registry: Record<string, Record<string, any>>,\n type: string,\n resolveCanonicalSafe?: (name: string) => Record<string, any> | undefined\n ) {\n const cache = new Map<string, Record<string, any>>()\n return new Proxy({} as Record<string, any>, {\n get(_target: any, name: string) {\n if (typeof name !== 'string') return undefined\n const cached = cache.get(name)\n if (cached) return cached\n const existing = registry[name]\n const fn = (overrides?: Record<string, any>) => {\n const canonicalSafe =\n resolveCanonicalSafe && !overrides?.target\n ? resolveCanonicalSafe(name)\n : undefined\n if (canonicalSafe) {\n return makeNodeRef({\n type,\n nonce: 0n,\n target: canonicalSafe,\n owner: canonicalSafe,\n avatar: canonicalSafe,\n ...overrides,\n label: name,\n })\n }\n return makeNodeRef({\n type,\n ...(existing || {}),\n ...overrides,\n label: name,\n })\n }\n Object.assign(fn, {\n type,\n ...(existing || {}),\n label: name,\n chain: opts.chain,\n _constellation: meta,\n })\n cache.set(name, fn)\n return fn\n },\n })\n }\n\n function userAccessor() {\n return new Proxy(\n {},\n {\n get(_target: any, name: string) {\n const user = codegen.users[name]\n if (!user) throw new Error(`Unknown user: ${name}`)\n const personalSafe = user.personalSafes[opts.chain]\n if (!personalSafe) {\n throw new Error(\n `User ${name} has no personal safe on chain ${opts.chain}`\n )\n }\n return personalSafe.address\n },\n }\n )\n }\n\n const safe = entityAccessor(vaultsByLabel, 'SAFE')\n const roles = entityAccessor(vaultsByLabel, 'ROLES', (name) => {\n const invoked = newSafes.get(name)\n if (invoked) return invoked\n if (name in vaultsByLabel) return safe[name]\n return undefined\n })\n\n return {\n safe,\n roles,\n user: userAccessor(),\n } as ConstellationResult<C, W, Ch>\n}\n","import type {\n ApplyConstellationPayload,\n ApplyConstellationResult,\n} from '@zodiac-os/api-types'\nimport { invariant } from '@epic-web/invariant'\nimport { processPermissions } from 'zodiac-roles-sdk'\nimport type { PermissionSet } from 'zodiac-roles-sdk'\nimport { ApiClient } from './api'\nimport type {\n ConstellationMeta,\n ConstellationNode,\n ConstellationNodeInternal,\n RoleDef,\n} from './constellation'\n\ntype ApplyOpts = {\n /** API client instance. Defaults to a client configured from environment variables. */\n api?: ApiClient\n}\n\n/**\n * Resolves node references and applies the constellation specification via the API.\n *\n *\n * ```ts\n * const eth = constellation({ workspace: 'GG', label: 'my constellation', chain: 1 })\n * const dao = eth.safe['GG DAO']\n * const roles = eth.roles['New Roles']({ nonce: 0n, target: dao, owner: dao, avatar: dao })\n *\n * await apply([dao, roles])\n * ```\n */\nexport async function apply(\n nodes: ConstellationNode[] | { [key: string]: ConstellationNode },\n opts?: ApplyOpts\n): Promise<ApplyConstellationResult[]> {\n const api = opts?.api ?? new ApiClient()\n const refs = deriveRefs(nodes)\n\n // Group nodes by constellation (multiple constellations can be applied with a single call)\n const groups = new Map<\n string,\n { meta: ConstellationMeta; nodes: ConstellationNodeInternal[] }\n >()\n\n for (const node of refs.byIdentity.keys()) {\n const meta = node._constellation\n invariant(\n meta,\n `Node \"${node.label}\" is not associated with a constellation`\n )\n const key = `${meta.workspaceId}:${meta.chain}:${meta.label}`\n let group = groups.get(key)\n if (!group) {\n group = { meta, nodes: [] }\n groups.set(key, group)\n }\n group.nodes.push(node)\n }\n\n const results: ApplyConstellationResult[] = []\n for (const { meta, nodes: groupNodes } of groups.values()) {\n const specification = await Promise.all(\n groupNodes.map((n) => nodeToSpec(n, refs))\n )\n const result = await api.applyConstellation(meta.workspaceId, {\n label: meta.label,\n chain: meta.chain,\n specification,\n })\n results.push(result)\n }\n\n return results\n}\n\ntype RefsIndex = {\n byIdentity: Map<ConstellationNodeInternal, string>\n byLabel: Map<string, string>\n}\n\nfunction labelKey(node: ConstellationNodeInternal): string {\n return `${node._constellation.workspaceId}:${node.type}:${node.label}`\n}\n\nfunction deriveRefs(\n nodes: ConstellationNode[] | { [key: string]: ConstellationNode }\n): RefsIndex {\n const byIdentity = new Map<ConstellationNodeInternal, string>()\n const byLabel = new Map<string, string>()\n\n const register = (node: ConstellationNodeInternal, ref: string) => {\n byIdentity.set(node, ref)\n byLabel.set(labelKey(node), ref)\n }\n\n if (Array.isArray(nodes)) {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]\n invariant(\n isConstellationNode(node),\n `unexpected node input at index: ${i}`\n )\n register(node, `${i}`)\n }\n } else {\n for (const [key, node] of Object.entries(nodes)) {\n invariant(\n isConstellationNode(node),\n `unexpected node input under key: ${key}`\n )\n register(node, key)\n }\n }\n\n return { byIdentity, byLabel }\n}\n\nasync function nodeToSpec(\n node: ConstellationNodeInternal,\n refs: RefsIndex\n): Promise<ApplyConstellationPayload['specification'][number]> {\n const { id, _constellation, ...rest } = node as Record<string, any>\n const spec: Record<string, any> = {}\n\n for (const [key, value] of Object.entries(rest)) {\n if (node.type === 'ROLES' && key === 'roles' && value != null) {\n spec.roles = await expandRoles(value as Record<string, RoleDef>, refs)\n continue\n }\n spec[key] = resolveRefs(value, refs)\n }\n\n spec.ref = refs.byIdentity.get(node)\n invariant(spec.ref != null, 'ref not found')\n\n return stringifyBigints(\n spec\n ) as ApplyConstellationPayload['specification'][number]\n}\n\nasync function expandRoles(\n roles: Record<string, RoleDef>,\n refs: RefsIndex\n): Promise<unknown[]> {\n return Promise.all(\n Object.entries(roles).map(async ([key, def]) => {\n const resolvedPermissions = (await Promise.all(\n def.permissions.map((p) => Promise.resolve(p))\n )) as Parameters<typeof processPermissions>[0][number][]\n const { targets, annotations } = processPermissions(resolvedPermissions)\n return {\n key,\n members: resolveRefs(def.members, refs),\n targets,\n annotations: [...(def.annotations ?? []), ...annotations],\n }\n })\n )\n}\n\nfunction resolveRefs(value: unknown, refs: RefsIndex): unknown {\n if (isConstellationNode(value)) {\n const ref = refs.byIdentity.get(value) ?? refs.byLabel.get(labelKey(value))\n invariant(\n ref != null,\n `Node \"${value.label}\" is referenced not included in the apply() call`\n )\n return `$${ref}`\n }\n\n if (Array.isArray(value)) {\n return value.map((v) => resolveRefs(v, refs))\n }\n\n if (typeof value === 'object' && value !== null) {\n const resolved: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value)) {\n resolved[k] = resolveRefs(v, refs)\n }\n return resolved\n }\n\n // API expects lowercase addresses\n if (typeof value === 'string' && /^0x[0-9a-fA-F]{40}$/.test(value)) {\n return value.toLowerCase()\n }\n\n return value\n}\n\nfunction stringifyBigints(value: unknown): unknown {\n if (typeof value === 'bigint') {\n return value.toString()\n }\n\n if (Array.isArray(value)) {\n return value.map(stringifyBigints)\n }\n\n if (typeof value === 'object' && value !== null) {\n const result: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value)) {\n result[k] = stringifyBigints(v)\n }\n return result\n }\n\n return value\n}\n\nfunction isConstellationNode(\n value: unknown\n): value is ConstellationNodeInternal {\n if (typeof value === 'function' || typeof value === 'object') {\n const obj = value as any\n return (\n obj != null &&\n typeof obj.type === 'string' &&\n typeof obj.chain === 'number' &&\n typeof obj._constellation === 'object'\n )\n }\n return false\n}\n"],"mappings":";;;;;;AA+NA,SAAS,cAA2B;AAElC,QADgB,cAAc,OAAO,KAAK,IAAI,CAC/B,aAAa;;;;;;;;;;;;;;AAe9B,SAAgB,cAKd,MACA,UAC+B;CAC/B,MAAM,UAAuB,UAAU,WAAW,aAAa;CAE/D,MAAM,KAAK,QAAQ,OAAO,KAAK;CAC/B,MAAM,gBAAuC,EAAE;AAC/C,KAAI,GACF,MAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,GAAG,OAAO,CACpD,eAAc,SAAS;CAI3B,MAAM,OAA0B;EAC9B,OAAO,KAAK;EACZ,OAAO,KAAK;EACZ,aAAc,IAAI,eAAe;EAClC;CAED,MAAM,2BAAW,IAAI,KAA4C;CAEjE,SAAS,YACP,MAC+B;EAC/B,MAAM,MAA2B,OAAO,OAAO;GAC7C,GAAG;GACH,OAAO,KAAK;GACZ,gBAAgB;GACjB,CAAC;AACF,MAAI,IAAI,SAAS,UAAU,OAAO,IAAI,UAAU,SAC9C,UAAS,IAAI,IAAI,OAAO,IAAI;AAE9B,SAAO;;CAGT,SAAS,eACP,UACA,MACA,sBACA;EACA,MAAM,wBAAQ,IAAI,KAAkC;AACpD,SAAO,IAAI,MAAM,EAAE,EAAyB,EAC1C,IAAI,SAAc,MAAc;AAC9B,OAAI,OAAO,SAAS,SAAU,QAAO,KAAA;GACrC,MAAM,SAAS,MAAM,IAAI,KAAK;AAC9B,OAAI,OAAQ,QAAO;GACnB,MAAM,WAAW,SAAS;GAC1B,MAAM,MAAM,cAAoC;IAC9C,MAAM,gBACJ,wBAAwB,CAAC,WAAW,SAChC,qBAAqB,KAAK,GAC1B,KAAA;AACN,QAAI,cACF,QAAO,YAAY;KACjB;KACA,OAAO;KACP,QAAQ;KACR,OAAO;KACP,QAAQ;KACR,GAAG;KACH,OAAO;KACR,CAAC;AAEJ,WAAO,YAAY;KACjB;KACA,GAAI,YAAY,EAAE;KAClB,GAAG;KACH,OAAO;KACR,CAAC;;AAEJ,UAAO,OAAO,IAAI;IAChB;IACA,GAAI,YAAY,EAAE;IAClB,OAAO;IACP,OAAO,KAAK;IACZ,gBAAgB;IACjB,CAAC;AACF,SAAM,IAAI,MAAM,GAAG;AACnB,UAAO;KAEV,CAAC;;CAGJ,SAAS,eAAe;AACtB,SAAO,IAAI,MACT,EAAE,EACF,EACE,IAAI,SAAc,MAAc;GAC9B,MAAM,OAAO,QAAQ,MAAM;AAC3B,OAAI,CAAC,KAAM,OAAM,IAAI,MAAM,iBAAiB,OAAO;GACnD,MAAM,eAAe,KAAK,cAAc,KAAK;AAC7C,OAAI,CAAC,aACH,OAAM,IAAI,MACR,QAAQ,KAAK,iCAAiC,KAAK,QACpD;AAEH,UAAO,aAAa;KAEvB,CACF;;CAGH,MAAM,OAAO,eAAe,eAAe,OAAO;AAQlD,QAAO;EACL;EACA,OATY,eAAe,eAAe,UAAU,SAAS;GAC7D,MAAM,UAAU,SAAS,IAAI,KAAK;AAClC,OAAI,QAAS,QAAO;AACpB,OAAI,QAAQ,cAAe,QAAO,KAAK;IAEvC;EAKA,MAAM,cAAc;EACrB;;;;;;;;;;;;;;;;ACvUH,eAAsB,MACpB,OACA,MACqC;CACrC,MAAM,MAAM,MAAM,OAAO,IAAI,WAAW;CACxC,MAAM,OAAO,WAAW,MAAM;CAG9B,MAAM,yBAAS,IAAI,KAGhB;AAEH,MAAK,MAAM,QAAQ,KAAK,WAAW,MAAM,EAAE;EACzC,MAAM,OAAO,KAAK;AAClB,YACE,MACA,SAAS,KAAK,MAAM,0CACrB;EACD,MAAM,MAAM,GAAG,KAAK,YAAY,GAAG,KAAK,MAAM,GAAG,KAAK;EACtD,IAAI,QAAQ,OAAO,IAAI,IAAI;AAC3B,MAAI,CAAC,OAAO;AACV,WAAQ;IAAE;IAAM,OAAO,EAAE;IAAE;AAC3B,UAAO,IAAI,KAAK,MAAM;;AAExB,QAAM,MAAM,KAAK,KAAK;;CAGxB,MAAM,UAAsC,EAAE;AAC9C,MAAK,MAAM,EAAE,MAAM,OAAO,gBAAgB,OAAO,QAAQ,EAAE;EACzD,MAAM,gBAAgB,MAAM,QAAQ,IAClC,WAAW,KAAK,MAAM,WAAW,GAAG,KAAK,CAAC,CAC3C;EACD,MAAM,SAAS,MAAM,IAAI,mBAAmB,KAAK,aAAa;GAC5D,OAAO,KAAK;GACZ,OAAO,KAAK;GACZ;GACD,CAAC;AACF,UAAQ,KAAK,OAAO;;AAGtB,QAAO;;AAQT,SAAS,SAAS,MAAyC;AACzD,QAAO,GAAG,KAAK,eAAe,YAAY,GAAG,KAAK,KAAK,GAAG,KAAK;;AAGjE,SAAS,WACP,OACW;CACX,MAAM,6BAAa,IAAI,KAAwC;CAC/D,MAAM,0BAAU,IAAI,KAAqB;CAEzC,MAAM,YAAY,MAAiC,QAAgB;AACjE,aAAW,IAAI,MAAM,IAAI;AACzB,UAAQ,IAAI,SAAS,KAAK,EAAE,IAAI;;AAGlC,KAAI,MAAM,QAAQ,MAAM,CACtB,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,OAAO,MAAM;AACnB,YACE,oBAAoB,KAAK,EACzB,mCAAmC,IACpC;AACD,WAAS,MAAM,GAAG,IAAI;;KAGxB,MAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,MAAM,EAAE;AAC/C,YACE,oBAAoB,KAAK,EACzB,oCAAoC,MACrC;AACD,WAAS,MAAM,IAAI;;AAIvB,QAAO;EAAE;EAAY;EAAS;;AAGhC,eAAe,WACb,MACA,MAC6D;CAC7D,MAAM,EAAE,IAAI,gBAAgB,GAAG,SAAS;CACxC,MAAM,OAA4B,EAAE;AAEpC,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,EAAE;AAC/C,MAAI,KAAK,SAAS,WAAW,QAAQ,WAAW,SAAS,MAAM;AAC7D,QAAK,QAAQ,MAAM,YAAY,OAAkC,KAAK;AACtE;;AAEF,OAAK,OAAO,YAAY,OAAO,KAAK;;AAGtC,MAAK,MAAM,KAAK,WAAW,IAAI,KAAK;AACpC,WAAU,KAAK,OAAO,MAAM,gBAAgB;AAE5C,QAAO,iBACL,KACD;;AAGH,eAAe,YACb,OACA,MACoB;AACpB,QAAO,QAAQ,IACb,OAAO,QAAQ,MAAM,CAAC,IAAI,OAAO,CAAC,KAAK,SAAS;EAI9C,MAAM,EAAE,SAAS,gBAAgB,mBAHJ,MAAM,QAAQ,IACzC,IAAI,YAAY,KAAK,MAAM,QAAQ,QAAQ,EAAE,CAAC,CAC/C,CACuE;AACxE,SAAO;GACL;GACA,SAAS,YAAY,IAAI,SAAS,KAAK;GACvC;GACA,aAAa,CAAC,GAAI,IAAI,eAAe,EAAE,EAAG,GAAG,YAAY;GAC1D;GACD,CACH;;AAGH,SAAS,YAAY,OAAgB,MAA0B;AAC7D,KAAI,oBAAoB,MAAM,EAAE;EAC9B,MAAM,MAAM,KAAK,WAAW,IAAI,MAAM,IAAI,KAAK,QAAQ,IAAI,SAAS,MAAM,CAAC;AAC3E,YACE,OAAO,MACP,SAAS,MAAM,MAAM,kDACtB;AACD,SAAO,IAAI;;AAGb,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,KAAK,MAAM,YAAY,GAAG,KAAK,CAAC;AAG/C,KAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EAC/C,MAAM,WAAoC,EAAE;AAC5C,OAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,CACxC,UAAS,KAAK,YAAY,GAAG,KAAK;AAEpC,SAAO;;AAIT,KAAI,OAAO,UAAU,YAAY,sBAAsB,KAAK,MAAM,CAChE,QAAO,MAAM,aAAa;AAG5B,QAAO;;AAGT,SAAS,iBAAiB,OAAyB;AACjD,KAAI,OAAO,UAAU,SACnB,QAAO,MAAM,UAAU;AAGzB,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,IAAI,iBAAiB;AAGpC,KAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EAC/C,MAAM,SAAkC,EAAE;AAC1C,OAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,CACxC,QAAO,KAAK,iBAAiB,EAAE;AAEjC,SAAO;;AAGT,QAAO;;AAGT,SAAS,oBACP,OACoC;AACpC,KAAI,OAAO,UAAU,cAAc,OAAO,UAAU,UAAU;EAC5D,MAAM,MAAM;AACZ,SACE,OAAO,QACP,OAAO,IAAI,SAAS,YACpB,OAAO,IAAI,UAAU,YACrB,OAAO,IAAI,mBAAmB;;AAGlC,QAAO"}
|