@zodiac-os/sdk 1.7.0 → 1.8.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/README.md CHANGED
@@ -10,20 +10,24 @@ Programmatically manage [Zodiac](https://www.zodiac.eco) account constellations.
10
10
  npm install @zodiac-os/sdk
11
11
  ```
12
12
 
13
- ### 2. Generate a Zodiac OS API key
13
+ ### 2. Authorize project
14
14
 
15
- Sign in to [app.zodiac.eco](https://app.zodiac.eco) and create an API key at [app.zodiac.eco/admin/api-keys](https://app.zodiac.eco/admin/api-keys).
15
+ ```bash
16
+ zodiac init
17
+ ```
18
+
19
+ Opens a browser tab so you can sign in, pick the org you want to use, and approve a new API key. The key (and matching `ZODIAC_API_URL`) are written to a `.env` file in your project root — labeled after the directory so you can find and revoke it later from [app.zodiac.eco/admin/api-keys](https://app.zodiac.eco/admin/api-keys).
20
+
21
+ **IMPORTANT:** Make sure to add `.env` to your `.gitignore`.
16
22
 
17
23
  ### 3. Create a config file
18
24
 
19
- Create a `zodiac.config.ts` in your project root:
25
+ Create a `zodiac.config.ts` in your project root.
20
26
 
21
27
  ```ts
22
28
  import { defineConfig } from '@zodiac-os/sdk/cli/config'
23
29
 
24
30
  export default defineConfig({
25
- apiKey: 'zodiac_...',
26
- // Optional: contracts to fetch for permissions authoring
27
31
  contracts: {
28
32
  mainnet: {
29
33
  dai: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
@@ -36,7 +40,7 @@ export default defineConfig({
36
40
 
37
41
  ```bash
38
42
  # Pull everything (org data + contract ABIs)
39
- zodiac-os pull
43
+ zodiac pull
40
44
  ```
41
45
 
42
46
  This generates typed data in `.zodiac/` at your project root with your org's users and accounts (workspace vaults plus accounts that have been applied via a constellation). Add `.zodiac/` to your `.gitignore`.
@@ -153,9 +157,9 @@ await push({ ggDao, newRoles }, { api: new ApiClient({ apiKey: '...' }) })
153
157
  ## CLI reference
154
158
 
155
159
  ```
156
- Usage: zodiac-os [options] [command]
160
+ Usage: zodiac [options] [command]
157
161
 
158
- Zodiac OS SDK CLI – pull org data and contract ABIs
162
+ Zodiac SDK CLI – pull org data and contract ABIs
159
163
 
160
164
  Options:
161
165
  -V, --version output the version number
@@ -163,7 +167,8 @@ Options:
163
167
  -h, --help display help for command
164
168
 
165
169
  Commands:
166
- pull-org Fetch Zodiac users and vaults, generate TypeScript types
170
+ init Authorize this directory with a Zodiac org. Opens a browser to mint an API key and writes it to .env.
171
+ pull-org Fetch Zodiac users and accounts, generate TypeScript types
167
172
  pull-contracts Fetch contract ABIs, generate typed permissions kit
168
173
  pull Fetch Zodiac org and contracts ABI, generate SDK functions
169
174
  help [command] display help for command
@@ -1,3 +1,3 @@
1
1
  import { n as chainIdFor, t as CHAIN_IDS } from "../networks-BTW1qAAa.mjs";
2
- import { n as EVERYTHING, t as buildAllowKit } from "../allow-Dzh6t_l8.mjs";
2
+ import { n as EVERYTHING, t as buildAllowKit } from "../allow-DSj3TXgN.mjs";
3
3
  export { CHAIN_IDS, EVERYTHING, buildAllowKit, chainIdFor };
@@ -29,7 +29,7 @@ function attachAt(root, segments, value) {
29
29
  }
30
30
  function missingAbiProxy(node) {
31
31
  const explain = () => {
32
- throw new Error(`ABI missing for ${node.chain}.${node.segments.join(".")} (${node.address}). Run \`zodiac-os pull-contracts\` to fetch it, or paste the ABI JSON manually at <abisDir>/${node.chain}/${node.segments.join("/")}.json`);
32
+ throw new Error(`ABI missing for ${node.chain}.${node.segments.join(".")} (${node.address}). Run \`zodiac pull-contracts\` to fetch it, or paste the ABI JSON manually at <abisDir>/${node.chain}/${node.segments.join("/")}.json`);
33
33
  };
34
34
  return new Proxy({}, {
35
35
  get: explain,
@@ -119,4 +119,4 @@ const applyOptions = (permission, options) => {
119
119
  //#endregion
120
120
  export { EVERYTHING as n, buildAllowKit as t };
121
121
 
122
- //# sourceMappingURL=allow-Dzh6t_l8.mjs.map
122
+ //# sourceMappingURL=allow-DSj3TXgN.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allow-DSj3TXgN.mjs","names":[],"sources":["../src/allow/types.ts","../src/allow/runtime.ts"],"sourcesContent":["import type { BigNumberish, BytesLike, ParamType } from 'ethers'\nimport type {\n Condition,\n FunctionPermission,\n TargetPermission,\n} from 'zodiac-roles-sdk'\n\nexport type Options = {\n send?: boolean\n delegatecall?: boolean\n etherWithinAllowance?: `0x${string}`\n callWithinAllowance?: `0x${string}`\n}\n\nexport type PrimitiveValue = BigNumberish | BytesLike | string | boolean\n\n// Signature matches `zodiac-roles-sdk` so values from `c.*` are assignable.\nexport type ConditionFunction<T = unknown> = (\n abiType: ParamType,\n _?: T\n) => Condition\n\ntype RequireAtLeastOne<T> = {\n [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>\n}[keyof T]\n\ntype ArrayElement<T extends readonly unknown[]> = T extends readonly (infer U)[]\n ? U\n : never\n\nexport type PrimitiveScoping<T extends PrimitiveValue> =\n | T\n | ConditionFunction<T>\n\nexport type ArrayScoping<T extends readonly any[]> =\n | readonly Scoping<ArrayElement<T>>[]\n | ConditionFunction<T>\n\nexport type StructScoping<T extends { [key: string]: any }> =\n | RequireAtLeastOne<{ [K in keyof T]?: Scoping<T[K]> }>\n | ConditionFunction<T>\n\nexport type Scoping<T> = T extends PrimitiveValue\n ? PrimitiveScoping<T>\n : T extends readonly any[]\n ? ArrayScoping<T>\n : T extends { [key: string]: any }\n ? StructScoping<T>\n : unknown\n\nexport type { FunctionPermission, TargetPermission }\n\nexport const EVERYTHING = Symbol.for('@zodiac-os/allow-kit/EVERYTHING')\nexport type EVERYTHING = typeof EVERYTHING\n","import { Interface, FunctionFragment, isError, type InterfaceAbi } from 'ethers'\nimport { c, coercePermission } from 'zodiac-roles-sdk'\nimport type {\n Condition,\n FunctionPermission,\n TargetPermission,\n} from 'zodiac-roles-sdk'\nimport { ParameterType, Operator } from 'zodiac-roles-deployments'\nimport { readAbi, walkContracts, type ContractNode } from './abi'\nimport { EVERYTHING, type Options } from './types'\n\nexport function buildAllowKit(\n abisDir: string,\n contractsConfig: Record<string, any>\n): Record<string, any> {\n const kit: Record<string, any> = {}\n for (const node of walkContracts(contractsConfig)) {\n const abi = readAbi(abisDir, node)\n if (!abi) {\n // Defer the error until the user touches this contract — otherwise an\n // ABI missing for one chain crashes all unrelated role definitions.\n attachAt(kit, [node.chain, ...node.segments], missingAbiProxy(node))\n continue\n }\n attachAt(\n kit,\n [node.chain, ...node.segments],\n makeAllowContract(node.address, abi as InterfaceAbi)\n )\n }\n return kit\n}\n\nfunction attachAt(root: Record<string, any>, segments: string[], value: any) {\n let cursor = root\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i]!\n if (!(seg in cursor)) cursor[seg] = {}\n cursor = cursor[seg]\n }\n cursor[segments[segments.length - 1]!] = value\n}\n\nfunction missingAbiProxy(node: ContractNode) {\n const explain = () => {\n throw new Error(\n `ABI missing for ${node.chain}.${node.segments.join('.')} ` +\n `(${node.address}). Run \\`zodiac pull-contracts\\` to fetch it, or ` +\n `paste the ABI JSON manually at <abisDir>/${node.chain}/${node.segments.join('/')}.json`\n )\n }\n return new Proxy(\n {},\n {\n get: explain,\n has: explain,\n }\n )\n}\n\nfunction makeAllowContract(\n address: `0x${string}`,\n abi: InterfaceAbi\n): Record<string | symbol, any> {\n const iface = Interface.from(abi)\n const lowerAddr = address.toLowerCase() as `0x${string}`\n\n const allowEverything = (options?: Options): TargetPermission => ({\n targetAddress: lowerAddr,\n send: options?.send,\n delegatecall: options?.delegatecall,\n })\n\n const has = (name: string) => {\n try {\n const fn = iface.getFunction(name)\n if (!fn) return false\n return fn.stateMutability !== 'view' && fn.stateMutability !== 'pure'\n } catch (error) {\n if (!isError(error as any, 'INVALID_ARGUMENT')) throw error\n return false\n }\n }\n\n return new Proxy(\n {},\n {\n get: (_target, prop) => {\n if (prop === EVERYTHING) return allowEverything\n if (typeof prop !== 'string') return undefined\n if (!has(prop)) return undefined\n const fn = iface.getFunction(prop)!\n return makeAllowFunction(fn, lowerAddr)\n },\n has: (_target, prop) => {\n if (prop === EVERYTHING) return true\n return typeof prop === 'string' && has(prop)\n },\n }\n )\n}\n\nfunction makeAllowFunction(\n fn: FunctionFragment,\n targetAddress: `0x${string}`\n): (...args: any[]) => FunctionPermission {\n const inputs = fn.inputs\n return (...args: any[]) => {\n const scopings = args.slice(0, inputs.length)\n const hasScopings = scopings.some((s) => s !== undefined && s !== null)\n const options: Options = args[inputs.length] ?? {}\n const condition = hasScopings\n ? c.calldataMatches(scopings, inputs)()\n : undefined\n const preset = {\n targetAddress,\n signature: fn.format('sighash'),\n condition,\n }\n return applyOptions(coercePermission(preset as any) as any, options)\n }\n}\n\nconst emptyCalldataMatches: Condition = {\n paramType: ParameterType.Calldata,\n operator: Operator.Matches,\n children: [],\n}\n\nconst applyGlobalAllowance = (\n condition: Condition | undefined,\n allowanceCondition: Condition\n): Condition => {\n const base = condition ?? emptyCalldataMatches\n if (\n base.paramType !== ParameterType.Calldata ||\n base.operator !== Operator.Matches\n ) {\n throw new Error(\n 'Global allowance can only be applied to calldata matches nodes'\n )\n }\n return {\n ...base,\n children: [...(base.children ?? []), allowanceCondition],\n }\n}\n\nconst applyOptions = (\n permission: FunctionPermission & { condition?: Condition },\n options: Options\n): FunctionPermission => {\n let condition = permission.condition\n if (options.etherWithinAllowance) {\n if (!options.send) {\n throw new Error(\n '`etherWithinAllowance` can only be used if `send` is allowed'\n )\n }\n condition = applyGlobalAllowance(condition, {\n paramType: ParameterType.None,\n operator: Operator.EtherWithinAllowance,\n compValue: options.etherWithinAllowance,\n })\n }\n if (options.callWithinAllowance) {\n condition = applyGlobalAllowance(condition, {\n paramType: ParameterType.None,\n operator: Operator.CallWithinAllowance,\n compValue: options.callWithinAllowance,\n })\n }\n return {\n ...permission,\n send: options.send,\n delegatecall: options.delegatecall,\n condition,\n }\n}\n"],"mappings":";;;;;AAoDA,MAAa,aAAa,OAAO,IAAI,kCAAkC;;;ACzCvE,SAAgB,cACd,SACA,iBACqB;CACrB,MAAM,MAA2B,EAAE;AACnC,MAAK,MAAM,QAAQ,cAAc,gBAAgB,EAAE;EACjD,MAAM,MAAM,QAAQ,SAAS,KAAK;AAClC,MAAI,CAAC,KAAK;AAGR,YAAS,KAAK,CAAC,KAAK,OAAO,GAAG,KAAK,SAAS,EAAE,gBAAgB,KAAK,CAAC;AACpE;;AAEF,WACE,KACA,CAAC,KAAK,OAAO,GAAG,KAAK,SAAS,EAC9B,kBAAkB,KAAK,SAAS,IAAoB,CACrD;;AAEH,QAAO;;AAGT,SAAS,SAAS,MAA2B,UAAoB,OAAY;CAC3E,IAAI,SAAS;AACb,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,SAAS,GAAG,KAAK;EAC5C,MAAM,MAAM,SAAS;AACrB,MAAI,EAAE,OAAO,QAAS,QAAO,OAAO,EAAE;AACtC,WAAS,OAAO;;AAElB,QAAO,SAAS,SAAS,SAAS,MAAO;;AAG3C,SAAS,gBAAgB,MAAoB;CAC3C,MAAM,gBAAgB;AACpB,QAAM,IAAI,MACR,mBAAmB,KAAK,MAAM,GAAG,KAAK,SAAS,KAAK,IAAI,CAAC,IACnD,KAAK,QAAQ,4FAC2B,KAAK,MAAM,GAAG,KAAK,SAAS,KAAK,IAAI,CAAC,OACrF;;AAEH,QAAO,IAAI,MACT,EAAE,EACF;EACE,KAAK;EACL,KAAK;EACN,CACF;;AAGH,SAAS,kBACP,SACA,KAC8B;CAC9B,MAAM,QAAQ,UAAU,KAAK,IAAI;CACjC,MAAM,YAAY,QAAQ,aAAa;CAEvC,MAAM,mBAAmB,aAAyC;EAChE,eAAe;EACf,MAAM,SAAS;EACf,cAAc,SAAS;EACxB;CAED,MAAM,OAAO,SAAiB;AAC5B,MAAI;GACF,MAAM,KAAK,MAAM,YAAY,KAAK;AAClC,OAAI,CAAC,GAAI,QAAO;AAChB,UAAO,GAAG,oBAAoB,UAAU,GAAG,oBAAoB;WACxD,OAAO;AACd,OAAI,CAAC,QAAQ,OAAc,mBAAmB,CAAE,OAAM;AACtD,UAAO;;;AAIX,QAAO,IAAI,MACT,EAAE,EACF;EACE,MAAM,SAAS,SAAS;AACtB,OAAI,SAAS,WAAY,QAAO;AAChC,OAAI,OAAO,SAAS,SAAU,QAAO,KAAA;AACrC,OAAI,CAAC,IAAI,KAAK,CAAE,QAAO,KAAA;AAEvB,UAAO,kBADI,MAAM,YAAY,KAAK,EACL,UAAU;;EAEzC,MAAM,SAAS,SAAS;AACtB,OAAI,SAAS,WAAY,QAAO;AAChC,UAAO,OAAO,SAAS,YAAY,IAAI,KAAK;;EAE/C,CACF;;AAGH,SAAS,kBACP,IACA,eACwC;CACxC,MAAM,SAAS,GAAG;AAClB,SAAQ,GAAG,SAAgB;EACzB,MAAM,WAAW,KAAK,MAAM,GAAG,OAAO,OAAO;EAC7C,MAAM,cAAc,SAAS,MAAM,MAAM,MAAM,KAAA,KAAa,MAAM,KAAK;EACvE,MAAM,UAAmB,KAAK,OAAO,WAAW,EAAE;EAClD,MAAM,YAAY,cACd,EAAE,gBAAgB,UAAU,OAAO,EAAE,GACrC,KAAA;AAMJ,SAAO,aAAa,iBALL;GACb;GACA,WAAW,GAAG,OAAO,UAAU;GAC/B;GACD,CACkD,EAAS,QAAQ;;;AAIxE,MAAM,uBAAkC;CACtC,WAAW,cAAc;CACzB,UAAU,SAAS;CACnB,UAAU,EAAE;CACb;AAED,MAAM,wBACJ,WACA,uBACc;CACd,MAAM,OAAO,aAAa;AAC1B,KACE,KAAK,cAAc,cAAc,YACjC,KAAK,aAAa,SAAS,QAE3B,OAAM,IAAI,MACR,iEACD;AAEH,QAAO;EACL,GAAG;EACH,UAAU,CAAC,GAAI,KAAK,YAAY,EAAE,EAAG,mBAAmB;EACzD;;AAGH,MAAM,gBACJ,YACA,YACuB;CACvB,IAAI,YAAY,WAAW;AAC3B,KAAI,QAAQ,sBAAsB;AAChC,MAAI,CAAC,QAAQ,KACX,OAAM,IAAI,MACR,+DACD;AAEH,cAAY,qBAAqB,WAAW;GAC1C,WAAW,cAAc;GACzB,UAAU,SAAS;GACnB,WAAW,QAAQ;GACpB,CAAC;;AAEJ,KAAI,QAAQ,oBACV,aAAY,qBAAqB,WAAW;EAC1C,WAAW,cAAc;EACzB,UAAU,SAAS;EACnB,WAAW,QAAQ;EACpB,CAAC;AAEJ,QAAO;EACL,GAAG;EACH,MAAM,QAAQ;EACd,cAAc,QAAQ;EACtB;EACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"api-CygEDU4N.mjs","names":[],"sources":["../src/paths.ts","../src/api.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\n/**\n * Resolve the project's `.zodiac/` directory — the shared home for SDK\n * codegen (`pull-org` emits the importable module here, `pull-contracts`\n * writes `allow.d.ts` alongside it).\n *\n * Pass `rootDir` to anchor explicitly (CLI commands do this using the\n * directory of the loaded `zodiac.config.ts`). Without a `rootDir`, walks\n * up from `cwd` to the nearest `zodiac.config.{ts,js,mjs,cjs}` so runtime\n * callers like `constellation()` work even when invoked from a subdirectory.\n */\nexport function resolveZodiacDir(rootDir?: string): string {\n return join(rootDir ?? findProjectRoot(), '.zodiac')\n}\n\nconst CONFIG_FILENAMES = [\n 'zodiac.config.ts',\n 'zodiac.config.js',\n 'zodiac.config.mjs',\n 'zodiac.config.cjs',\n]\n\nfunction findProjectRoot(): string {\n let dir = process.cwd()\n while (dir !== dirname(dir)) {\n if (CONFIG_FILENAMES.some((name) => existsSync(join(dir, name)))) return dir\n dir = dirname(dir)\n }\n return process.cwd()\n}\n","import type {\n ApplyConstellationPayload,\n ApplyConstellationResult,\n ResolveConstellationPayload,\n ResolveConstellationResult,\n ApiError as ApiErrorResponse,\n ListAccountsResult,\n ListUsersResult,\n} from '@zodiac-os/api-types'\nimport assert from 'assert'\nimport { UUID } from 'crypto'\n\nexport type Options = {\n workspace?: string\n apiKey?: string\n baseUrl?: string\n fetch?: typeof globalThis.fetch\n headers?: Record<string, string>\n}\n\nconst { ZODIAC_API_KEY, ZODIAC_API_URL = 'https://app.zodiac.eco/api/v1' } =\n process.env\n\nexport class ApiClient {\n private apiKey: string\n private baseUrl: string\n private _fetch: typeof fetch\n private headers: Record<string, string>\n\n constructor({\n baseUrl = ZODIAC_API_URL,\n fetch: customFetch = fetch,\n headers = {},\n apiKey = ZODIAC_API_KEY,\n }: Options = {}) {\n this.baseUrl = baseUrl.replace(/\\/$/, '')\n this._fetch = customFetch\n this.headers = headers\n\n assert(\n apiKey,\n 'No API key provided to the API client. Either pass it as the \"apiKey\" option or set the ZODIAC_API_KEY environment variable.'\n )\n\n this.apiKey = apiKey\n }\n\n protected async postJson(endpoint: string, payload: unknown) {\n const res = await this._fetch(`${this.baseUrl}/${endpoint}`, {\n method: 'POST',\n headers: {\n ...this.headers,\n 'content-type': 'application/json',\n authorization: `Bearer ${this.apiKey}`,\n },\n body: jsonStringify(payload),\n })\n if (!res.ok) {\n await handleApiError(res)\n }\n\n return res.json()\n }\n\n protected async get(endpoint: string) {\n const res = await this._fetch(`${this.baseUrl}/${endpoint}`, {\n headers: { ...this.headers, authorization: `Bearer ${this.apiKey}` },\n })\n\n if (!res.ok) {\n await handleApiError(res)\n }\n\n return res.json()\n }\n\n listAccounts(): Promise<ListAccountsResult> {\n return this.get('accounts')\n }\n\n listUsers(): Promise<ListUsersResult> {\n return this.get('users')\n }\n\n /**\n * Applies an accounts specification to Zodiac OS.\n */\n applyConstellation(\n workspaceId: UUID,\n payload: ApplyConstellationPayload\n ): Promise<ApplyConstellationResult> {\n return this.postJson(\n `workspace/${workspaceId}/constellation/apply`,\n payload\n )\n }\n\n /**\n * Resolves an accounts specification to Zodiac OS.\n */\n resolveConstellation(\n workspaceId: UUID,\n payload: ResolveConstellationPayload\n ): Promise<ResolveConstellationResult> {\n return this.postJson(\n `workspace/${workspaceId}/constellation/resolve`,\n payload\n )\n }\n}\n\nexport class ApiRequestError extends Error {\n public readonly status: number\n public readonly statusText: string\n public readonly details?: unknown\n\n constructor(\n message: string,\n opts: {\n status: number\n statusText: string\n details?: unknown\n cause?: unknown\n }\n ) {\n super(ApiRequestError.composeMessage(message, opts.details))\n this.name = 'ApiRequestError'\n this.status = opts.status\n this.statusText = opts.statusText\n this.details = opts.details\n if (opts.cause !== undefined) {\n ;(this as any).cause = opts.cause\n }\n }\n\n private static composeMessage(message: string, details?: unknown) {\n if (details == null) return message\n let detailsString: string\n try {\n detailsString =\n typeof details === 'string' ? details : jsonStringify(details, 2)\n } catch (_err) {\n detailsString = String(details)\n }\n return `${message}\\nDetails: ${detailsString}`\n }\n\n toString() {\n return `${this.name}: ${this.message}`\n }\n}\n\nasync function handleApiError(response: Response): Promise<never> {\n const contentType = response.headers.get('content-type')\n if (contentType?.includes('application/json')) {\n const errorData = (await response.json()) as ApiErrorResponse\n let error: ApiRequestError\n try {\n error = new ApiRequestError(errorData.error.message, {\n status: response.status,\n statusText: response.statusText,\n details: errorData.error.details,\n })\n } catch (jsonShapeError) {\n error = new ApiRequestError(\n `Failed parsing error response: ${jsonShapeError}`,\n {\n status: response.status,\n statusText: response.statusText,\n details: errorData,\n }\n )\n }\n throw error\n } else {\n throw new ApiRequestError(\n `${response.status} ${response.statusText}: ${response.url}`,\n {\n status: response.status,\n statusText: response.statusText,\n }\n )\n }\n}\n\n/** JSON.stringify with bigint support */\nconst jsonStringify = (value: unknown, indent?: number) =>\n JSON.stringify(\n value,\n (_, value) => {\n if (typeof value === 'bigint') {\n return value.toString()\n }\n\n return value\n },\n indent\n )\n"],"mappings":"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"}
1
+ {"version":3,"file":"api-CygEDU4N.mjs","names":[],"sources":["../src/paths.ts","../src/api.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\n/**\n * Resolve the project's `.zodiac/` directory — the shared home for SDK\n * codegen (`pull-org` emits the importable module here, `pull-contracts`\n * writes `allow.d.ts` alongside it).\n *\n * Pass `rootDir` to anchor explicitly (CLI commands do this using the\n * directory of the loaded `zodiac.config.ts`). Without a `rootDir`, walks\n * up from `cwd` to the nearest `zodiac.config.{ts,js,mjs,cjs}` so runtime\n * callers like `constellation()` work even when invoked from a subdirectory.\n */\nexport function resolveZodiacDir(rootDir?: string): string {\n return join(rootDir ?? findProjectRoot(), '.zodiac')\n}\n\nconst CONFIG_FILENAMES = [\n 'zodiac.config.ts',\n 'zodiac.config.js',\n 'zodiac.config.mjs',\n 'zodiac.config.cjs',\n]\n\nfunction findProjectRoot(): string {\n let dir = process.cwd()\n while (dir !== dirname(dir)) {\n if (CONFIG_FILENAMES.some((name) => existsSync(join(dir, name)))) return dir\n dir = dirname(dir)\n }\n return process.cwd()\n}\n","import type {\n ApplyConstellationPayload,\n ApplyConstellationResult,\n ResolveConstellationPayload,\n ResolveConstellationResult,\n ApiError as ApiErrorResponse,\n ListAccountsResult,\n ListUsersResult,\n} from '@zodiac-os/api-types'\nimport assert from 'assert'\nimport { UUID } from 'crypto'\n\nexport type Options = {\n workspace?: string\n apiKey?: string\n baseUrl?: string\n fetch?: typeof globalThis.fetch\n headers?: Record<string, string>\n}\n\nconst { ZODIAC_API_KEY, ZODIAC_API_URL = 'https://app.zodiac.eco/api/v1' } =\n process.env\n\nexport class ApiClient {\n private apiKey: string\n private baseUrl: string\n private _fetch: typeof fetch\n private headers: Record<string, string>\n\n constructor({\n baseUrl = ZODIAC_API_URL,\n fetch: customFetch = fetch,\n headers = {},\n apiKey = ZODIAC_API_KEY,\n }: Options = {}) {\n this.baseUrl = baseUrl.replace(/\\/$/, '')\n this._fetch = customFetch\n this.headers = headers\n\n assert(\n apiKey,\n 'No API key provided to the API client. Either pass it as the \"apiKey\" option or set the ZODIAC_API_KEY environment variable.'\n )\n\n this.apiKey = apiKey\n }\n\n protected async postJson(endpoint: string, payload: unknown) {\n const res = await this._fetch(`${this.baseUrl}/${endpoint}`, {\n method: 'POST',\n headers: {\n ...this.headers,\n 'content-type': 'application/json',\n authorization: `Bearer ${this.apiKey}`,\n },\n body: jsonStringify(payload),\n })\n if (!res.ok) {\n await handleApiError(res)\n }\n\n return res.json()\n }\n\n protected async get(endpoint: string) {\n const res = await this._fetch(`${this.baseUrl}/${endpoint}`, {\n headers: { ...this.headers, authorization: `Bearer ${this.apiKey}` },\n })\n\n if (!res.ok) {\n await handleApiError(res)\n }\n\n return res.json()\n }\n\n listAccounts(): Promise<ListAccountsResult> {\n return this.get('accounts')\n }\n\n listUsers(): Promise<ListUsersResult> {\n return this.get('users')\n }\n\n /**\n * Applies an accounts specification to Zodiac OS.\n */\n applyConstellation(\n workspaceId: UUID,\n payload: ApplyConstellationPayload\n ): Promise<ApplyConstellationResult> {\n return this.postJson(\n `workspace/${workspaceId}/constellation/apply`,\n payload\n )\n }\n\n /**\n * Resolves an accounts specification to Zodiac OS.\n */\n resolveConstellation(\n workspaceId: UUID,\n payload: ResolveConstellationPayload\n ): Promise<ResolveConstellationResult> {\n return this.postJson(\n `workspace/${workspaceId}/constellation/resolve`,\n payload\n )\n }\n}\n\nexport class ApiRequestError extends Error {\n public readonly status: number\n public readonly statusText: string\n public readonly details?: unknown\n\n constructor(\n message: string,\n opts: {\n status: number\n statusText: string\n details?: unknown\n cause?: unknown\n }\n ) {\n super(ApiRequestError.composeMessage(message, opts.details))\n this.name = 'ApiRequestError'\n this.status = opts.status\n this.statusText = opts.statusText\n this.details = opts.details\n if (opts.cause !== undefined) {\n ;(this as any).cause = opts.cause\n }\n }\n\n private static composeMessage(message: string, details?: unknown) {\n if (details == null) return message\n let detailsString: string\n try {\n detailsString =\n typeof details === 'string' ? details : jsonStringify(details, 2)\n } catch (_err) {\n detailsString = String(details)\n }\n return `${message}\\nDetails: ${detailsString}`\n }\n\n toString() {\n return `${this.name}: ${this.message}`\n }\n}\n\nasync function handleApiError(response: Response): Promise<never> {\n const contentType = response.headers.get('content-type')\n if (contentType?.includes('application/json')) {\n const errorData = (await response.json()) as ApiErrorResponse\n let error: ApiRequestError\n try {\n error = new ApiRequestError(errorData.error.message, {\n status: response.status,\n statusText: response.statusText,\n details: errorData.error.details,\n })\n } catch (jsonShapeError) {\n error = new ApiRequestError(\n `Failed parsing error response: ${jsonShapeError}`,\n {\n status: response.status,\n statusText: response.statusText,\n details: errorData,\n }\n )\n }\n throw error\n } else {\n throw new ApiRequestError(\n `${response.status} ${response.statusText}: ${response.url}`,\n {\n status: response.status,\n statusText: response.statusText,\n }\n )\n }\n}\n\n/** JSON.stringify with bigint support */\nconst jsonStringify = (value: unknown, indent?: number) =>\n JSON.stringify(\n value,\n (_, value) => {\n if (typeof value === 'bigint') {\n return value.toString()\n }\n\n return value\n },\n indent\n )\n"],"mappings":"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"}
@@ -6,7 +6,14 @@ type ContractsNode = `0x${string}` | {
6
6
  [name: string]: ContractsNode;
7
7
  };
8
8
  interface ZodiacConfig {
9
- apiKey: `zodiac_${string}`;
9
+ /**
10
+ * API key authorizing this directory against a Zodiac org.
11
+ *
12
+ * Optional — defaults to `process.env.ZODIAC_API_KEY` (populated by
13
+ * `zodiac init`). Set this explicitly only when you need to override
14
+ * the env var from inside the config file.
15
+ */
16
+ apiKey?: `zodiac_${string}`;
10
17
  /**
11
18
  * Contracts the `allow` kit should know about, keyed by chain prefix.
12
19
  * Nested objects are allowed for grouping related addresses.
@@ -19,8 +26,9 @@ interface ZodiacConfig {
19
26
  */
20
27
  abisDir?: string;
21
28
  }
22
- /** User-provided config plus the directory it was loaded from. */
23
- interface ResolvedConfig extends ZodiacConfig {
29
+ /** User-provided config plus the resolved API key + project root. */
30
+ interface ResolvedConfig extends Omit<ZodiacConfig, 'apiKey'> {
31
+ apiKey: `zodiac_${string}`;
24
32
  rootDir: string;
25
33
  }
26
34
  /**
@@ -30,7 +38,7 @@ interface ResolvedConfig extends ZodiacConfig {
30
38
  * recursive `ContractsNode` union.
31
39
  */
32
40
  type DefineConfigInput = {
33
- apiKey: `zodiac_${string}`;
41
+ apiKey?: `zodiac_${string}`;
34
42
  contracts?: Record<string, unknown>;
35
43
  abisDir?: string;
36
44
  };
@@ -43,7 +51,17 @@ type ValidateContracts<C> = { [K in keyof C]: C[K] extends `0x${string}` ? C[K]
43
51
  declare const defineConfig: <const T extends DefineConfigInput>(config: T & {
44
52
  contracts?: ValidateContracts<NonNullable<T["contracts"]>>;
45
53
  }) => T;
46
- declare function loadConfig(configPath?: string): Promise<ResolvedConfig>;
54
+ type LoadConfigOptions = {
55
+ /**
56
+ * Called when neither `config.apiKey` nor `process.env.ZODIAC_API_KEY` is
57
+ * set. Receives the resolved project root and must return a freshly minted
58
+ * API key. Typically wired to the interactive `init()` flow.
59
+ *
60
+ * If omitted, `loadConfig` throws when no key is found.
61
+ */
62
+ onMissingKey?: (rootDir: string) => Promise<string>;
63
+ };
64
+ declare function loadConfig(configPath?: string, options?: LoadConfigOptions): Promise<ResolvedConfig>;
47
65
  declare const DEFAULT_ABIS_DIR = "abis";
48
66
  declare function resolveAbisDir(config: ResolvedConfig): string;
49
67
  //#endregion
@@ -3,7 +3,7 @@ import { dirname, resolve } from "path";
3
3
  //#region src/cli/config.ts
4
4
  const defineConfig = (config) => config;
5
5
  const DEFAULT_CONFIG_PATH = "zodiac.config.ts";
6
- async function loadConfig(configPath = DEFAULT_CONFIG_PATH) {
6
+ async function loadConfig(configPath = DEFAULT_CONFIG_PATH, options = {}) {
7
7
  const absolutePath = resolve(process.cwd(), configPath);
8
8
  let mod;
9
9
  try {
@@ -14,12 +14,30 @@ async function loadConfig(configPath = DEFAULT_CONFIG_PATH) {
14
14
  }
15
15
  const config = mod.default ?? mod.config;
16
16
  if (!config) throw new Error(`Config file must export a default value or a named "config" export: ${absolutePath}`);
17
- if (!config.apiKey) throw new Error(`Config is missing required field "apiKey"`);
17
+ const rootDir = dirname(absolutePath);
18
+ const apiKey = await resolveApiKey(config, rootDir, options);
18
19
  return {
19
20
  ...config,
20
- rootDir: dirname(absolutePath)
21
+ apiKey,
22
+ rootDir
21
23
  };
22
24
  }
25
+ const resolveApiKey = async (config, rootDir, { onMissingKey }) => {
26
+ if (config.apiKey != null) {
27
+ if (!isApiKey(config.apiKey)) throw new Error("`apiKey` in zodiac.config.ts is malformed: a valid Zodiac API key starts with \"zodiac_\". Either remove the field to use ZODIAC_API_KEY, or run `zodiac init` to mint a fresh key.");
28
+ return config.apiKey;
29
+ }
30
+ const fromEnv = process.env.ZODIAC_API_KEY;
31
+ if (fromEnv != null && fromEnv !== "") {
32
+ if (!isApiKey(fromEnv)) throw new Error("ZODIAC_API_KEY is set but malformed: a valid Zodiac API key starts with \"zodiac_\". Run `zodiac init` to mint a fresh key.");
33
+ return fromEnv;
34
+ }
35
+ if (onMissingKey == null) throw new Error("No Zodiac API key found. Set ZODIAC_API_KEY in your environment, or run `zodiac init` to generate one.");
36
+ const minted = await onMissingKey(rootDir);
37
+ if (!isApiKey(minted)) throw new Error(`onMissingKey returned an invalid Zodiac API key (expected a value starting with "zodiac_").`);
38
+ return minted;
39
+ };
40
+ const isApiKey = (value) => value != null && value.startsWith("zodiac_");
23
41
  const DEFAULT_ABIS_DIR = "abis";
24
42
  function resolveAbisDir(config) {
25
43
  return resolve(config.rootDir, config.abisDir ?? "abis");
@@ -1 +1 @@
1
- {"version":3,"file":"config.mjs","names":[],"sources":["../../src/cli/config.ts"],"sourcesContent":["import { pathToFileURL } from 'url'\nimport { dirname, 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 (config file's directory).\n * Defaults to `./abis`.\n */\n abisDir?: string\n}\n\n/** User-provided config plus the directory it was loaded from. */\nexport interface ResolvedConfig extends ZodiacConfig {\n rootDir: 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}\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<ResolvedConfig> {\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, rootDir: dirname(absolutePath) }\n}\n\nexport const DEFAULT_ABIS_DIR = 'abis'\n\nexport function resolveAbisDir(config: ResolvedConfig): string {\n return resolve(config.rootDir, config.abisDir ?? DEFAULT_ABIS_DIR)\n}\n"],"mappings":";;;AAqDA,MAAa,gBACX,WAGM;AAER,MAAM,sBAAsB;AAE5B,eAAsB,WACpB,aAAqB,qBACI;CACzB,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;EAAE,GAAG;EAAQ,SAAS,QAAQ,aAAa;EAAE;;AAGtD,MAAa,mBAAmB;AAEhC,SAAgB,eAAe,QAAgC;AAC7D,QAAO,QAAQ,OAAO,SAAS,OAAO,WAAA,OAA4B"}
1
+ {"version":3,"file":"config.mjs","names":[],"sources":["../../src/cli/config.ts"],"sourcesContent":["import { pathToFileURL } from 'url'\nimport { dirname, 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 /**\n * API key authorizing this directory against a Zodiac org.\n *\n * Optional — defaults to `process.env.ZODIAC_API_KEY` (populated by\n * `zodiac init`). Set this explicitly only when you need to override\n * the env var from inside the config file.\n */\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 (config file's directory).\n * Defaults to `./abis`.\n */\n abisDir?: string\n}\n\n/** User-provided config plus the resolved API key + project root. */\nexport interface ResolvedConfig extends Omit<ZodiacConfig, 'apiKey'> {\n apiKey: `zodiac_${string}`\n rootDir: 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}\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\ntype LoadConfigOptions = {\n /**\n * Called when neither `config.apiKey` nor `process.env.ZODIAC_API_KEY` is\n * set. Receives the resolved project root and must return a freshly minted\n * API key. Typically wired to the interactive `init()` flow.\n *\n * If omitted, `loadConfig` throws when no key is found.\n */\n onMissingKey?: (rootDir: string) => Promise<string>\n}\n\nexport async function loadConfig(\n configPath: string = DEFAULT_CONFIG_PATH,\n options: LoadConfigOptions = {}\n): Promise<ResolvedConfig> {\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 const rootDir = dirname(absolutePath)\n const apiKey = await resolveApiKey(config, rootDir, options)\n\n return { ...config, apiKey, rootDir }\n}\n\nconst resolveApiKey = async (\n config: ZodiacConfig,\n rootDir: string,\n { onMissingKey }: LoadConfigOptions\n): Promise<`zodiac_${string}`> => {\n if (config.apiKey != null) {\n if (!isApiKey(config.apiKey)) {\n throw new Error(\n '`apiKey` in zodiac.config.ts is malformed: a valid Zodiac API key starts with \"zodiac_\". Either remove the field to use ZODIAC_API_KEY, or run `zodiac init` to mint a fresh key.'\n )\n }\n return config.apiKey\n }\n\n const fromEnv = process.env.ZODIAC_API_KEY\n if (fromEnv != null && fromEnv !== '') {\n if (!isApiKey(fromEnv)) {\n throw new Error(\n 'ZODIAC_API_KEY is set but malformed: a valid Zodiac API key starts with \"zodiac_\". Run `zodiac init` to mint a fresh key.'\n )\n }\n return fromEnv\n }\n\n if (onMissingKey == null) {\n throw new Error(\n 'No Zodiac API key found. Set ZODIAC_API_KEY in your environment, or run `zodiac init` to generate one.'\n )\n }\n\n const minted = await onMissingKey(rootDir)\n if (!isApiKey(minted)) {\n throw new Error(\n `onMissingKey returned an invalid Zodiac API key (expected a value starting with \"zodiac_\").`\n )\n }\n return minted\n}\n\nconst isApiKey = (value: string | undefined): value is `zodiac_${string}` =>\n value != null && value.startsWith('zodiac_')\n\nexport const DEFAULT_ABIS_DIR = 'abis'\n\nexport function resolveAbisDir(config: ResolvedConfig): string {\n return resolve(config.rootDir, config.abisDir ?? DEFAULT_ABIS_DIR)\n}\n"],"mappings":";;;AA6DA,MAAa,gBACX,WAGM;AAER,MAAM,sBAAsB;AAa5B,eAAsB,WACpB,aAAqB,qBACrB,UAA6B,EAAE,EACN;CACzB,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;CAGH,MAAM,UAAU,QAAQ,aAAa;CACrC,MAAM,SAAS,MAAM,cAAc,QAAQ,SAAS,QAAQ;AAE5D,QAAO;EAAE,GAAG;EAAQ;EAAQ;EAAS;;AAGvC,MAAM,gBAAgB,OACpB,QACA,SACA,EAAE,mBAC8B;AAChC,KAAI,OAAO,UAAU,MAAM;AACzB,MAAI,CAAC,SAAS,OAAO,OAAO,CAC1B,OAAM,IAAI,MACR,sLACD;AAEH,SAAO,OAAO;;CAGhB,MAAM,UAAU,QAAQ,IAAI;AAC5B,KAAI,WAAW,QAAQ,YAAY,IAAI;AACrC,MAAI,CAAC,SAAS,QAAQ,CACpB,OAAM,IAAI,MACR,8HACD;AAEH,SAAO;;AAGT,KAAI,gBAAgB,KAClB,OAAM,IAAI,MACR,yGACD;CAGH,MAAM,SAAS,MAAM,aAAa,QAAQ;AAC1C,KAAI,CAAC,SAAS,OAAO,CACnB,OAAM,IAAI,MACR,8FACD;AAEH,QAAO;;AAGT,MAAM,YAAY,UAChB,SAAS,QAAQ,MAAM,WAAW,UAAU;AAE9C,MAAa,mBAAmB;AAEhC,SAAgB,eAAe,QAAgC;AAC7D,QAAO,QAAQ,OAAO,SAAS,OAAO,WAAA,OAA4B"}
package/dist/cli.mjs CHANGED
@@ -2,14 +2,141 @@
2
2
  import { n as resolveZodiacDir, t as ApiClient } from "./api-CygEDU4N.mjs";
3
3
  import { a as walkContracts, i as readAbi, n as chainIdFor, o as writeAbi, r as abiFilePath } from "./networks-BTW1qAAa.mjs";
4
4
  import { loadConfig, resolveAbisDir } from "./cli/config.mjs";
5
- import fs from "node:fs";
6
- import path from "node:path";
5
+ import fs, { existsSync, readFileSync, writeFileSync } from "node:fs";
6
+ import path, { basename, join, resolve } from "node:path";
7
7
  import { invariant } from "@epic-web/invariant";
8
8
  import { getAddress } from "ethers";
9
9
  import { Command } from "commander";
10
+ import { config } from "dotenv";
11
+ import { randomBytes } from "node:crypto";
12
+ import { createServer } from "node:http";
13
+ import open from "open";
10
14
  import { join as join$1 } from "path";
11
15
  import { ModuleKind, Project, ScriptTarget, VariableDeclarationKind } from "ts-morph";
12
- import { mkdirSync, writeFileSync } from "fs";
16
+ import { mkdirSync, writeFileSync as writeFileSync$1 } from "fs";
17
+ //#region src/cli/commands/init.ts
18
+ const DEFAULT_APP_URL = "https://app.zodiac.eco";
19
+ const CALLBACK_TIMEOUT_MS = 300 * 1e3;
20
+ const init = async (options = {}) => {
21
+ const rootDir = options.rootDir ?? process.cwd();
22
+ const appUrl = (options.appUrl ?? process.env.ZODIAC_APP_URL ?? DEFAULT_APP_URL).replace(/\/$/, "");
23
+ const label = basename(resolve(rootDir)) || "zodiac-cli";
24
+ const state = randomBytes(32).toString("base64url");
25
+ const appOrigin = new URL(appUrl).origin;
26
+ const { port, waitForKey, close } = await startCallbackServer({
27
+ appOrigin,
28
+ expectedState: state
29
+ });
30
+ const callbackUrl = `http://127.0.0.1:${port}/callback`;
31
+ const authUrl = new URL("/cli-auth", appUrl);
32
+ authUrl.searchParams.set("callback", callbackUrl);
33
+ authUrl.searchParams.set("state", state);
34
+ authUrl.searchParams.set("label", label);
35
+ console.log(`Opening ${authUrl} in your browser. Approve the request to receive an API key.`);
36
+ await open(authUrl.toString());
37
+ let key;
38
+ try {
39
+ key = await waitForKey(CALLBACK_TIMEOUT_MS);
40
+ } finally {
41
+ await close();
42
+ }
43
+ const envPath = join(rootDir, ".env");
44
+ const apiUrl = `${appUrl}/api/v1`;
45
+ writeEnv(envPath, {
46
+ ZODIAC_API_KEY: key,
47
+ ZODIAC_API_URL: apiUrl
48
+ });
49
+ console.log(`✅ API key written to ${envPath}`);
50
+ return key;
51
+ };
52
+ const startCallbackServer = async ({ appOrigin, expectedState }) => {
53
+ let resolveKey = () => {};
54
+ let rejectKey = () => {};
55
+ const keyPromise = new Promise((res, rej) => {
56
+ resolveKey = res;
57
+ rejectKey = rej;
58
+ });
59
+ const handler = (req, res) => {
60
+ const isAllowedOrigin = req.headers.origin === appOrigin;
61
+ if (isAllowedOrigin) {
62
+ res.setHeader("Access-Control-Allow-Origin", appOrigin);
63
+ res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
64
+ res.setHeader("Access-Control-Allow-Headers", "content-type");
65
+ if (req.headers["access-control-request-private-network"] === "true") res.setHeader("Access-Control-Allow-Private-Network", "true");
66
+ }
67
+ if (req.method === "OPTIONS") {
68
+ res.statusCode = isAllowedOrigin ? 204 : 403;
69
+ res.end();
70
+ return;
71
+ }
72
+ if (req.method !== "POST" || req.url !== "/callback") {
73
+ res.statusCode = 404;
74
+ res.end();
75
+ return;
76
+ }
77
+ if (!isAllowedOrigin) {
78
+ res.statusCode = 403;
79
+ res.end();
80
+ return;
81
+ }
82
+ const chunks = [];
83
+ req.on("data", (chunk) => chunks.push(chunk));
84
+ req.on("end", () => {
85
+ try {
86
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
87
+ if (typeof body.state !== "string" || body.state !== expectedState) {
88
+ res.statusCode = 403;
89
+ res.end();
90
+ return;
91
+ }
92
+ if (typeof body.key !== "string" || !body.key.startsWith("zodiac_")) {
93
+ res.statusCode = 400;
94
+ res.end();
95
+ return;
96
+ }
97
+ res.setHeader("Connection", "close");
98
+ res.statusCode = 200;
99
+ res.end(() => resolveKey(body.key));
100
+ } catch {
101
+ res.statusCode = 400;
102
+ res.end();
103
+ }
104
+ });
105
+ };
106
+ const server = createServer(handler);
107
+ await new Promise((res) => server.listen(0, "127.0.0.1", res));
108
+ const address = server.address();
109
+ if (address == null || typeof address === "string") throw new Error("Failed to bind loopback callback server");
110
+ const port = address.port;
111
+ const close = () => new Promise((resolve) => {
112
+ server.close(() => resolve());
113
+ });
114
+ const waitForKey = (timeoutMs) => Promise.race([keyPromise, new Promise((_, rej) => setTimeout(() => {
115
+ rejectKey(/* @__PURE__ */ new Error(`Timed out waiting for CLI auth callback`));
116
+ rej(/* @__PURE__ */ new Error(`Timed out waiting for CLI auth callback`));
117
+ }, timeoutMs))]);
118
+ return {
119
+ port,
120
+ waitForKey,
121
+ close
122
+ };
123
+ };
124
+ const writeEnv = (envPath, vars) => {
125
+ let contents = existsSync(envPath) ? readFileSync(envPath, "utf8") : "";
126
+ for (const [key, value] of Object.entries(vars)) contents = upsertEnvLine(contents, key, value);
127
+ writeFileSync(envPath, contents, "utf8");
128
+ };
129
+ const upsertEnvLine = (contents, key, value) => {
130
+ const line = `${key}=${value}`;
131
+ const lines = contents.split(/\r?\n/);
132
+ const idx = lines.findIndex((l) => new RegExp(`^\\s*${key}\\s*=`).test(l));
133
+ if (idx >= 0) {
134
+ lines[idx] = line;
135
+ return lines.join("\n");
136
+ }
137
+ return contents.length === 0 || contents.endsWith("\n") ? `${contents}${line}\n` : `${contents}\n${line}\n`;
138
+ };
139
+ //#endregion
13
140
  //#region src/cli/commands/pullOrg.ts
14
141
  const toLiteral = (value, indent = 0) => {
15
142
  const pad = " ".repeat(indent);
@@ -105,7 +232,7 @@ const pullOrg = async (config) => {
105
232
  }
106
233
  const outDir = resolveZodiacDir(config.rootDir);
107
234
  mkdirSync(outDir, { recursive: true });
108
- writeFileSync(join$1(outDir, "package.json"), JSON.stringify({
235
+ writeFileSync$1(join$1(outDir, "package.json"), JSON.stringify({
109
236
  type: "commonjs",
110
237
  main: "index.js",
111
238
  types: "index.d.ts"
@@ -147,7 +274,7 @@ declare global {
147
274
  }
148
275
  }
149
276
  `;
150
- writeFileSync(join$1(outDir, fileName), contents);
277
+ writeFileSync$1(join$1(outDir, fileName), contents);
151
278
  }
152
279
  };
153
280
  //#endregion
@@ -184,7 +311,7 @@ function generateAllowTypes(abisDir, contractsConfig) {
184
311
  });
185
312
  }
186
313
  const out = [];
187
- out.push("// AUTO-GENERATED by `zodiac-os pull-contracts`. Do not edit.");
314
+ out.push("// AUTO-GENERATED by `zodiac pull-contracts`. Do not edit.");
188
315
  out.push("/* eslint-disable */");
189
316
  out.push("");
190
317
  out.push(`import type { FunctionPermission, TargetPermission } from "zodiac-roles-sdk";`);
@@ -358,7 +485,7 @@ const pullContracts = async (config) => {
358
485
  if (missing > 0) {
359
486
  console.log("");
360
487
  console.log("Missing ABIs must be provided manually. Paste the contract");
361
- console.log("ABI JSON at the paths listed above and re-run `zodiac-os pull-contracts`.");
488
+ console.log("ABI JSON at the paths listed above and re-run `zodiac pull-contracts`.");
362
489
  }
363
490
  writeGenerated(generatedFile, generateAllowTypes(abisDir, config.contracts));
364
491
  console.log("");
@@ -378,17 +505,25 @@ function report(chain, segments, address, status, file, reason) {
378
505
  }
379
506
  //#endregion
380
507
  //#region src/cli/run.ts
508
+ config({ quiet: true });
509
+ const loadConfigOrInit = (configPath) => loadConfig(configPath, { onMissingKey: async (rootDir) => {
510
+ console.log("No ZODIAC_API_KEY found. Starting authorization to mint one for this directory…");
511
+ return init({ rootDir });
512
+ } });
381
513
  const run = async (argv = process.argv) => {
382
514
  const program = new Command();
383
- program.name("zodiac-os").description("Zodiac OS SDK CLI – pull org data and contract ABIs").version("1.0.0").option("-c, --config <path>", "path to the config file", "zodiac.config.ts");
384
- program.command("pull-org").description("Fetch Zodiac users and vaults, generate TypeScript types").action(async (_opts, cmd) => {
385
- await pullOrg(await loadConfig(cmd.optsWithGlobals().config));
515
+ program.name("zodiac").description("Zodiac SDK CLI – pull org data and contract ABIs").version("1.0.0").option("-c, --config <path>", "path to the config file", "zodiac.config.ts");
516
+ program.command("init").description("Authorize this directory with a Zodiac org. Opens a browser to mint an API key and writes it to .env.").option("--app-url <url>", "Override the Zodiac app URL (defaults to ZODIAC_APP_URL or app.zodiac.eco)").action(async (opts) => {
517
+ await init({ appUrl: opts.appUrl });
518
+ });
519
+ program.command("pull-org").description("Fetch Zodiac users and accounts, generate TypeScript types").action(async (_opts, cmd) => {
520
+ await pullOrg(await loadConfigOrInit(cmd.optsWithGlobals().config));
386
521
  });
387
522
  program.command("pull-contracts").description("Fetch contract ABIs, generate typed permissions kit").action(async (_opts, cmd) => {
388
- await pullContracts(await loadConfig(cmd.optsWithGlobals().config));
523
+ await pullContracts(await loadConfigOrInit(cmd.optsWithGlobals().config));
389
524
  });
390
525
  program.command("pull").description("Fetch Zodiac org and contracts ABI, generate SDK functions").action(async (_opts, cmd) => {
391
- const config = await loadConfig(cmd.optsWithGlobals().config);
526
+ const config = await loadConfigOrInit(cmd.optsWithGlobals().config);
392
527
  await Promise.all([pullOrg(config), pullContracts(config)]);
393
528
  });
394
529
  await program.parseAsync(argv);
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":["join"],"sources":["../src/cli/commands/pullOrg.ts","../src/allow/fetch.ts","../src/allow/codegen.ts","../src/cli/commands/pullContracts.ts","../src/cli/run.ts","../src/cli/index.ts"],"sourcesContent":["import type { ResolvedConfig } from '../config'\nimport { ApiClient } from '../../api'\nimport { invariant } from '@epic-web/invariant'\nimport { getAddress } from 'ethers'\nimport {\n ModuleKind,\n Project,\n ScriptTarget,\n VariableDeclarationKind,\n} from 'ts-morph'\nimport { mkdirSync, writeFileSync } from 'fs'\nimport { join } from 'path'\nimport { resolveZodiacDir } from '../../paths'\n\nconst toLiteral = (value: unknown, indent = 0): string => {\n const pad = ' '.repeat(indent)\n const childPad = ' '.repeat(indent + 1)\n\n if (value === null) return 'null'\n if (typeof value === 'bigint') return `${value}n`\n if (typeof value === 'string') return JSON.stringify(value)\n if (typeof value === 'number' || typeof value === 'boolean')\n return String(value)\n if (Array.isArray(value)) {\n if (value.length === 0) return '[]'\n return `[\\n${value.map((v) => `${childPad}${toLiteral(v, indent + 1)}`).join(',\\n')},\\n${pad}]`\n }\n if (typeof value === 'object') {\n const entries = Object.entries(value as Record<string, unknown>)\n if (entries.length === 0) return '{}'\n const props = entries.map(\n ([k, v]) =>\n `${childPad}${/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? k : JSON.stringify(k)}: ${toLiteral(v, indent + 1)}`\n )\n return `{\\n${props.join(',\\n')},\\n${pad}}`\n }\n return String(value)\n}\n\nexport const pullOrg = async (config: ResolvedConfig) => {\n const client = new ApiClient({\n apiKey: config.apiKey,\n })\n\n const [users, workspaceAccounts] = await Promise.all([\n client.listUsers(),\n client.listAccounts(),\n ])\n\n // Fetch fresh on-chain state via `resolveConstellation` for every\n // account we can resolve:\n // - `spec` present → pass the stored apply-time node verbatim\n // (deployed nodes match on-chain; undeployed ones derive via\n // CREATE2 from the stored nonce + config).\n // - `vault: true` with no spec → treat as a pre-existing on-chain\n // SAFE (e.g. a workspace vault created outside the\n // constellation-as-code flow). The resolver finds it on-chain.\n // - `vault: false` with no spec → a constituent of a still-pending\n // constellation that's never been deployed. We can't usefully\n // resolve it, so skip; the codegen emits minimal fields.\n const allAccounts = workspaceAccounts.flatMap((ws) => ws.accounts)\n const resolvableAccounts = allAccounts.filter(\n (a) => a.spec != null || a.vault\n )\n const resolved = new Map<\n string,\n Awaited<ReturnType<typeof client.resolveConstellation>>['result'][number]\n >()\n if (resolvableAccounts.length > 0) {\n const response = await client.resolveConstellation(\n workspaceAccounts[0].workspaceId, // any workspace works for the resolve route\n {\n specification: resolvableAccounts.map((account, i) =>\n account.spec != null\n ? account.spec\n : {\n // Synthesize a ref for vault-fallback entries (no stored\n // spec). The /resolve payload requires a ref on every\n // entry; the value isn't used downstream beyond echoing\n // back into the response, so a positional id is fine.\n ref: `vault_${i}` as Lowercase<string>,\n type: 'SAFE',\n chain: account.chain,\n address: account.address,\n }\n ),\n }\n )\n invariant(\n response?.result?.length === resolvableAccounts.length,\n `resolveConstellation returned ${response?.result?.length ?? 0} accounts for ${resolvableAccounts.length} accounts`\n )\n resolvableAccounts.forEach((account, i) => {\n resolved.set(account.id, response.result[i])\n })\n }\n\n // Group accounts by type into separate bracket-access namespaces:\n // `safes`, `rolesMods`, `delays`. This way `eth.safe[...]`\n // IntelliSense only suggests SAFE labels, and the label-collision\n // suffix only kicks in when two accounts **of the same type** share\n // a label.\n const accountsRecord: Record<string, unknown> = {}\n for (const ws of workspaceAccounts) {\n const safes: Record<string, unknown> = {}\n const rolesMods: Record<string, unknown> = {}\n const delays: Record<string, unknown> = {}\n\n const bucketsByType = {\n SAFE: safes,\n ROLES: rolesMods,\n DELAY: delays,\n } as const\n\n type NodeType = 'SAFE' | 'ROLES' | 'DELAY'\n const isNodeType = (type: string): type is NodeType =>\n type === 'SAFE' || type === 'ROLES' || type === 'DELAY'\n\n // Count labels per type so we only suffix within-type collisions.\n const labelCountByType: Record<NodeType, Map<string, number>> = {\n SAFE: new Map(),\n ROLES: new Map(),\n DELAY: new Map(),\n }\n for (const account of ws.accounts) {\n if (!isNodeType(account.type)) continue\n const counts = labelCountByType[account.type]\n counts.set(account.label, (counts.get(account.label) ?? 0) + 1)\n }\n\n for (const account of ws.accounts) {\n if (!isNodeType(account.type)) continue\n const onChain = resolved.get(account.id)\n const counts = labelCountByType[account.type]\n const key =\n (counts.get(account.label) ?? 0) > 1\n ? `${account.label} (${getAddress(account.address)})`\n : account.label\n bucketsByType[account.type][key] = {\n id: account.id,\n label: account.label,\n address: account.address,\n chain: account.chain,\n vault: account.vault,\n ...(onChain?.type === 'SAFE' && {\n threshold: onChain.threshold,\n owners: [...onChain.owners],\n modules: [...onChain.modules],\n }),\n }\n }\n\n accountsRecord[ws.workspaceName] = {\n workspaceId: ws.workspaceId,\n workspaceName: ws.workspaceName,\n safes,\n rolesMods,\n delays,\n }\n }\n\n const nameCount = new Map<string, number>()\n for (const user of users) {\n nameCount.set(user.fullName, (nameCount.get(user.fullName) ?? 0) + 1)\n }\n\n const usersRecord: Record<string, unknown> = {}\n for (const user of users) {\n const handle =\n nameCount.get(user.fullName)! > 1\n ? `${user.fullName} (${user.id})`\n : user.fullName\n usersRecord[handle] = {\n id: user.id,\n fullName: user.fullName,\n personalSafes: user.personalSafes,\n }\n }\n\n const outDir = resolveZodiacDir(config.rootDir)\n\n mkdirSync(outDir, { recursive: true })\n\n // Pin CJS so `require()` works regardless of the parent package.json's type\n writeFileSync(\n join(outDir, 'package.json'),\n JSON.stringify(\n {\n type: 'commonjs',\n main: 'index.js',\n types: 'index.d.ts',\n },\n null,\n 2\n )\n )\n\n // Use ts-morph to generate TS, then emit JS + d.ts\n const project = new Project({\n compilerOptions: {\n declaration: true,\n module: ModuleKind.CommonJS,\n target: ScriptTarget.ESNext,\n outDir,\n },\n useInMemoryFileSystem: true,\n })\n\n const sourceFile = project.createSourceFile('index.ts', '')\n\n sourceFile.addVariableStatement({\n isExported: true,\n declarationKind: VariableDeclarationKind.Const,\n declarations: [\n {\n name: 'users',\n initializer: `${toLiteral(usersRecord)} as const`,\n },\n ],\n })\n\n sourceFile.addVariableStatement({\n isExported: true,\n declarationKind: VariableDeclarationKind.Const,\n declarations: [\n {\n name: 'accounts',\n initializer: `${toLiteral(accountsRecord)} as const`,\n },\n ],\n })\n\n const emitResult = sourceFile.getEmitOutput()\n for (const outputFile of emitResult.getOutputFiles()) {\n const filePath = outputFile.getFilePath()\n const fileName = filePath.includes('.d.ts') ? 'index.d.ts' : 'index.js'\n let contents = outputFile.getText()\n // Augment the SDK's global `ZodiacGeneratedCodegen` interface so\n // `constellation()`'s default type parameter picks up these literal\n // shapes automatically.\n if (fileName === 'index.d.ts') {\n contents += `\ndeclare global {\n interface ZodiacGeneratedCodegen {\n users: typeof users;\n accounts: typeof accounts;\n }\n}\n`\n }\n writeFileSync(join(outDir, fileName), contents)\n }\n}\n","import { chainIdFor, type ChainPrefix } from './networks'\n\nexport type AbiFragment = Record<string, any>\nexport type Abi = AbiFragment[]\n\n// Returns null on any failure so callers can fall back to a manual ABI file.\nexport async function fetchAbi(\n chainId: number,\n address: `0x${string}`\n): Promise<Abi | null> {\n const url = `https://api.abi.pub/v1/chains/${chainId}/etherscan?module=contract&action=getabi&address=${address}`\n let body: { status?: string; message?: string; result?: string }\n try {\n const resp = await fetch(url)\n if (!resp.ok) return null\n body = (await resp.json()) as typeof body\n } catch {\n return null\n }\n if (body.status !== '1' || typeof body.result !== 'string') return null\n try {\n const parsed = JSON.parse(body.result)\n if (!Array.isArray(parsed) || parsed.length === 0) return null\n return parsed as Abi\n } catch {\n return null\n }\n}\n\nexport const fetchAbiForPrefix = (\n prefix: ChainPrefix,\n address: `0x${string}`\n) => fetchAbi(chainIdFor(prefix), address)\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport { walkContracts, readAbi, type ContractNode } from './abi'\nimport type { Abi, AbiFragment } from './fetch'\n\n// Emits named tuple elements per ABI input — this is the reason we don't rely\n// on viem/abitype, which produces unnamed tuples (microsoft/TypeScript#44939).\nexport function generateAllowTypes(\n abisDir: string,\n contractsConfig: Record<string, any>\n): string {\n type Tree = Map<string, Tree | { node: ContractNode; abi: Abi }>\n\n const root: Tree = new Map()\n for (const node of walkContracts(contractsConfig)) {\n const abi = readAbi(abisDir, node)\n if (!abi) continue\n insertIntoTree(root, [node.chain, ...node.segments], { node, abi })\n }\n\n const out: string[] = []\n out.push('// AUTO-GENERATED by `zodiac-os pull-contracts`. Do not edit.')\n out.push('/* eslint-disable */')\n out.push('')\n out.push(\n `import type { FunctionPermission, TargetPermission } from \"zodiac-roles-sdk\";`\n )\n out.push(\n `import type { Scoping, Options, EVERYTHING } from \"@zodiac-os/sdk/allow\";`\n )\n out.push('')\n out.push('declare global {')\n out.push(' interface AllowKit ' + renderTree(root, ' '))\n out.push('}')\n out.push('')\n out.push('export {};')\n out.push('')\n return out.join('\\n')\n}\n\nfunction insertIntoTree(\n tree: Map<string, any>,\n segments: string[],\n leaf: { node: ContractNode; abi: Abi }\n): void {\n const [first, ...rest] = segments\n if (!first) throw new Error('empty segments')\n if (rest.length === 0) {\n tree.set(first, leaf)\n return\n }\n let child = tree.get(first)\n if (!child || child.node) {\n child = new Map()\n tree.set(first, child)\n }\n insertIntoTree(child, rest, leaf)\n}\n\nfunction renderTree(tree: Map<string, any>, indent: string): string {\n const lines: string[] = ['{']\n for (const [key, value] of tree) {\n const safeKey = renderPropKey(key)\n if (value instanceof Map) {\n lines.push(`${indent} ${safeKey}: ${renderTree(value, indent + ' ')};`)\n } else {\n const { node, abi } = value as { node: ContractNode; abi: Abi }\n lines.push(\n `${indent} ${safeKey}: ${renderContractType(node, abi, indent + ' ')};`\n )\n }\n }\n lines.push(`${indent}}`)\n return lines.join('\\n')\n}\n\nfunction renderPropKey(name: string): string {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name)\n}\n\nfunction renderContractType(\n node: ContractNode,\n abi: Abi,\n indent: string\n): string {\n const members: string[] = []\n const seen = new Set<string>()\n members.push(\n `${indent} [EVERYTHING]: (options?: Options) => TargetPermission;`\n )\n for (const fragment of abi) {\n if (fragment.type !== 'function') continue\n if (\n fragment.stateMutability === 'view' ||\n fragment.stateMutability === 'pure'\n ) {\n continue\n }\n const name = fragment.name as string\n if (!name || seen.has(name)) continue\n seen.add(name)\n members.push(renderFunctionSignature(fragment, indent + ' '))\n }\n return ['{', ...members, `${indent}}`].join('\\n')\n}\n\nfunction renderFunctionSignature(\n fragment: AbiFragment,\n indent: string\n): string {\n const name = renderPropKey(fragment.name as string)\n const params: string[] = []\n for (const input of (fragment.inputs as AbiFragment[]) ?? []) {\n const paramName = sanitizeParamName(\n input.name || `arg${params.length}`,\n params.length\n )\n params.push(`${paramName}?: Scoping<${tsTypeFor(input)}>`)\n }\n params.push(`options?: Options`)\n return `${indent}${name}: (${params.join(', ')}) => FunctionPermission;`\n}\n\nfunction sanitizeParamName(name: string, index: number): string {\n const cleaned = name.replace(/[^A-Za-z0-9_$]/g, '_')\n if (!cleaned || /^[0-9]/.test(cleaned)) return `arg${index}`\n // Named tuple element labels can't collide with TS reserved words.\n const reserved = new Set([\n 'function',\n 'class',\n 'new',\n 'number',\n 'string',\n 'object',\n 'boolean',\n 'symbol',\n 'default',\n 'return',\n 'this',\n 'void',\n 'delete',\n 'in',\n 'of',\n 'for',\n 'while',\n 'switch',\n 'case',\n 'if',\n 'else',\n 'null',\n 'true',\n 'false',\n 'undefined',\n 'any',\n 'never',\n 'unknown',\n ])\n return reserved.has(cleaned) ? `_${cleaned}` : cleaned\n}\n\nfunction tsTypeFor(fragment: AbiFragment): string {\n const type = fragment.type as string\n\n const arrayMatch = /^(.*)\\[(\\d*)\\]$/.exec(type)\n if (arrayMatch) {\n const inner: AbiFragment = { ...fragment, type: arrayMatch[1] }\n return `readonly (${tsTypeFor(inner)})[]`\n }\n\n if (type === 'tuple') {\n const components = (fragment.components as AbiFragment[]) ?? []\n if (components.length === 0) return 'Record<string, unknown>'\n const fields = components.map((c, i) => {\n const key = sanitizeParamName(c.name || `f${i}`, i)\n return `${renderPropKey(key)}: ${tsTypeFor(c)}`\n })\n return `{ ${fields.join('; ')} }`\n }\n\n if (type === 'address') return '`0x${string}`'\n if (type === 'bool') return 'boolean'\n if (type === 'string') return 'string'\n if (type === 'bytes') return 'import(\"ethers\").BytesLike'\n if (/^bytes\\d+$/.test(type)) return '`0x${string}`'\n if (/^u?int\\d*$/.test(type)) return 'import(\"ethers\").BigNumberish'\n return 'unknown'\n}\n\nexport function writeGenerated(outFile: string, source: string): void {\n fs.mkdirSync(path.dirname(outFile), { recursive: true })\n fs.writeFileSync(outFile, source, 'utf8')\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport type { ResolvedConfig } from '../config'\nimport { resolveAbisDir } from '../config'\nimport { resolveZodiacDir } from '../../paths'\nimport { abiFilePath, walkContracts, writeAbi } from '../../allow/abi'\nimport { fetchAbi } from '../../allow/fetch'\nimport { chainIdFor } from '../../allow/networks'\nimport { generateAllowTypes, writeGenerated } from '../../allow/codegen'\n\ntype Status = 'ok' | 'fetched' | 'missing'\n\nexport const pullContracts = async (config: ResolvedConfig) => {\n if (!config.contracts || Object.keys(config.contracts).length === 0) {\n console.log('No contracts defined in config, skipping.')\n return\n }\n\n const abisDir = resolveAbisDir(config)\n const generatedFile = path.join(\n resolveZodiacDir(config.rootDir),\n 'allow.d.ts'\n )\n\n let missing = 0\n let fetched = 0\n let existing = 0\n\n for (const node of walkContracts(config.contracts)) {\n const file = abiFilePath(abisDir, node)\n\n if (fs.existsSync(file)) {\n existing++\n report(node.chain, node.segments, node.address, 'ok', file)\n continue\n }\n\n let chainId: number\n try {\n chainId = chainIdFor(node.chain)\n } catch (error) {\n missing++\n report(\n node.chain,\n node.segments,\n node.address,\n 'missing',\n file,\n (error as Error).message\n )\n continue\n }\n\n const abi = await fetchAbi(chainId, node.address)\n if (!abi) {\n missing++\n report(\n node.chain,\n node.segments,\n node.address,\n 'missing',\n file,\n `api.abi.pub returned no ABI for chain ${chainId}`\n )\n continue\n }\n writeAbi(abisDir, node, abi)\n fetched++\n report(node.chain, node.segments, node.address, 'fetched', file)\n }\n\n console.log('')\n console.log(\n `Contracts summary: ${existing} existing, ${fetched} fetched, ${missing} missing.`\n )\n if (missing > 0) {\n console.log('')\n console.log('Missing ABIs must be provided manually. Paste the contract')\n console.log(\n 'ABI JSON at the paths listed above and re-run `zodiac-os pull-contracts`.'\n )\n }\n\n const source = generateAllowTypes(abisDir, config.contracts)\n writeGenerated(generatedFile, source)\n console.log('')\n console.log(`Wrote typings to ${path.relative(process.cwd(), generatedFile)}`)\n\n if (missing > 0) process.exit(1)\n}\n\nfunction report(\n chain: string,\n segments: string[],\n address: string,\n status: Status,\n file: string,\n reason?: string\n) {\n const label = `${chain}.${segments.join('.')}`.padEnd(40, ' ')\n const tag = {\n ok: ' cached ',\n fetched: ' fetched ',\n missing: ' MISSING ',\n }[status]\n const suffix = reason ? ` — ${reason}` : ''\n console.log(`${tag} ${label} ${address}${suffix}`)\n if (status === 'missing') {\n console.log(` → paste ABI at ${file}`)\n }\n}\n","import { Command } from 'commander'\nimport { loadConfig } from './config'\nimport { pullOrg } from './commands/pullOrg'\nimport { pullContracts } from './commands/pullContracts'\n\nexport const run = async (argv: string[] = process.argv) => {\n const program = new Command()\n\n program\n .name('zodiac-os')\n .description('Zodiac OS SDK CLI – pull org data and contract ABIs')\n .version('1.0.0')\n .option(\n '-c, --config <path>',\n 'path to the config file',\n 'zodiac.config.ts'\n )\n\n program\n .command('pull-org')\n .description('Fetch Zodiac users and vaults, generate TypeScript types')\n .action(async (_opts, cmd) => {\n const config = await loadConfig(cmd.optsWithGlobals().config)\n await pullOrg(config)\n })\n\n program\n .command('pull-contracts')\n .description('Fetch contract ABIs, generate typed permissions kit')\n .action(async (_opts, cmd) => {\n const config = await loadConfig(cmd.optsWithGlobals().config)\n await pullContracts(config)\n })\n\n program\n .command('pull')\n .description('Fetch Zodiac org and contracts ABI, generate SDK functions')\n .action(async (_opts, cmd) => {\n const config = await loadConfig(cmd.optsWithGlobals().config)\n await Promise.all([pullOrg(config), pullContracts(config)])\n })\n\n await program.parseAsync(argv)\n}\n","#!/usr/bin/env node\nimport { run } from './run'\n\nrun().then(\n () => {\n process.exit(0)\n },\n (error: unknown) => {\n if (error) console.error(error)\n process.exit(1)\n }\n)\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":["join","loadDotenv"],"sources":["../src/cli/commands/init.ts","../src/cli/commands/pullOrg.ts","../src/allow/fetch.ts","../src/allow/codegen.ts","../src/cli/commands/pullContracts.ts","../src/cli/run.ts","../src/cli/index.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto'\nimport {\n createServer,\n type IncomingMessage,\n type ServerResponse,\n} from 'node:http'\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { basename, join, resolve } from 'node:path'\nimport open from 'open'\n\nconst DEFAULT_APP_URL = 'https://app.zodiac.eco'\nconst CALLBACK_TIMEOUT_MS = 5 * 60 * 1000 // 5 minutes\n\ntype InitOptions = {\n rootDir?: string\n /** Override the Zodiac app base URL (defaults to ZODIAC_APP_URL env or app.zodiac.eco). */\n appUrl?: string\n}\n\nexport const init = async (options: InitOptions = {}): Promise<string> => {\n const rootDir = options.rootDir ?? process.cwd()\n const appUrl = (\n options.appUrl ??\n process.env.ZODIAC_APP_URL ??\n DEFAULT_APP_URL\n ).replace(/\\/$/, '')\n\n const label = basename(resolve(rootDir)) || 'zodiac-cli'\n const state = randomBytes(32).toString('base64url')\n\n const appOrigin = new URL(appUrl).origin\n\n const { port, waitForKey, close } = await startCallbackServer({\n appOrigin,\n expectedState: state,\n })\n\n const callbackUrl = `http://127.0.0.1:${port}/callback`\n const authUrl = new URL('/cli-auth', appUrl)\n authUrl.searchParams.set('callback', callbackUrl)\n authUrl.searchParams.set('state', state)\n authUrl.searchParams.set('label', label)\n\n console.log(\n `Opening ${authUrl} in your browser. Approve the request to receive an API key.`\n )\n await open(authUrl.toString())\n\n let key: string\n try {\n key = await waitForKey(CALLBACK_TIMEOUT_MS)\n } finally {\n await close()\n }\n\n const envPath = join(rootDir, '.env')\n const apiUrl = `${appUrl}/api/v1`\n writeEnv(envPath, { ZODIAC_API_KEY: key, ZODIAC_API_URL: apiUrl })\n\n console.log(`✅ API key written to ${envPath}`)\n\n return key\n}\n\ntype StartServerOptions = {\n appOrigin: string\n expectedState: string\n}\n\ntype StartServerResult = {\n port: number\n waitForKey: (timeoutMs: number) => Promise<string>\n close: () => Promise<void>\n}\n\nconst startCallbackServer = async ({\n appOrigin,\n expectedState,\n}: StartServerOptions): Promise<StartServerResult> => {\n let resolveKey: (key: string) => void = () => {}\n let rejectKey: (err: Error) => void = () => {}\n const keyPromise = new Promise<string>((res, rej) => {\n resolveKey = res\n rejectKey = rej\n })\n\n const handler = (req: IncomingMessage, res: ServerResponse) => {\n const origin = req.headers.origin\n const isAllowedOrigin = origin === appOrigin\n\n // CORS / Private Network Access preflight + actual response headers.\n if (isAllowedOrigin) {\n res.setHeader('Access-Control-Allow-Origin', appOrigin)\n res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS')\n res.setHeader('Access-Control-Allow-Headers', 'content-type')\n // Required for Chrome's Private Network Access preflight when an\n // HTTPS page targets http://localhost.\n if (req.headers['access-control-request-private-network'] === 'true') {\n res.setHeader('Access-Control-Allow-Private-Network', 'true')\n }\n }\n\n if (req.method === 'OPTIONS') {\n res.statusCode = isAllowedOrigin ? 204 : 403\n res.end()\n return\n }\n\n if (req.method !== 'POST' || req.url !== '/callback') {\n res.statusCode = 404\n res.end()\n return\n }\n\n if (!isAllowedOrigin) {\n res.statusCode = 403\n res.end()\n return\n }\n\n const chunks: Buffer[] = []\n req.on('data', (chunk: Buffer) => chunks.push(chunk))\n req.on('end', () => {\n try {\n const body = JSON.parse(Buffer.concat(chunks).toString('utf8'))\n if (typeof body.state !== 'string' || body.state !== expectedState) {\n // Don't reject — could be a stray request from a malicious local\n // process. Stay silent and keep waiting for the real one.\n res.statusCode = 403\n res.end()\n return\n }\n if (typeof body.key !== 'string' || !body.key.startsWith('zodiac_')) {\n res.statusCode = 400\n res.end()\n return\n }\n // Force the connection to close after the response so the browser\n // doesn't keep waiting on a keep-alive socket once the CLI exits.\n // Resolve only after 'finish' (response handed to the OS) so the\n // bytes can flush before the process tears down.\n res.setHeader('Connection', 'close')\n res.statusCode = 200\n res.end(() => resolveKey(body.key))\n } catch {\n res.statusCode = 400\n res.end()\n }\n })\n }\n\n const server = createServer(handler)\n await new Promise<void>((res) => server.listen(0, '127.0.0.1', res))\n const address = server.address()\n if (address == null || typeof address === 'string') {\n throw new Error('Failed to bind loopback callback server')\n }\n const port = address.port\n\n const close = (): Promise<void> =>\n new Promise<void>((resolve) => {\n server.close(() => resolve())\n })\n\n const waitForKey = (timeoutMs: number) =>\n Promise.race<string>([\n keyPromise,\n new Promise<string>((_, rej) =>\n setTimeout(() => {\n rejectKey(new Error(`Timed out waiting for CLI auth callback`))\n rej(new Error(`Timed out waiting for CLI auth callback`))\n }, timeoutMs)\n ),\n ])\n\n return { port, waitForKey, close }\n}\n\nconst writeEnv = (envPath: string, vars: Record<string, string>): void => {\n let contents = existsSync(envPath) ? readFileSync(envPath, 'utf8') : ''\n\n for (const [key, value] of Object.entries(vars)) {\n contents = upsertEnvLine(contents, key, value)\n }\n\n writeFileSync(envPath, contents, 'utf8')\n}\n\nconst upsertEnvLine = (\n contents: string,\n key: string,\n value: string\n): string => {\n const line = `${key}=${value}`\n const lines = contents.split(/\\r?\\n/)\n const idx = lines.findIndex((l) => new RegExp(`^\\\\s*${key}\\\\s*=`).test(l))\n\n if (idx >= 0) {\n lines[idx] = line\n return lines.join('\\n')\n }\n\n return contents.length === 0 || contents.endsWith('\\n')\n ? `${contents}${line}\\n`\n : `${contents}\\n${line}\\n`\n}\n","import type { ResolvedConfig } from '../config'\nimport { ApiClient } from '../../api'\nimport { invariant } from '@epic-web/invariant'\nimport { getAddress } from 'ethers'\nimport {\n ModuleKind,\n Project,\n ScriptTarget,\n VariableDeclarationKind,\n} from 'ts-morph'\nimport { mkdirSync, writeFileSync } from 'fs'\nimport { join } from 'path'\nimport { resolveZodiacDir } from '../../paths'\n\nconst toLiteral = (value: unknown, indent = 0): string => {\n const pad = ' '.repeat(indent)\n const childPad = ' '.repeat(indent + 1)\n\n if (value === null) return 'null'\n if (typeof value === 'bigint') return `${value}n`\n if (typeof value === 'string') return JSON.stringify(value)\n if (typeof value === 'number' || typeof value === 'boolean')\n return String(value)\n if (Array.isArray(value)) {\n if (value.length === 0) return '[]'\n return `[\\n${value.map((v) => `${childPad}${toLiteral(v, indent + 1)}`).join(',\\n')},\\n${pad}]`\n }\n if (typeof value === 'object') {\n const entries = Object.entries(value as Record<string, unknown>)\n if (entries.length === 0) return '{}'\n const props = entries.map(\n ([k, v]) =>\n `${childPad}${/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? k : JSON.stringify(k)}: ${toLiteral(v, indent + 1)}`\n )\n return `{\\n${props.join(',\\n')},\\n${pad}}`\n }\n return String(value)\n}\n\nexport const pullOrg = async (config: ResolvedConfig) => {\n const client = new ApiClient({\n apiKey: config.apiKey,\n })\n\n const [users, workspaceAccounts] = await Promise.all([\n client.listUsers(),\n client.listAccounts(),\n ])\n\n // Fetch fresh on-chain state via `resolveConstellation` for every\n // account we can resolve:\n // - `spec` present → pass the stored apply-time node verbatim\n // (deployed nodes match on-chain; undeployed ones derive via\n // CREATE2 from the stored nonce + config).\n // - `vault: true` with no spec → treat as a pre-existing on-chain\n // SAFE (e.g. a workspace vault created outside the\n // constellation-as-code flow). The resolver finds it on-chain.\n // - `vault: false` with no spec → a constituent of a still-pending\n // constellation that's never been deployed. We can't usefully\n // resolve it, so skip; the codegen emits minimal fields.\n const allAccounts = workspaceAccounts.flatMap((ws) => ws.accounts)\n const resolvableAccounts = allAccounts.filter(\n (a) => a.spec != null || a.vault\n )\n const resolved = new Map<\n string,\n Awaited<ReturnType<typeof client.resolveConstellation>>['result'][number]\n >()\n if (resolvableAccounts.length > 0) {\n const response = await client.resolveConstellation(\n workspaceAccounts[0].workspaceId, // any workspace works for the resolve route\n {\n specification: resolvableAccounts.map((account, i) =>\n account.spec != null\n ? account.spec\n : {\n // Synthesize a ref for vault-fallback entries (no stored\n // spec). The /resolve payload requires a ref on every\n // entry; the value isn't used downstream beyond echoing\n // back into the response, so a positional id is fine.\n ref: `vault_${i}` as Lowercase<string>,\n type: 'SAFE',\n chain: account.chain,\n address: account.address,\n }\n ),\n }\n )\n invariant(\n response?.result?.length === resolvableAccounts.length,\n `resolveConstellation returned ${response?.result?.length ?? 0} accounts for ${resolvableAccounts.length} accounts`\n )\n resolvableAccounts.forEach((account, i) => {\n resolved.set(account.id, response.result[i])\n })\n }\n\n // Group accounts by type into separate bracket-access namespaces:\n // `safes`, `rolesMods`, `delays`. This way `eth.safe[...]`\n // IntelliSense only suggests SAFE labels, and the label-collision\n // suffix only kicks in when two accounts **of the same type** share\n // a label.\n const accountsRecord: Record<string, unknown> = {}\n for (const ws of workspaceAccounts) {\n const safes: Record<string, unknown> = {}\n const rolesMods: Record<string, unknown> = {}\n const delays: Record<string, unknown> = {}\n\n const bucketsByType = {\n SAFE: safes,\n ROLES: rolesMods,\n DELAY: delays,\n } as const\n\n type NodeType = 'SAFE' | 'ROLES' | 'DELAY'\n const isNodeType = (type: string): type is NodeType =>\n type === 'SAFE' || type === 'ROLES' || type === 'DELAY'\n\n // Count labels per type so we only suffix within-type collisions.\n const labelCountByType: Record<NodeType, Map<string, number>> = {\n SAFE: new Map(),\n ROLES: new Map(),\n DELAY: new Map(),\n }\n for (const account of ws.accounts) {\n if (!isNodeType(account.type)) continue\n const counts = labelCountByType[account.type]\n counts.set(account.label, (counts.get(account.label) ?? 0) + 1)\n }\n\n for (const account of ws.accounts) {\n if (!isNodeType(account.type)) continue\n const onChain = resolved.get(account.id)\n const counts = labelCountByType[account.type]\n const key =\n (counts.get(account.label) ?? 0) > 1\n ? `${account.label} (${getAddress(account.address)})`\n : account.label\n bucketsByType[account.type][key] = {\n id: account.id,\n label: account.label,\n address: account.address,\n chain: account.chain,\n vault: account.vault,\n ...(onChain?.type === 'SAFE' && {\n threshold: onChain.threshold,\n owners: [...onChain.owners],\n modules: [...onChain.modules],\n }),\n }\n }\n\n accountsRecord[ws.workspaceName] = {\n workspaceId: ws.workspaceId,\n workspaceName: ws.workspaceName,\n safes,\n rolesMods,\n delays,\n }\n }\n\n const nameCount = new Map<string, number>()\n for (const user of users) {\n nameCount.set(user.fullName, (nameCount.get(user.fullName) ?? 0) + 1)\n }\n\n const usersRecord: Record<string, unknown> = {}\n for (const user of users) {\n const handle =\n nameCount.get(user.fullName)! > 1\n ? `${user.fullName} (${user.id})`\n : user.fullName\n usersRecord[handle] = {\n id: user.id,\n fullName: user.fullName,\n personalSafes: user.personalSafes,\n }\n }\n\n const outDir = resolveZodiacDir(config.rootDir)\n\n mkdirSync(outDir, { recursive: true })\n\n // Pin CJS so `require()` works regardless of the parent package.json's type\n writeFileSync(\n join(outDir, 'package.json'),\n JSON.stringify(\n {\n type: 'commonjs',\n main: 'index.js',\n types: 'index.d.ts',\n },\n null,\n 2\n )\n )\n\n // Use ts-morph to generate TS, then emit JS + d.ts\n const project = new Project({\n compilerOptions: {\n declaration: true,\n module: ModuleKind.CommonJS,\n target: ScriptTarget.ESNext,\n outDir,\n },\n useInMemoryFileSystem: true,\n })\n\n const sourceFile = project.createSourceFile('index.ts', '')\n\n sourceFile.addVariableStatement({\n isExported: true,\n declarationKind: VariableDeclarationKind.Const,\n declarations: [\n {\n name: 'users',\n initializer: `${toLiteral(usersRecord)} as const`,\n },\n ],\n })\n\n sourceFile.addVariableStatement({\n isExported: true,\n declarationKind: VariableDeclarationKind.Const,\n declarations: [\n {\n name: 'accounts',\n initializer: `${toLiteral(accountsRecord)} as const`,\n },\n ],\n })\n\n const emitResult = sourceFile.getEmitOutput()\n for (const outputFile of emitResult.getOutputFiles()) {\n const filePath = outputFile.getFilePath()\n const fileName = filePath.includes('.d.ts') ? 'index.d.ts' : 'index.js'\n let contents = outputFile.getText()\n // Augment the SDK's global `ZodiacGeneratedCodegen` interface so\n // `constellation()`'s default type parameter picks up these literal\n // shapes automatically.\n if (fileName === 'index.d.ts') {\n contents += `\ndeclare global {\n interface ZodiacGeneratedCodegen {\n users: typeof users;\n accounts: typeof accounts;\n }\n}\n`\n }\n writeFileSync(join(outDir, fileName), contents)\n }\n}\n","import { chainIdFor, type ChainPrefix } from './networks'\n\nexport type AbiFragment = Record<string, any>\nexport type Abi = AbiFragment[]\n\n// Returns null on any failure so callers can fall back to a manual ABI file.\nexport async function fetchAbi(\n chainId: number,\n address: `0x${string}`\n): Promise<Abi | null> {\n const url = `https://api.abi.pub/v1/chains/${chainId}/etherscan?module=contract&action=getabi&address=${address}`\n let body: { status?: string; message?: string; result?: string }\n try {\n const resp = await fetch(url)\n if (!resp.ok) return null\n body = (await resp.json()) as typeof body\n } catch {\n return null\n }\n if (body.status !== '1' || typeof body.result !== 'string') return null\n try {\n const parsed = JSON.parse(body.result)\n if (!Array.isArray(parsed) || parsed.length === 0) return null\n return parsed as Abi\n } catch {\n return null\n }\n}\n\nexport const fetchAbiForPrefix = (\n prefix: ChainPrefix,\n address: `0x${string}`\n) => fetchAbi(chainIdFor(prefix), address)\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport { walkContracts, readAbi, type ContractNode } from './abi'\nimport type { Abi, AbiFragment } from './fetch'\n\n// Emits named tuple elements per ABI input — this is the reason we don't rely\n// on viem/abitype, which produces unnamed tuples (microsoft/TypeScript#44939).\nexport function generateAllowTypes(\n abisDir: string,\n contractsConfig: Record<string, any>\n): string {\n type Tree = Map<string, Tree | { node: ContractNode; abi: Abi }>\n\n const root: Tree = new Map()\n for (const node of walkContracts(contractsConfig)) {\n const abi = readAbi(abisDir, node)\n if (!abi) continue\n insertIntoTree(root, [node.chain, ...node.segments], { node, abi })\n }\n\n const out: string[] = []\n out.push('// AUTO-GENERATED by `zodiac pull-contracts`. Do not edit.')\n out.push('/* eslint-disable */')\n out.push('')\n out.push(\n `import type { FunctionPermission, TargetPermission } from \"zodiac-roles-sdk\";`\n )\n out.push(\n `import type { Scoping, Options, EVERYTHING } from \"@zodiac-os/sdk/allow\";`\n )\n out.push('')\n out.push('declare global {')\n out.push(' interface AllowKit ' + renderTree(root, ' '))\n out.push('}')\n out.push('')\n out.push('export {};')\n out.push('')\n return out.join('\\n')\n}\n\nfunction insertIntoTree(\n tree: Map<string, any>,\n segments: string[],\n leaf: { node: ContractNode; abi: Abi }\n): void {\n const [first, ...rest] = segments\n if (!first) throw new Error('empty segments')\n if (rest.length === 0) {\n tree.set(first, leaf)\n return\n }\n let child = tree.get(first)\n if (!child || child.node) {\n child = new Map()\n tree.set(first, child)\n }\n insertIntoTree(child, rest, leaf)\n}\n\nfunction renderTree(tree: Map<string, any>, indent: string): string {\n const lines: string[] = ['{']\n for (const [key, value] of tree) {\n const safeKey = renderPropKey(key)\n if (value instanceof Map) {\n lines.push(`${indent} ${safeKey}: ${renderTree(value, indent + ' ')};`)\n } else {\n const { node, abi } = value as { node: ContractNode; abi: Abi }\n lines.push(\n `${indent} ${safeKey}: ${renderContractType(node, abi, indent + ' ')};`\n )\n }\n }\n lines.push(`${indent}}`)\n return lines.join('\\n')\n}\n\nfunction renderPropKey(name: string): string {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name)\n}\n\nfunction renderContractType(\n node: ContractNode,\n abi: Abi,\n indent: string\n): string {\n const members: string[] = []\n const seen = new Set<string>()\n members.push(\n `${indent} [EVERYTHING]: (options?: Options) => TargetPermission;`\n )\n for (const fragment of abi) {\n if (fragment.type !== 'function') continue\n if (\n fragment.stateMutability === 'view' ||\n fragment.stateMutability === 'pure'\n ) {\n continue\n }\n const name = fragment.name as string\n if (!name || seen.has(name)) continue\n seen.add(name)\n members.push(renderFunctionSignature(fragment, indent + ' '))\n }\n return ['{', ...members, `${indent}}`].join('\\n')\n}\n\nfunction renderFunctionSignature(\n fragment: AbiFragment,\n indent: string\n): string {\n const name = renderPropKey(fragment.name as string)\n const params: string[] = []\n for (const input of (fragment.inputs as AbiFragment[]) ?? []) {\n const paramName = sanitizeParamName(\n input.name || `arg${params.length}`,\n params.length\n )\n params.push(`${paramName}?: Scoping<${tsTypeFor(input)}>`)\n }\n params.push(`options?: Options`)\n return `${indent}${name}: (${params.join(', ')}) => FunctionPermission;`\n}\n\nfunction sanitizeParamName(name: string, index: number): string {\n const cleaned = name.replace(/[^A-Za-z0-9_$]/g, '_')\n if (!cleaned || /^[0-9]/.test(cleaned)) return `arg${index}`\n // Named tuple element labels can't collide with TS reserved words.\n const reserved = new Set([\n 'function',\n 'class',\n 'new',\n 'number',\n 'string',\n 'object',\n 'boolean',\n 'symbol',\n 'default',\n 'return',\n 'this',\n 'void',\n 'delete',\n 'in',\n 'of',\n 'for',\n 'while',\n 'switch',\n 'case',\n 'if',\n 'else',\n 'null',\n 'true',\n 'false',\n 'undefined',\n 'any',\n 'never',\n 'unknown',\n ])\n return reserved.has(cleaned) ? `_${cleaned}` : cleaned\n}\n\nfunction tsTypeFor(fragment: AbiFragment): string {\n const type = fragment.type as string\n\n const arrayMatch = /^(.*)\\[(\\d*)\\]$/.exec(type)\n if (arrayMatch) {\n const inner: AbiFragment = { ...fragment, type: arrayMatch[1] }\n return `readonly (${tsTypeFor(inner)})[]`\n }\n\n if (type === 'tuple') {\n const components = (fragment.components as AbiFragment[]) ?? []\n if (components.length === 0) return 'Record<string, unknown>'\n const fields = components.map((c, i) => {\n const key = sanitizeParamName(c.name || `f${i}`, i)\n return `${renderPropKey(key)}: ${tsTypeFor(c)}`\n })\n return `{ ${fields.join('; ')} }`\n }\n\n if (type === 'address') return '`0x${string}`'\n if (type === 'bool') return 'boolean'\n if (type === 'string') return 'string'\n if (type === 'bytes') return 'import(\"ethers\").BytesLike'\n if (/^bytes\\d+$/.test(type)) return '`0x${string}`'\n if (/^u?int\\d*$/.test(type)) return 'import(\"ethers\").BigNumberish'\n return 'unknown'\n}\n\nexport function writeGenerated(outFile: string, source: string): void {\n fs.mkdirSync(path.dirname(outFile), { recursive: true })\n fs.writeFileSync(outFile, source, 'utf8')\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport type { ResolvedConfig } from '../config'\nimport { resolveAbisDir } from '../config'\nimport { resolveZodiacDir } from '../../paths'\nimport { abiFilePath, walkContracts, writeAbi } from '../../allow/abi'\nimport { fetchAbi } from '../../allow/fetch'\nimport { chainIdFor } from '../../allow/networks'\nimport { generateAllowTypes, writeGenerated } from '../../allow/codegen'\n\ntype Status = 'ok' | 'fetched' | 'missing'\n\nexport const pullContracts = async (config: ResolvedConfig) => {\n if (!config.contracts || Object.keys(config.contracts).length === 0) {\n console.log('No contracts defined in config, skipping.')\n return\n }\n\n const abisDir = resolveAbisDir(config)\n const generatedFile = path.join(\n resolveZodiacDir(config.rootDir),\n 'allow.d.ts'\n )\n\n let missing = 0\n let fetched = 0\n let existing = 0\n\n for (const node of walkContracts(config.contracts)) {\n const file = abiFilePath(abisDir, node)\n\n if (fs.existsSync(file)) {\n existing++\n report(node.chain, node.segments, node.address, 'ok', file)\n continue\n }\n\n let chainId: number\n try {\n chainId = chainIdFor(node.chain)\n } catch (error) {\n missing++\n report(\n node.chain,\n node.segments,\n node.address,\n 'missing',\n file,\n (error as Error).message\n )\n continue\n }\n\n const abi = await fetchAbi(chainId, node.address)\n if (!abi) {\n missing++\n report(\n node.chain,\n node.segments,\n node.address,\n 'missing',\n file,\n `api.abi.pub returned no ABI for chain ${chainId}`\n )\n continue\n }\n writeAbi(abisDir, node, abi)\n fetched++\n report(node.chain, node.segments, node.address, 'fetched', file)\n }\n\n console.log('')\n console.log(\n `Contracts summary: ${existing} existing, ${fetched} fetched, ${missing} missing.`\n )\n if (missing > 0) {\n console.log('')\n console.log('Missing ABIs must be provided manually. Paste the contract')\n console.log(\n 'ABI JSON at the paths listed above and re-run `zodiac pull-contracts`.'\n )\n }\n\n const source = generateAllowTypes(abisDir, config.contracts)\n writeGenerated(generatedFile, source)\n console.log('')\n console.log(`Wrote typings to ${path.relative(process.cwd(), generatedFile)}`)\n\n if (missing > 0) process.exit(1)\n}\n\nfunction report(\n chain: string,\n segments: string[],\n address: string,\n status: Status,\n file: string,\n reason?: string\n) {\n const label = `${chain}.${segments.join('.')}`.padEnd(40, ' ')\n const tag = {\n ok: ' cached ',\n fetched: ' fetched ',\n missing: ' MISSING ',\n }[status]\n const suffix = reason ? ` — ${reason}` : ''\n console.log(`${tag} ${label} ${address}${suffix}`)\n if (status === 'missing') {\n console.log(` → paste ABI at ${file}`)\n }\n}\n","import { Command } from 'commander'\nimport { config as loadDotenv } from 'dotenv'\nimport { init } from './commands/init'\nimport { loadConfig } from './config'\nimport { pullOrg } from './commands/pullOrg'\nimport { pullContracts } from './commands/pullContracts'\n\n// Load `.env` from the current working directory before reading any env vars.\nloadDotenv({ quiet: true })\n\nconst loadConfigOrInit = (configPath: string) =>\n loadConfig(configPath, {\n onMissingKey: async (rootDir) => {\n console.log(\n 'No ZODIAC_API_KEY found. Starting authorization to mint one for this directory…'\n )\n return init({ rootDir })\n },\n })\n\nexport const run = async (argv: string[] = process.argv) => {\n const program = new Command()\n\n program\n .name('zodiac')\n .description('Zodiac SDK CLI – pull org data and contract ABIs')\n .version('1.0.0')\n .option(\n '-c, --config <path>',\n 'path to the config file',\n 'zodiac.config.ts'\n )\n\n program\n .command('init')\n .description(\n 'Authorize this directory with a Zodiac org. Opens a browser to mint an API key and writes it to .env.'\n )\n .option(\n '--app-url <url>',\n 'Override the Zodiac app URL (defaults to ZODIAC_APP_URL or app.zodiac.eco)'\n )\n .action(async (opts) => {\n await init({ appUrl: opts.appUrl })\n })\n\n program\n .command('pull-org')\n .description('Fetch Zodiac users and accounts, generate TypeScript types')\n .action(async (_opts, cmd) => {\n const config = await loadConfigOrInit(cmd.optsWithGlobals().config)\n await pullOrg(config)\n })\n\n program\n .command('pull-contracts')\n .description('Fetch contract ABIs, generate typed permissions kit')\n .action(async (_opts, cmd) => {\n const config = await loadConfigOrInit(cmd.optsWithGlobals().config)\n await pullContracts(config)\n })\n\n program\n .command('pull')\n .description('Fetch Zodiac org and contracts ABI, generate SDK functions')\n .action(async (_opts, cmd) => {\n const config = await loadConfigOrInit(cmd.optsWithGlobals().config)\n await Promise.all([pullOrg(config), pullContracts(config)])\n })\n\n await program.parseAsync(argv)\n}\n","#!/usr/bin/env node\nimport { run } from './run'\n\nrun().then(\n () => {\n process.exit(0)\n },\n (error: unknown) => {\n if (error) console.error(error)\n process.exit(1)\n }\n)\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -56,7 +56,7 @@ type WorkspaceAccounts = {
56
56
  rolesMods: Readonly<Record<string, Account>>;
57
57
  delays: Readonly<Record<string, Account>>;
58
58
  };
59
- /** Shape of the codegen data produced by `zodiac-os pull-org`. */
59
+ /** Shape of the codegen data produced by `zodiac pull-org`. */
60
60
  type CodegenData = {
61
61
  users: Readonly<Record<string, User>>;
62
62
  accounts: Readonly<Record<string, WorkspaceAccounts>>;
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as resolveZodiacDir, t as ApiClient } from "./api-CygEDU4N.mjs";
2
- import { n as EVERYTHING, t as buildAllowKit } from "./allow-Dzh6t_l8.mjs";
2
+ import { n as EVERYTHING, t as buildAllowKit } from "./allow-DSj3TXgN.mjs";
3
3
  import { createRequire } from "module";
4
4
  import { invariant } from "@epic-web/invariant";
5
5
  import { c, forAll, processPermissions } from "zodiac-roles-sdk";
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/constellation.ts","../src/push.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 { resolveZodiacDir } from './paths'\nimport { UUID } from 'crypto'\n\n/**\n * A role definition keyed by role name. Permissions are expanded into\n * `{ targets, annotations }` via `processPermissions` at `push()` 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 Account = {\n id: UUID\n label: string\n address: Lowercase<Address>\n chain: ChainId\n /** True for accounts promoted to a workspace vault. */\n vault: boolean\n}\n\n/**\n * Accounts grouped by node type within a workspace. Per-type maps keep\n * bracket-accessor namespaces separate: a SAFE and a ROLES mod sharing a\n * label don't collide, and `eth.safe[...]` IntelliSense doesn't suggest\n * ROLES mod labels (and vice versa).\n */\ntype WorkspaceAccounts = {\n workspaceId: UUID\n workspaceName: string\n safes: Readonly<Record<string, Account>>\n rolesMods: Readonly<Record<string, Account>>\n delays: Readonly<Record<string, Account>>\n}\n\n/** Shape of the codegen data produced by `zodiac-os pull-org`. */\nexport type CodegenData = {\n users: Readonly<Record<string, User>>\n accounts: Readonly<Record<string, WorkspaceAccounts>>\n}\n\n// If `pull-org` has been run, the consumer's `.zodiac/index.d.ts` augments\n// `ZodiacGeneratedCodegen` with literal `users`/`accounts` shapes. Otherwise\n// the interface is empty and we fall back to the wide `CodegenData`.\ntype GeneratedCodegen = ZodiacGeneratedCodegen extends CodegenData\n ? ZodiacGeneratedCodegen\n : CodegenData\n\ntype ConstellationOpts<C extends CodegenData> = {\n /** Workspace to scope accounts and roles to. */\n workspace: keyof C['accounts'] & 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 SafeEntries<\n C extends CodegenData,\n W extends keyof C['accounts'],\n> = C['accounts'][W]['safes']\n\ntype RolesEntries<\n C extends CodegenData,\n W extends keyof C['accounts'],\n> = C['accounts'][W]['rolesMods']\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. Either an array or\n * a Record keyed by name — both forms carry the same allowance specs. */\n allowances?: readonly AllowanceSpec[] | Record<string, AllowanceSpec>\n }>\n\n/** Any complete node that can be passed to `push()`. */\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. */\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. Either an array or\n * a Record keyed by name — both forms carry the same allowance specs. */\n allowances?: readonly AllowanceSpec[] | Record<string, AllowanceSpec>\n}\n\ntype ExistingNodeAccessor<\n Type extends string,\n K extends string,\n E,\n Ch extends ChainId,\n NP extends Record<string, any>,\n> = Readonly<Prettify<E & { type: Type; label: K; chain: Ch }>> &\n (<\n O extends {\n [P in Exclude<keyof E & string, 'id' | 'label'>]?: any\n } & Partial<NP> = {},\n >(\n overrides?: {\n [P in Exclude<keyof E & string, 'id' | 'label'>]?: any\n } & Partial<NP> &\n O\n ) => Readonly<\n Prettify<\n Omit<E, keyof O> & O & Partial<NP> & { type: Type; label: K; chain: Ch }\n >\n >)\n\ntype NewNodeAccessor<\n Type extends string,\n Ch extends ChainId,\n NP extends Record<string, any>,\n> = Readonly<Prettify<{ type: Type; label: string; chain: Ch }>> &\n ((\n props: NP\n ) => Readonly<Prettify<NP & { type: Type; label: string; chain: Ch }>>)\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 keyof Entries & string]: ExistingNodeAccessor<\n Type,\n K,\n Entries[K],\n Ch,\n NP\n >\n} & {\n readonly [key: string]: NewNodeAccessor<Type, Ch, NP>\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['accounts'] = keyof C['accounts'],\n Ch extends ChainId = ChainId,\n> = {\n /** Access existing safes by label or create new ones with a new label.\n * Only SAFE-typed accounts are suggested in IntelliSense. */\n safe: EntityAccessor<'SAFE', SafeEntries<C, W>, Ch, NewSafeProps>\n /** Access existing roles modifiers by label or create new ones with a\n * new label. Only ROLES-typed accounts are suggested in IntelliSense. */\n roles: EntityAccessor<'ROLES', RolesEntries<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(resolveZodiacDir()) as CodegenData\n}\n\n/**\n * Creates a constellation scoped to a workspace and chain.\n *\n * Use bracket access to reference existing accounts (vaults and other\n * applied constellation nodes) or define new ones:\n * ```ts\n * const eth = constellation({ workspace: 'GG', label: 'my constellation', chain: 1 })\n *\n * const dao = eth.safe['GG DAO'] // existing account 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['accounts'] & string = keyof C['accounts'] & 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.accounts[opts.workspace]\n const safesByLabel: Record<string, Account> = {}\n const rolesByLabel: Record<string, Account> = {}\n if (ws) {\n for (const [label, account] of Object.entries(ws.safes)) {\n safesByLabel[label] = account\n }\n for (const [label, account] of Object.entries(ws.rolesMods)) {\n rolesByLabel[label] = account\n }\n }\n\n const meta: ConstellationMeta = {\n label: opts.label,\n chain: opts.chain,\n workspaceId: (ws?.workspaceId ?? '') as UUID,\n }\n\n function makeNodeRef(\n data: Record<string, any>\n ): Readonly<Record<string, any>> {\n return Object.freeze({\n ...data,\n chain: opts.chain,\n _constellation: meta,\n })\n }\n\n function entityAccessor(\n registry: Record<string, Record<string, any>>,\n type: string\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 // Bracket-access keys in the generated codegen carry a\n // ` (0xChecksummed…)` suffix when multiple workspace accounts share\n // a label. The label sent in the push spec should be the clean\n // original, so prefer `existing.label` when it's available.\n const specLabel: string = existing?.label ?? name\n const fn = (overrides?: Record<string, any>) =>\n makeNodeRef({\n type,\n ...(existing || {}),\n ...overrides,\n label: specLabel,\n })\n Object.assign(fn, {\n type,\n ...(existing || {}),\n label: specLabel,\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(safesByLabel, 'SAFE')\n const roles = entityAccessor(rolesByLabel, 'ROLES')\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'\nimport type { AllowanceSpec } from './types'\n\ntype PushOpts = {\n /** API client instance. Defaults to a client configured from environment variables. */\n api?: ApiClient\n}\n\n/**\n * Resolves node references and pushes the constellation specification to 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 push([dao, roles])\n * ```\n */\nexport async function push(\n nodes: ConstellationNode[] | { [key: string]: ConstellationNode },\n opts?: PushOpts\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 if (\n node.type === 'ROLES' &&\n key === 'allowances' &&\n value != null &&\n !Array.isArray(value)\n ) {\n spec.allowances = resolveRefs(\n Object.values(value as Record<string, AllowanceSpec>),\n refs\n )\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 push() 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":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/constellation.ts","../src/push.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 { resolveZodiacDir } from './paths'\nimport { UUID } from 'crypto'\n\n/**\n * A role definition keyed by role name. Permissions are expanded into\n * `{ targets, annotations }` via `processPermissions` at `push()` 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 Account = {\n id: UUID\n label: string\n address: Lowercase<Address>\n chain: ChainId\n /** True for accounts promoted to a workspace vault. */\n vault: boolean\n}\n\n/**\n * Accounts grouped by node type within a workspace. Per-type maps keep\n * bracket-accessor namespaces separate: a SAFE and a ROLES mod sharing a\n * label don't collide, and `eth.safe[...]` IntelliSense doesn't suggest\n * ROLES mod labels (and vice versa).\n */\ntype WorkspaceAccounts = {\n workspaceId: UUID\n workspaceName: string\n safes: Readonly<Record<string, Account>>\n rolesMods: Readonly<Record<string, Account>>\n delays: Readonly<Record<string, Account>>\n}\n\n/** Shape of the codegen data produced by `zodiac pull-org`. */\nexport type CodegenData = {\n users: Readonly<Record<string, User>>\n accounts: Readonly<Record<string, WorkspaceAccounts>>\n}\n\n// If `pull-org` has been run, the consumer's `.zodiac/index.d.ts` augments\n// `ZodiacGeneratedCodegen` with literal `users`/`accounts` shapes. Otherwise\n// the interface is empty and we fall back to the wide `CodegenData`.\ntype GeneratedCodegen = ZodiacGeneratedCodegen extends CodegenData\n ? ZodiacGeneratedCodegen\n : CodegenData\n\ntype ConstellationOpts<C extends CodegenData> = {\n /** Workspace to scope accounts and roles to. */\n workspace: keyof C['accounts'] & 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 SafeEntries<\n C extends CodegenData,\n W extends keyof C['accounts'],\n> = C['accounts'][W]['safes']\n\ntype RolesEntries<\n C extends CodegenData,\n W extends keyof C['accounts'],\n> = C['accounts'][W]['rolesMods']\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. Either an array or\n * a Record keyed by name — both forms carry the same allowance specs. */\n allowances?: readonly AllowanceSpec[] | Record<string, AllowanceSpec>\n }>\n\n/** Any complete node that can be passed to `push()`. */\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. */\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. Either an array or\n * a Record keyed by name — both forms carry the same allowance specs. */\n allowances?: readonly AllowanceSpec[] | Record<string, AllowanceSpec>\n}\n\ntype ExistingNodeAccessor<\n Type extends string,\n K extends string,\n E,\n Ch extends ChainId,\n NP extends Record<string, any>,\n> = Readonly<Prettify<E & { type: Type; label: K; chain: Ch }>> &\n (<\n O extends {\n [P in Exclude<keyof E & string, 'id' | 'label'>]?: any\n } & Partial<NP> = {},\n >(\n overrides?: {\n [P in Exclude<keyof E & string, 'id' | 'label'>]?: any\n } & Partial<NP> &\n O\n ) => Readonly<\n Prettify<\n Omit<E, keyof O> & O & Partial<NP> & { type: Type; label: K; chain: Ch }\n >\n >)\n\ntype NewNodeAccessor<\n Type extends string,\n Ch extends ChainId,\n NP extends Record<string, any>,\n> = Readonly<Prettify<{ type: Type; label: string; chain: Ch }>> &\n ((\n props: NP\n ) => Readonly<Prettify<NP & { type: Type; label: string; chain: Ch }>>)\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 keyof Entries & string]: ExistingNodeAccessor<\n Type,\n K,\n Entries[K],\n Ch,\n NP\n >\n} & {\n readonly [key: string]: NewNodeAccessor<Type, Ch, NP>\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['accounts'] = keyof C['accounts'],\n Ch extends ChainId = ChainId,\n> = {\n /** Access existing safes by label or create new ones with a new label.\n * Only SAFE-typed accounts are suggested in IntelliSense. */\n safe: EntityAccessor<'SAFE', SafeEntries<C, W>, Ch, NewSafeProps>\n /** Access existing roles modifiers by label or create new ones with a\n * new label. Only ROLES-typed accounts are suggested in IntelliSense. */\n roles: EntityAccessor<'ROLES', RolesEntries<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(resolveZodiacDir()) as CodegenData\n}\n\n/**\n * Creates a constellation scoped to a workspace and chain.\n *\n * Use bracket access to reference existing accounts (vaults and other\n * applied constellation nodes) or define new ones:\n * ```ts\n * const eth = constellation({ workspace: 'GG', label: 'my constellation', chain: 1 })\n *\n * const dao = eth.safe['GG DAO'] // existing account 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['accounts'] & string = keyof C['accounts'] & 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.accounts[opts.workspace]\n const safesByLabel: Record<string, Account> = {}\n const rolesByLabel: Record<string, Account> = {}\n if (ws) {\n for (const [label, account] of Object.entries(ws.safes)) {\n safesByLabel[label] = account\n }\n for (const [label, account] of Object.entries(ws.rolesMods)) {\n rolesByLabel[label] = account\n }\n }\n\n const meta: ConstellationMeta = {\n label: opts.label,\n chain: opts.chain,\n workspaceId: (ws?.workspaceId ?? '') as UUID,\n }\n\n function makeNodeRef(\n data: Record<string, any>\n ): Readonly<Record<string, any>> {\n return Object.freeze({\n ...data,\n chain: opts.chain,\n _constellation: meta,\n })\n }\n\n function entityAccessor(\n registry: Record<string, Record<string, any>>,\n type: string\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 // Bracket-access keys in the generated codegen carry a\n // ` (0xChecksummed…)` suffix when multiple workspace accounts share\n // a label. The label sent in the push spec should be the clean\n // original, so prefer `existing.label` when it's available.\n const specLabel: string = existing?.label ?? name\n const fn = (overrides?: Record<string, any>) =>\n makeNodeRef({\n type,\n ...(existing || {}),\n ...overrides,\n label: specLabel,\n })\n Object.assign(fn, {\n type,\n ...(existing || {}),\n label: specLabel,\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(safesByLabel, 'SAFE')\n const roles = entityAccessor(rolesByLabel, 'ROLES')\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'\nimport type { AllowanceSpec } from './types'\n\ntype PushOpts = {\n /** API client instance. Defaults to a client configured from environment variables. */\n api?: ApiClient\n}\n\n/**\n * Resolves node references and pushes the constellation specification to 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 push([dao, roles])\n * ```\n */\nexport async function push(\n nodes: ConstellationNode[] | { [key: string]: ConstellationNode },\n opts?: PushOpts\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 if (\n node.type === 'ROLES' &&\n key === 'allowances' &&\n value != null &&\n !Array.isArray(value)\n ) {\n spec.allowances = resolveRefs(\n Object.values(value as Record<string, AllowanceSpec>),\n refs\n )\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 push() 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":"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"}
@@ -1,6 +1,6 @@
1
1
  // Ambient fallbacks. Script file (no imports / exports), so top-level
2
2
  // interface declarations are implicitly global. Consumers augment these
3
- // via files generated into `<cwd>/.zodiac/` by `zodiac-os pull-org` /
3
+ // via files generated into `<cwd>/.zodiac/` by `zodiac pull-org` /
4
4
  // `pull-contracts`.
5
5
 
6
6
  // Augmented by `<cwd>/.zodiac/allow.d.ts` (from `pull-contracts`) with
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@zodiac-os/sdk",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "author": "Gnosis Guild",
5
5
  "license": "LGPL-3.0-only",
6
6
  "homepage": "https://github.com/gnosisguild/zodiac-os-sdk",
7
- "packageManager": "bun@1.1.12",
7
+ "packageManager": "bun@1.3.12",
8
8
  "repository": {
9
9
  "url": "https://github.com/gnosisguild/zodiac-os-sdk"
10
10
  },
@@ -31,7 +31,7 @@
31
31
  "LICENSE"
32
32
  ],
33
33
  "bin": {
34
- "zodiac-os": "./dist/cli.mjs"
34
+ "zodiac": "./dist/cli.mjs"
35
35
  },
36
36
  "scripts": {
37
37
  "clean": "rimraf dist",
@@ -48,6 +48,8 @@
48
48
  "@epic-web/invariant": "1.0.0",
49
49
  "@zodiac-os/api-types": "1.6.0",
50
50
  "commander": "^14.0.3",
51
+ "dotenv": "^17.4.2",
52
+ "open": "^11.0.0",
51
53
  "ts-morph": "^27.0.2",
52
54
  "zodiac-roles-deployments": "^3.2.0",
53
55
  "zodiac-roles-sdk": "^3.4.7"
@@ -1 +0,0 @@
1
- {"version":3,"file":"allow-Dzh6t_l8.mjs","names":[],"sources":["../src/allow/types.ts","../src/allow/runtime.ts"],"sourcesContent":["import type { BigNumberish, BytesLike, ParamType } from 'ethers'\nimport type {\n Condition,\n FunctionPermission,\n TargetPermission,\n} from 'zodiac-roles-sdk'\n\nexport type Options = {\n send?: boolean\n delegatecall?: boolean\n etherWithinAllowance?: `0x${string}`\n callWithinAllowance?: `0x${string}`\n}\n\nexport type PrimitiveValue = BigNumberish | BytesLike | string | boolean\n\n// Signature matches `zodiac-roles-sdk` so values from `c.*` are assignable.\nexport type ConditionFunction<T = unknown> = (\n abiType: ParamType,\n _?: T\n) => Condition\n\ntype RequireAtLeastOne<T> = {\n [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>\n}[keyof T]\n\ntype ArrayElement<T extends readonly unknown[]> = T extends readonly (infer U)[]\n ? U\n : never\n\nexport type PrimitiveScoping<T extends PrimitiveValue> =\n | T\n | ConditionFunction<T>\n\nexport type ArrayScoping<T extends readonly any[]> =\n | readonly Scoping<ArrayElement<T>>[]\n | ConditionFunction<T>\n\nexport type StructScoping<T extends { [key: string]: any }> =\n | RequireAtLeastOne<{ [K in keyof T]?: Scoping<T[K]> }>\n | ConditionFunction<T>\n\nexport type Scoping<T> = T extends PrimitiveValue\n ? PrimitiveScoping<T>\n : T extends readonly any[]\n ? ArrayScoping<T>\n : T extends { [key: string]: any }\n ? StructScoping<T>\n : unknown\n\nexport type { FunctionPermission, TargetPermission }\n\nexport const EVERYTHING = Symbol.for('@zodiac-os/allow-kit/EVERYTHING')\nexport type EVERYTHING = typeof EVERYTHING\n","import { Interface, FunctionFragment, isError, type InterfaceAbi } from 'ethers'\nimport { c, coercePermission } from 'zodiac-roles-sdk'\nimport type {\n Condition,\n FunctionPermission,\n TargetPermission,\n} from 'zodiac-roles-sdk'\nimport { ParameterType, Operator } from 'zodiac-roles-deployments'\nimport { readAbi, walkContracts, type ContractNode } from './abi'\nimport { EVERYTHING, type Options } from './types'\n\nexport function buildAllowKit(\n abisDir: string,\n contractsConfig: Record<string, any>\n): Record<string, any> {\n const kit: Record<string, any> = {}\n for (const node of walkContracts(contractsConfig)) {\n const abi = readAbi(abisDir, node)\n if (!abi) {\n // Defer the error until the user touches this contract — otherwise an\n // ABI missing for one chain crashes all unrelated role definitions.\n attachAt(kit, [node.chain, ...node.segments], missingAbiProxy(node))\n continue\n }\n attachAt(\n kit,\n [node.chain, ...node.segments],\n makeAllowContract(node.address, abi as InterfaceAbi)\n )\n }\n return kit\n}\n\nfunction attachAt(root: Record<string, any>, segments: string[], value: any) {\n let cursor = root\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i]!\n if (!(seg in cursor)) cursor[seg] = {}\n cursor = cursor[seg]\n }\n cursor[segments[segments.length - 1]!] = value\n}\n\nfunction missingAbiProxy(node: ContractNode) {\n const explain = () => {\n throw new Error(\n `ABI missing for ${node.chain}.${node.segments.join('.')} ` +\n `(${node.address}). Run \\`zodiac-os pull-contracts\\` to fetch it, or ` +\n `paste the ABI JSON manually at <abisDir>/${node.chain}/${node.segments.join('/')}.json`\n )\n }\n return new Proxy(\n {},\n {\n get: explain,\n has: explain,\n }\n )\n}\n\nfunction makeAllowContract(\n address: `0x${string}`,\n abi: InterfaceAbi\n): Record<string | symbol, any> {\n const iface = Interface.from(abi)\n const lowerAddr = address.toLowerCase() as `0x${string}`\n\n const allowEverything = (options?: Options): TargetPermission => ({\n targetAddress: lowerAddr,\n send: options?.send,\n delegatecall: options?.delegatecall,\n })\n\n const has = (name: string) => {\n try {\n const fn = iface.getFunction(name)\n if (!fn) return false\n return fn.stateMutability !== 'view' && fn.stateMutability !== 'pure'\n } catch (error) {\n if (!isError(error as any, 'INVALID_ARGUMENT')) throw error\n return false\n }\n }\n\n return new Proxy(\n {},\n {\n get: (_target, prop) => {\n if (prop === EVERYTHING) return allowEverything\n if (typeof prop !== 'string') return undefined\n if (!has(prop)) return undefined\n const fn = iface.getFunction(prop)!\n return makeAllowFunction(fn, lowerAddr)\n },\n has: (_target, prop) => {\n if (prop === EVERYTHING) return true\n return typeof prop === 'string' && has(prop)\n },\n }\n )\n}\n\nfunction makeAllowFunction(\n fn: FunctionFragment,\n targetAddress: `0x${string}`\n): (...args: any[]) => FunctionPermission {\n const inputs = fn.inputs\n return (...args: any[]) => {\n const scopings = args.slice(0, inputs.length)\n const hasScopings = scopings.some((s) => s !== undefined && s !== null)\n const options: Options = args[inputs.length] ?? {}\n const condition = hasScopings\n ? c.calldataMatches(scopings, inputs)()\n : undefined\n const preset = {\n targetAddress,\n signature: fn.format('sighash'),\n condition,\n }\n return applyOptions(coercePermission(preset as any) as any, options)\n }\n}\n\nconst emptyCalldataMatches: Condition = {\n paramType: ParameterType.Calldata,\n operator: Operator.Matches,\n children: [],\n}\n\nconst applyGlobalAllowance = (\n condition: Condition | undefined,\n allowanceCondition: Condition\n): Condition => {\n const base = condition ?? emptyCalldataMatches\n if (\n base.paramType !== ParameterType.Calldata ||\n base.operator !== Operator.Matches\n ) {\n throw new Error(\n 'Global allowance can only be applied to calldata matches nodes'\n )\n }\n return {\n ...base,\n children: [...(base.children ?? []), allowanceCondition],\n }\n}\n\nconst applyOptions = (\n permission: FunctionPermission & { condition?: Condition },\n options: Options\n): FunctionPermission => {\n let condition = permission.condition\n if (options.etherWithinAllowance) {\n if (!options.send) {\n throw new Error(\n '`etherWithinAllowance` can only be used if `send` is allowed'\n )\n }\n condition = applyGlobalAllowance(condition, {\n paramType: ParameterType.None,\n operator: Operator.EtherWithinAllowance,\n compValue: options.etherWithinAllowance,\n })\n }\n if (options.callWithinAllowance) {\n condition = applyGlobalAllowance(condition, {\n paramType: ParameterType.None,\n operator: Operator.CallWithinAllowance,\n compValue: options.callWithinAllowance,\n })\n }\n return {\n ...permission,\n send: options.send,\n delegatecall: options.delegatecall,\n condition,\n }\n}\n"],"mappings":"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"}