@zodiaceco/sdk 2.1.2 → 2.1.3
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 +20 -1
- package/dist/{actions-vTsHy73w.d.mts → actions-tA_Oe_kC.d.mts} +39 -20
- package/dist/actions.d.mts +1 -1
- package/dist/{api-C49DoaPC.mjs → api-kKgzHQlu.mjs} +17 -3
- package/dist/api-kKgzHQlu.mjs.map +1 -0
- package/dist/cli.mjs +61 -22
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +29 -5
- package/dist/index.mjs +62 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/api-C49DoaPC.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -46,6 +46,23 @@ zodiac pull
|
|
|
46
46
|
|
|
47
47
|
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`.
|
|
48
48
|
|
|
49
|
+
## Lifecycle
|
|
50
|
+
|
|
51
|
+
State lives in three places — your code, the Zodiac OS database, and chain — and each command moves it in one direction:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
your code ──push──▶ Zodiac OS (revisions) ──deploy (in the app)──▶ chain
|
|
55
|
+
your code ◀──pull── Zodiac OS ◀──────────────indexing──────────── chain
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**`pull` reads, never writes.** It fetches your org's users and every account that exists on chain, and generates `.zodiac/`. A referenced node comes filled with its live on-chain state (owners, threshold, roles) so it reads well in the editor — that state is for reading, not something your code has declared.
|
|
59
|
+
|
|
60
|
+
**`push` writes to the database, never to chain.** It stores your nodes as the constellation's next _revision_ and answers with a link to review it in the app. Re-pushing before a deployment replaces the pending revision rather than stacking new ones. Only what your code explicitly declares is stored: a bare reference like `eth.safe['Treasury']` travels as identity only, so an owner added through the app since your last pull is not silently reverted — while `eth.safe['Treasury']({ threshold: 3 })` declares exactly that field. Within a roles mod, unmentioned roles and allowances stay untouched; passing `null` for a key clears it.
|
|
61
|
+
|
|
62
|
+
**Deploying happens in the app.** The review page diffs the revision against on-chain state and derives addresses for new nodes from the personal Safe of whoever triggers the deployment — so until a deployment settles them, two people looking at the same constellation see different addresses for new nodes. Deploying registers the accounts in Zodiac OS and executes the transactions. A constellation moves Draft → Pending → Deployed, and the next push starts a new revision back at Draft.
|
|
63
|
+
|
|
64
|
+
**Then pull again.** Once the new accounts have been seen on chain, the next `pull` includes them, keyed by label per chain — from that point on, reference them (`eth.safe['New Safe']`) instead of re-declaring them with a `nonce`. `bun push` runs `pull-org` first via the `prepush` hook, so this mostly takes care of itself. The case to avoid is pushing from a stale checkout where an already-deployed node is still declared by `nonce`: the original deployer would re-derive the same address, but anyone else deploying that revision would create a duplicate account.
|
|
65
|
+
|
|
49
66
|
## Constellation API
|
|
50
67
|
|
|
51
68
|
The `constellation()` function is the main SDK entry point. It returns an API for declaring account constellations — the set of Safes, Roles mods, and users that make up your on-chain setup.
|
|
@@ -68,7 +85,9 @@ const eth = constellation({
|
|
|
68
85
|
|
|
69
86
|
### Referencing existing accounts
|
|
70
87
|
|
|
71
|
-
Bracket access gives you existing Safes and Roles mods from the selected workspace — both **vault accounts** (manually-promoted entries surfaced in the workspace UI) and any **constellation accounts** previously created by a `push()`. The codegen records them under the same `accounts` map, marked with a `vault` flag for the subset that are also workspace vaults. Names auto-complete from the codegen output.
|
|
88
|
+
Bracket access gives you existing Safes and Roles mods from the selected workspace and chain — both **vault accounts** (manually-promoted entries surfaced in the workspace UI) and any **constellation accounts** previously created by a `push()`. The codegen records them under the same `accounts` map, marked with a `vault` flag for the subset that are also workspace vaults. Names auto-complete from the codegen output.
|
|
89
|
+
|
|
90
|
+
A label only ever names an account on the constellation's own chain. The same name on another chain is a different account whose address means nothing here, so it reads as a new node rather than as a reference — two workspaces can both have a `Treasury` on mainnet and on Gnosis without either having to be addressed by address.
|
|
72
91
|
|
|
73
92
|
```ts
|
|
74
93
|
// Reference an existing Safe — no invocation needed
|
|
@@ -43,19 +43,23 @@ type Account = {
|
|
|
43
43
|
chain: ChainId; /** True for accounts promoted to a workspace vault. */
|
|
44
44
|
vault: boolean;
|
|
45
45
|
};
|
|
46
|
+
type AccountsByLabel = Readonly<Record<string, Account>>;
|
|
46
47
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* A workspace's accounts, grouped by node type and then by chain.
|
|
49
|
+
*
|
|
50
|
+
* Both groupings keep bracket-accessor namespaces apart, so that a label is
|
|
51
|
+
* only ever ambiguous between accounts a constellation could actually choose
|
|
52
|
+
* between: a SAFE and a ROLES mod sharing a label don't collide, and neither
|
|
53
|
+
* do two safes of the same name on different chains.
|
|
51
54
|
*/
|
|
52
55
|
type WorkspaceAccounts = {
|
|
53
56
|
workspaceId: UUID;
|
|
54
57
|
workspaceName: string;
|
|
55
|
-
safes:
|
|
56
|
-
rolesMods:
|
|
57
|
-
delays:
|
|
58
|
+
safes: AccountsByChain;
|
|
59
|
+
rolesMods: AccountsByChain;
|
|
60
|
+
delays: AccountsByChain;
|
|
58
61
|
};
|
|
62
|
+
type AccountsByChain = { readonly [chain in ChainId]?: AccountsByLabel };
|
|
59
63
|
/** Shape of the codegen data produced by `zodiac pull-org`. */
|
|
60
64
|
type CodegenData = {
|
|
61
65
|
users: Readonly<Record<string, User>>;
|
|
@@ -71,8 +75,15 @@ type ConstellationInternalOpts<C extends CodegenData> = {
|
|
|
71
75
|
/** Injected codegen data (used for testing). */codegen?: C;
|
|
72
76
|
};
|
|
73
77
|
type Prettify<T> = { readonly [K in keyof T]: T[K] } & {};
|
|
74
|
-
|
|
75
|
-
type
|
|
78
|
+
/**
|
|
79
|
+
* The accounts of one type that a constellation on `Ch` can name. Accounts on
|
|
80
|
+
* every other chain are not in scope: their addresses mean nothing here, so
|
|
81
|
+
* offering them would only invite a node that carries a foreign address under
|
|
82
|
+
* this constellation's chain.
|
|
83
|
+
*/
|
|
84
|
+
type ChainEntries<A, Ch extends ChainId> = Ch extends keyof A ? NonNullable<A[Ch]> : {};
|
|
85
|
+
type SafeEntries<C extends CodegenData, W extends keyof C['accounts'], Ch extends ChainId> = ChainEntries<C['accounts'][W]['safes'], Ch>;
|
|
86
|
+
type RolesEntries<C extends CodegenData, W extends keyof C['accounts'], Ch extends ChainId> = ChainEntries<C['accounts'][W]['rolesMods'], Ch>;
|
|
76
87
|
type NodeType = 'SAFE' | 'ROLES' | 'DELAY';
|
|
77
88
|
/** A reference to a node used in `owners`, `modules`, `target`, etc. */
|
|
78
89
|
type NodeRef = Readonly<{
|
|
@@ -92,7 +103,8 @@ type NodeBase = Readonly<{
|
|
|
92
103
|
address?: Address; /** Deployment nonce — required for new nodes, optional for existing. */
|
|
93
104
|
nonce?: bigint;
|
|
94
105
|
}>;
|
|
95
|
-
/** A safe node
|
|
106
|
+
/** A safe node — a reference to one the workspace already has, or a new one
|
|
107
|
+
* with the config it is deployed with. */
|
|
96
108
|
type SafeNode = NodeBase & Readonly<{
|
|
97
109
|
/** Discriminator identifying this node as a Safe. */type: 'SAFE'; /** Number of owner signatures required to execute a transaction. */
|
|
98
110
|
threshold: number; /** Safe owner addresses or node references. */
|
|
@@ -100,7 +112,8 @@ type SafeNode = NodeBase & Readonly<{
|
|
|
100
112
|
modules?: readonly (string | NodeRef)[]; /** Whether this safe shall appear as a vault in the workspace. @default false */
|
|
101
113
|
vault?: boolean;
|
|
102
114
|
}>;
|
|
103
|
-
/** A roles modifier node
|
|
115
|
+
/** A roles modifier node — a reference to one the workspace already has, or a
|
|
116
|
+
* new one with the config it is deployed with. */
|
|
104
117
|
type RolesNode = NodeBase & Readonly<{
|
|
105
118
|
/** Discriminator identifying this node as a Roles modifier. */type: 'ROLES'; /** The safe that this roles modifier controls. */
|
|
106
119
|
target?: AddressOrRef; /** The account that is allowed to update the configuration of the Roles mod. */
|
|
@@ -153,7 +166,7 @@ type ExistingNodeAccessor<Type extends string, K extends string, E, Ch extends C
|
|
|
153
166
|
type: Type;
|
|
154
167
|
label: K;
|
|
155
168
|
chain: Ch;
|
|
156
|
-
}>> & (<O extends { [P in Exclude<keyof E & string, 'id' | 'label'>]?: any } & Partial<NP> = {}>(overrides?: { [P in Exclude<keyof E & string, 'id' | 'label'>]?: any } & Partial<NP> & O) => Readonly<Prettify<Omit<E, keyof O> & O & Partial<NP> & {
|
|
169
|
+
}>> & (<const O extends { [P in Exclude<keyof E & string, 'id' | 'label'>]?: any } & Partial<NP> = {}>(overrides?: { [P in Exclude<keyof E & string, 'id' | 'label'>]?: any } & Partial<NP> & O) => Readonly<Prettify<Omit<E, keyof O> & O & Partial<NP> & {
|
|
157
170
|
type: Type;
|
|
158
171
|
label: K;
|
|
159
172
|
chain: Ch;
|
|
@@ -162,7 +175,7 @@ type NewNodeAccessor<Type extends string, Ch extends ChainId, NP extends Record<
|
|
|
162
175
|
type: Type;
|
|
163
176
|
label: string;
|
|
164
177
|
chain: Ch;
|
|
165
|
-
}>> & (<P extends NP>(props: P) => Readonly<Prettify<P & {
|
|
178
|
+
}>> & (<const P extends NP>(props: P) => Readonly<Prettify<P & {
|
|
166
179
|
type: Type;
|
|
167
180
|
label: string;
|
|
168
181
|
chain: Ch;
|
|
@@ -174,25 +187,31 @@ type UserAccessor<C extends CodegenData, Ch extends number> = { readonly [K in k
|
|
|
174
187
|
type ConstellationResult<C extends CodegenData, W extends keyof C['accounts'] = keyof C['accounts'], Ch extends ChainId = ChainId> = {
|
|
175
188
|
/** Access existing safes by label or create new ones with a new label.
|
|
176
189
|
* Only SAFE-typed accounts are suggested in IntelliSense. */
|
|
177
|
-
safe: EntityAccessor<'SAFE', SafeEntries<C, W>, Ch, NewSafeProps>;
|
|
190
|
+
safe: EntityAccessor<'SAFE', SafeEntries<C, W, Ch>, Ch, NewSafeProps>;
|
|
178
191
|
/** Access existing roles modifiers by label or create new ones with a
|
|
179
192
|
* new label. Only ROLES-typed accounts are suggested in IntelliSense. */
|
|
180
|
-
roles: EntityAccessor<'ROLES', RolesEntries<C, W>, Ch, NewRolesProps>; /** Resolve a user's personal safe address on the constellation's chain. */
|
|
193
|
+
roles: EntityAccessor<'ROLES', RolesEntries<C, W, Ch>, Ch, NewRolesProps>; /** Resolve a user's personal safe address on the constellation's chain. */
|
|
181
194
|
user: UserAccessor<C, Ch>;
|
|
182
195
|
};
|
|
183
196
|
/** @internal */
|
|
184
197
|
/**
|
|
185
198
|
* Creates a constellation scoped to a workspace and chain.
|
|
186
199
|
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
200
|
+
* Bracket access names an account of that workspace **on that chain** — a
|
|
201
|
+
* vault, or a node an earlier constellation deployed. A label the chain has no
|
|
202
|
+
* account for reads as a new node instead, so it has to be given the config it
|
|
203
|
+
* is deployed with.
|
|
204
|
+
*
|
|
189
205
|
* ```ts
|
|
190
206
|
* const eth = constellation({ workspace: 'GG', label: 'my constellation', chain: 1 })
|
|
191
207
|
*
|
|
192
|
-
* const dao = eth.safe['GG DAO'] //
|
|
193
|
-
* const roles = eth.roles['GG DAO'] //
|
|
208
|
+
* const dao = eth.safe['GG DAO'] // the safe of that name on chain 1
|
|
209
|
+
* const roles = eth.roles['GG DAO'] // the roles mod of that name
|
|
194
210
|
* const newSafe = eth.safe['New Safe']({ nonce: 0n, threshold: 2, owners: [...], modules: [...] })
|
|
195
211
|
* ```
|
|
212
|
+
*
|
|
213
|
+
* Names come from the last `pull-org`, so an account deployed since then is
|
|
214
|
+
* not one yet.
|
|
196
215
|
*/
|
|
197
216
|
declare function constellation<const C extends CodegenData = GeneratedCodegen, const W extends keyof C['accounts'] & string = keyof C['accounts'] & string, const Ch extends ChainId = ChainId>(opts: ConstellationOpts<C> & {
|
|
198
217
|
workspace: W;
|
|
@@ -354,4 +373,4 @@ type DeFiKitVerb<Verb> = Verb extends ((params: infer Params) => unknown) ? (par
|
|
|
354
373
|
}) => DeFiKitEntry;
|
|
355
374
|
//#endregion
|
|
356
375
|
export { PermissionEntry as a, ConstellationNode as c, transfer as i, constellation as l, defikit as n, PermissionTarget as o, swap as r, Permissions as s, custom as t };
|
|
357
|
-
//# sourceMappingURL=actions-
|
|
376
|
+
//# sourceMappingURL=actions-tA_Oe_kC.d.mts.map
|
package/dist/actions.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as PermissionEntry, i as transfer, n as defikit, o as PermissionTarget, r as swap, s as Permissions, t as custom } from "./actions-
|
|
1
|
+
import { a as PermissionEntry, i as transfer, n as defikit, o as PermissionTarget, r as swap, s as Permissions, t as custom } from "./actions-tA_Oe_kC.mjs";
|
|
2
2
|
export { PermissionEntry, PermissionTarget, Permissions, custom, defikit, swap, transfer };
|
|
@@ -65,20 +65,34 @@ var ApiClient = class {
|
|
|
65
65
|
if (!res.ok) await handleApiError(res);
|
|
66
66
|
return res.json();
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Every account the workspaces of this org hold, grouped by workspace.
|
|
70
|
+
*
|
|
71
|
+
* Only accounts that exist on chain are listed, so every entry carries an
|
|
72
|
+
* address something was deployed to.
|
|
73
|
+
*/
|
|
68
74
|
listAccounts() {
|
|
69
75
|
return this.get("accounts");
|
|
70
76
|
}
|
|
77
|
+
/** Every member of this org, with the personal safes they have activated. */
|
|
71
78
|
listUsers() {
|
|
72
79
|
return this.get("users");
|
|
73
80
|
}
|
|
74
81
|
/**
|
|
75
|
-
*
|
|
82
|
+
* Stores a specification as the constellation's next revision, and answers
|
|
83
|
+
* with a url to review and deploy it. Applying does not deploy anything, and
|
|
84
|
+
* re-applying an undeployed revision replaces it rather than stacking.
|
|
76
85
|
*/
|
|
77
86
|
applyConstellation(workspaceId, payload) {
|
|
78
87
|
return this.postJson(`workspace/${workspaceId}/constellation/apply`, payload);
|
|
79
88
|
}
|
|
80
89
|
/**
|
|
81
|
-
*
|
|
90
|
+
* Reports what the accounts a specification names look like on chain.
|
|
91
|
+
*
|
|
92
|
+
* Declarations are merged over what is read, and a node with no address is
|
|
93
|
+
* resolved against the setup safe of whoever's API key asks — describing the
|
|
94
|
+
* account *that* caller would deploy. Name accounts by address and declare
|
|
95
|
+
* nothing to be told only what is there, the same for every key.
|
|
82
96
|
*/
|
|
83
97
|
resolveConstellation(workspaceId, payload) {
|
|
84
98
|
return this.postJson(`workspace/${workspaceId}/constellation/resolve`, payload);
|
|
@@ -141,4 +155,4 @@ const jsonStringify = (value, indent) => JSON.stringify(value, (_, value) => {
|
|
|
141
155
|
//#endregion
|
|
142
156
|
export { resolveZodiacDir as n, ApiClient as t };
|
|
143
157
|
|
|
144
|
-
//# sourceMappingURL=api-
|
|
158
|
+
//# sourceMappingURL=api-kKgzHQlu.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-kKgzHQlu.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 '@zodiaceco/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 DEFAULT_API_URL = 'https://app.zodiac.eco/api/v1'\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 // Read `ZODIAC_API_URL`/`ZODIAC_API_KEY` lazily at construction time\n // rather than at module load: the CLI loads `.env` (and `init` writes a\n // fresh one) before the first client is created, so capturing them at\n // import time would bake in the default and ignore the user's config.\n baseUrl = process.env.ZODIAC_API_URL ?? DEFAULT_API_URL,\n fetch: customFetch = fetch,\n headers = {},\n apiKey = process.env.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 /**\n * Every account the workspaces of this org hold, grouped by workspace.\n *\n * Only accounts that exist on chain are listed, so every entry carries an\n * address something was deployed to.\n */\n listAccounts(): Promise<ListAccountsResult> {\n return this.get('accounts')\n }\n\n /** Every member of this org, with the personal safes they have activated. */\n listUsers(): Promise<ListUsersResult> {\n return this.get('users')\n }\n\n /**\n * Stores a specification as the constellation's next revision, and answers\n * with a url to review and deploy it. Applying does not deploy anything, and\n * re-applying an undeployed revision replaces it rather than stacking.\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 * Reports what the accounts a specification names look like on chain.\n *\n * Declarations are merged over what is read, and a node with no address is\n * resolved against the setup safe of whoever's API key asks — describing the\n * account *that* caller would deploy. Name accounts by address and declare\n * nothing to be told only what is there, the same for every key.\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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"}
|
package/dist/cli.mjs
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as resolveZodiacDir, t as ApiClient } from "./api-
|
|
2
|
+
import { n as resolveZodiacDir, t as ApiClient } from "./api-kKgzHQlu.mjs";
|
|
3
3
|
import { a as walkContracts, i as readAbi, n as chainIdFor, o as writeAbi, r as abiFilePath } from "./networks-B0H4Uoyh.mjs";
|
|
4
4
|
import { a as resolveAbisDir, i as loadConfig, o as findProjectRoot, r as ensureConfigStub } from "./config-D0FqysME.mjs";
|
|
5
5
|
import fs, { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
6
|
import path, { basename, join, resolve } from "node:path";
|
|
7
|
-
import { invariant } from "@epic-web/invariant";
|
|
8
7
|
import { getAddress } from "ethers";
|
|
9
8
|
import { Command } from "commander";
|
|
10
9
|
import { config } from "dotenv";
|
|
@@ -179,25 +178,61 @@ const toLiteral = (value, indent = 0) => {
|
|
|
179
178
|
if (typeof value === "object") {
|
|
180
179
|
const entries = Object.entries(value);
|
|
181
180
|
if (entries.length === 0) return "{}";
|
|
182
|
-
return `{\n${entries.map(([k, v]) => `${childPad}${
|
|
181
|
+
return `{\n${entries.map(([k, v]) => `${childPad}${isBareKey(k) ? k : JSON.stringify(k)}: ${toLiteral(v, indent + 1)}`).join(",\n")},\n${pad}}`;
|
|
183
182
|
}
|
|
184
183
|
return String(value);
|
|
185
184
|
};
|
|
185
|
+
/**
|
|
186
|
+
* An account written as the specification node that names it and declares
|
|
187
|
+
* nothing else.
|
|
188
|
+
*
|
|
189
|
+
* The `ref` is required of every node and has to be distinct, but nothing
|
|
190
|
+
* reads it back — the result comes aligned with the request.
|
|
191
|
+
*
|
|
192
|
+
* Spelled out per type rather than passed through: `/accounts` answers with
|
|
193
|
+
* the same three names as an enum, whose members are types of their own, and
|
|
194
|
+
* a node is only a node once it says which of the three it is.
|
|
195
|
+
*/
|
|
196
|
+
const asNodeReference = ({ type, chain, address }, index) => {
|
|
197
|
+
const node = {
|
|
198
|
+
ref: `account_${index}`,
|
|
199
|
+
chain,
|
|
200
|
+
address
|
|
201
|
+
};
|
|
202
|
+
switch (asNodeType(type)) {
|
|
203
|
+
case "SAFE": return {
|
|
204
|
+
...node,
|
|
205
|
+
type: "SAFE"
|
|
206
|
+
};
|
|
207
|
+
case "ROLES": return {
|
|
208
|
+
...node,
|
|
209
|
+
type: "ROLES"
|
|
210
|
+
};
|
|
211
|
+
case "DELAY": return {
|
|
212
|
+
...node,
|
|
213
|
+
type: "DELAY"
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
const asNodeType = (type) => {
|
|
218
|
+
if (type === "SAFE" || type === "ROLES" || type === "DELAY") return type;
|
|
219
|
+
throw new Error(`Cannot describe an account of type "${type}"`);
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Whether a key can be written without quotes. Chain ids are grouping keys in
|
|
223
|
+
* the generated data, and an unquoted number keys the `as const` type by chain
|
|
224
|
+
* id — quoted, it would key by a string the callers never hold.
|
|
225
|
+
*/
|
|
226
|
+
const isBareKey = (key) => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) || /^\d+$/.test(key);
|
|
186
227
|
const pullOrg = async (config) => {
|
|
187
228
|
const client = new ApiClient({ apiKey: config.apiKey });
|
|
188
229
|
const [users, workspaceAccounts] = await Promise.all([client.listUsers(), client.listAccounts()]);
|
|
189
|
-
const
|
|
230
|
+
const allAccounts = workspaceAccounts.flatMap((ws) => ws.accounts);
|
|
190
231
|
const resolved = /* @__PURE__ */ new Map();
|
|
191
|
-
if (
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
chain: account.chain,
|
|
196
|
-
address: account.address
|
|
197
|
-
}) });
|
|
198
|
-
invariant(response?.result?.length === resolvableAccounts.length, `resolveConstellation returned ${response?.result?.length ?? 0} accounts for ${resolvableAccounts.length} accounts`);
|
|
199
|
-
resolvableAccounts.forEach((account, i) => {
|
|
200
|
-
resolved.set(account.id, response.result[i]);
|
|
232
|
+
if (allAccounts.length > 0) {
|
|
233
|
+
const { result } = await client.resolveConstellation(workspaceAccounts[0].workspaceId, { specification: allAccounts.map(asNodeReference) });
|
|
234
|
+
allAccounts.forEach((account, index) => {
|
|
235
|
+
resolved.set(account.id, result[index]);
|
|
201
236
|
});
|
|
202
237
|
}
|
|
203
238
|
const accountsRecord = {};
|
|
@@ -211,23 +246,27 @@ const pullOrg = async (config) => {
|
|
|
211
246
|
DELAY: delays
|
|
212
247
|
};
|
|
213
248
|
const isNodeType = (type) => type === "SAFE" || type === "ROLES" || type === "DELAY";
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
ROLES: /* @__PURE__ */ new Map(),
|
|
217
|
-
DELAY: /* @__PURE__ */ new Map()
|
|
218
|
-
};
|
|
249
|
+
const namespaceKey = (type, chain) => `${type}:${chain}`;
|
|
250
|
+
const labelCounts = /* @__PURE__ */ new Map();
|
|
219
251
|
for (const account of ws.accounts) {
|
|
220
252
|
const { label } = account;
|
|
221
253
|
if (label == null || !isNodeType(account.type)) continue;
|
|
222
|
-
const
|
|
254
|
+
const key = namespaceKey(account.type, account.chain);
|
|
255
|
+
let counts = labelCounts.get(key);
|
|
256
|
+
if (!counts) {
|
|
257
|
+
counts = /* @__PURE__ */ new Map();
|
|
258
|
+
labelCounts.set(key, counts);
|
|
259
|
+
}
|
|
223
260
|
counts.set(label, (counts.get(label) ?? 0) + 1);
|
|
224
261
|
}
|
|
225
262
|
for (const account of ws.accounts) {
|
|
226
263
|
const { label } = account;
|
|
227
264
|
if (label == null || !isNodeType(account.type)) continue;
|
|
228
265
|
const onChain = resolved.get(account.id);
|
|
229
|
-
const key = (
|
|
230
|
-
bucketsByType[account.type]
|
|
266
|
+
const key = (labelCounts.get(namespaceKey(account.type, account.chain))?.get(label) ?? 0) > 1 ? `${label} (${getAddress(account.address)})` : label;
|
|
267
|
+
const byChain = bucketsByType[account.type];
|
|
268
|
+
const byLabel = byChain[account.chain] ??= {};
|
|
269
|
+
byLabel[key] = {
|
|
231
270
|
id: account.id,
|
|
232
271
|
label,
|
|
233
272
|
address: account.address,
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 { createHash, 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'\nimport { ensureConfigStub } from '../config'\nimport { findProjectRoot } from '../projectRoot'\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 ?? findProjectRoot()\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\n // PKCE: keep `code_verifier` private to this process. Send only the\n // SHA-256 hash (`code_challenge`) over the wire.\n const codeVerifier = randomBytes(32).toString('base64url')\n const codeChallenge = createHash('sha256')\n .update(codeVerifier)\n .digest('base64url')\n\n // `state` binds the redirect we receive on the loopback to this CLI\n // invocation, defending against a stray local process trying to inject\n // an auth code into our callback.\n const state = randomBytes(32).toString('base64url')\n\n const { port, waitForCode, close } = await startCallbackServer({\n expectedState: state,\n appUrl,\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('code_challenge', codeChallenge)\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 code: string\n try {\n code = await waitForCode(CALLBACK_TIMEOUT_MS)\n } finally {\n await close()\n }\n\n const apiKey = await exchangeCodeForKey(appUrl, code, codeVerifier)\n\n const envPath = join(rootDir, '.env')\n const apiUrl = `${appUrl}/api/v1`\n writeEnv(envPath, { ZODIAC_API_KEY: apiKey, ZODIAC_API_URL: apiUrl })\n\n // So a same-run `pull` targets the org we just authorized, not the default.\n process.env.ZODIAC_API_KEY = apiKey\n process.env.ZODIAC_API_URL = apiUrl\n\n console.log(`✅ API key written to ${envPath}`)\n\n const configPath = join(rootDir, 'zodiac.config.ts')\n if (ensureConfigStub(configPath)) {\n console.log(`✅ Created ${configPath}`)\n }\n\n return apiKey\n}\n\nconst exchangeCodeForKey = async (\n appUrl: string,\n code: string,\n codeVerifier: string\n): Promise<string> => {\n const response = await fetch(`${appUrl}/cli-auth/exchange`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ code, code_verifier: codeVerifier }),\n })\n\n if (!response.ok) {\n let detail = ''\n try {\n const body = (await response.json()) as { error?: string }\n detail = body.error ? `: ${body.error}` : ''\n } catch {\n // ignore — fall through to a generic message\n }\n throw new Error(\n `Failed to exchange auth code (${response.status})${detail}`\n )\n }\n\n const { key } = (await response.json()) as { key: unknown }\n if (typeof key !== 'string' || !key.startsWith('zodiac_')) {\n throw new Error(\n 'Exchange endpoint returned an unexpected response (missing or malformed key)'\n )\n }\n return key\n}\n\ntype StartServerOptions = {\n expectedState: string\n appUrl: string\n}\n\ntype StartServerResult = {\n port: number\n waitForCode: (timeoutMs: number) => Promise<string>\n close: () => Promise<void>\n}\n\nconst startCallbackServer = async ({\n expectedState,\n appUrl,\n}: StartServerOptions): Promise<StartServerResult> => {\n let resolveCode: (code: string) => void = () => {}\n let rejectCode: (err: Error) => void = () => {}\n const codePromise = new Promise<string>((res, rej) => {\n resolveCode = res\n rejectCode = rej\n })\n\n const handler = (req: IncomingMessage, res: ServerResponse) => {\n if (req.method !== 'GET' || !req.url?.startsWith('/callback')) {\n res.statusCode = 404\n res.end()\n return\n }\n\n const url = new URL(req.url, `http://127.0.0.1`)\n const code = url.searchParams.get('code')\n const state = url.searchParams.get('state')\n\n if (state !== expectedState) {\n // A stray request — could be a stale browser tab, a malicious\n // local process, etc. Silently 403 and keep waiting for the right\n // one. Don't reject the CLI promise.\n respondHtml(\n res,\n 403,\n renderHtml(\n 'Mismatched state',\n `<p>This callback didn't match the running <code>zodiac init</code> session. You can close this tab.</p>`\n )\n )\n return\n }\n\n if (typeof code !== 'string' || code.length === 0) {\n respondHtml(\n res,\n 400,\n renderHtml(\n 'Missing code',\n `<p>The callback URL didn't include an auth <code>code</code>. Please re-run <code>zodiac init</code>.</p>`\n )\n )\n return\n }\n\n respondHtml(\n res,\n 200,\n renderHtml(\n 'Authorized',\n `<p>Your CLI now has its API key. You can close this tab and return to your terminal.</p>\n <p><a href=\"${escapeHtml(appUrl)}\">Back to Zodiac</a></p>`\n )\n )\n resolveCode(code)\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 waitForCode = (timeoutMs: number) =>\n Promise.race<string>([\n codePromise,\n new Promise<string>((_, rej) =>\n setTimeout(() => {\n rejectCode(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, waitForCode, close }\n}\n\nconst respondHtml = (res: ServerResponse, status: number, body: string) => {\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.setHeader('content-type', 'text/html; charset=utf-8')\n res.statusCode = status\n res.end(body)\n}\n\nconst renderHtml = (title: string, bodyHtml: string) => `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${escapeHtml(title)} — Zodiac CLI</title>\n <style>\n body { font: 16px/1.5 -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; max-width: 32rem; margin: 4rem auto; padding: 0 1rem; color: #1f2937; }\n h1 { font-weight: 300; font-size: 1.75rem; margin-bottom: 1rem; }\n code { background: #f3f4f6; padding: 0.125rem 0.375rem; border-radius: 0.25rem; }\n a { color: #1d4ed8; }\n </style>\n </head>\n <body>\n <h1>${escapeHtml(title)}</h1>\n ${bodyHtml}\n </body>\n</html>\n`\n\nconst escapeHtml = (value: string) =>\n value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\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 // Accounts we only know by address — Safe owners, external role\n // members, route targets — carry no label. They can't key a\n // label-addressed namespace, so they stay out of the codegen.\n // Vault entries always carry one.\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 const { label } = account\n if (label == null || !isNodeType(account.type)) continue\n const counts = labelCountByType[account.type]\n counts.set(label, (counts.get(label) ?? 0) + 1)\n }\n\n for (const account of ws.accounts) {\n const { label } = account\n if (label == null || !isNodeType(account.type)) continue\n const onChain = resolved.get(account.id)\n const counts = labelCountByType[account.type]\n const key =\n (counts.get(label) ?? 0) > 1\n ? `${label} (${getAddress(account.address)})`\n : label\n bucketsByType[account.type][key] = {\n id: account.id,\n 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\ntype Implementation = { abi?: unknown }\ntype Account = Implementation & {\n proxy?: unknown\n implementation?: Implementation\n}\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}/accounts/${address}`\n let account: Account\n try {\n const resp = await fetch(url)\n if (!resp.ok) return null\n account = (await resp.json()) as Account\n } catch {\n return null\n }\n if (account == null || typeof account !== 'object') return null\n\n // For a proxy the endpoint reports the proxy under `proxy` and the contract\n // that actually holds the functions under `implementation`. Permissions are\n // written against the implementation ABI.\n const abi = account.proxy ? account.implementation?.abi : account.abi\n if (!Array.isArray(abi) || abi.length === 0) return null\n return abi as Abi\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 { Scoping, Options, EVERYTHING, FunctionPermission, TargetPermission } from \"@zodiaceco/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 members.push(\n `${indent} [EVERYTHING]: (options?: Options) => TargetPermission;`\n )\n\n // Members mirror what the runtime proxy can resolve, which is whatever\n // `Interface.getFunction` accepts. A name it would find ambiguous is not a\n // member there — the proxy returns undefined for it — so an overloaded name\n // is emitted once per full signature and never bare. Declaring the bare name\n // anyway is what used to make `allow.eth.susds.deposit(…)` type-check and\n // then fail at runtime.\n for (const overloads of groupByName(abi).values()) {\n const overloaded = overloads.length > 1\n for (const fragment of overloads) {\n const key = overloaded\n ? signatureFor(fragment)\n : (fragment.name as string)\n members.push(renderFunctionSignature(fragment, key, indent + ' '))\n }\n }\n return ['{', ...members, `${indent}}`].join('\\n')\n}\n\n/**\n * Permissionable fragments grouped by function name, in ABI order. Two\n * fragments that render the same signature are the same function listed twice,\n * so they collapse — emitting the key twice would not compile.\n */\nfunction groupByName(abi: Abi): Map<string, AbiFragment[]> {\n const byName = new Map<string, AbiFragment[]>()\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) continue\n const overloads = byName.get(name) ?? []\n if (overloads.some((f) => signatureFor(f) === signatureFor(fragment))) {\n continue\n }\n byName.set(name, [...overloads, fragment])\n }\n return byName\n}\n\n/** The canonical `name(type,…)` form, which is how `Interface.getFunction`\n * addresses one overload of a name. */\nfunction signatureFor(fragment: AbiFragment): string {\n const inputs = (fragment.inputs as AbiFragment[]) ?? []\n return `${fragment.name}(${inputs.map(canonicalTypeFor).join(',')})`\n}\n\nfunction canonicalTypeFor(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 `${canonicalTypeFor(inner)}[${arrayMatch[2]}]`\n }\n\n if (type === 'tuple') {\n const components = (fragment.components as AbiFragment[]) ?? []\n return `(${components.map(canonicalTypeFor).join(',')})`\n }\n\n return type\n}\n\nfunction renderFunctionSignature(\n fragment: AbiFragment,\n key: string,\n indent: string\n): string {\n const name = renderPropKey(key)\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 createIfMissing: true,\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('pull')\n .description(\n 'Fetch Zodiac org data and contract ABIs, generating typed SDK functions. Authorizes this directory automatically on first run.'\n )\n .action(async (_opts, cmd) => {\n const config = await loadConfigOrInit(cmd.optsWithGlobals().config)\n await Promise.all([pullOrg(config), pullContracts(config)])\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('init')\n .description(\n 'Re-authorize this directory / mint a new API key (run automatically by `pull` on first use).'\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 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 { createHash, 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'\nimport { ensureConfigStub } from '../config'\nimport { findProjectRoot } from '../projectRoot'\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 ?? findProjectRoot()\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\n // PKCE: keep `code_verifier` private to this process. Send only the\n // SHA-256 hash (`code_challenge`) over the wire.\n const codeVerifier = randomBytes(32).toString('base64url')\n const codeChallenge = createHash('sha256')\n .update(codeVerifier)\n .digest('base64url')\n\n // `state` binds the redirect we receive on the loopback to this CLI\n // invocation, defending against a stray local process trying to inject\n // an auth code into our callback.\n const state = randomBytes(32).toString('base64url')\n\n const { port, waitForCode, close } = await startCallbackServer({\n expectedState: state,\n appUrl,\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('code_challenge', codeChallenge)\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 code: string\n try {\n code = await waitForCode(CALLBACK_TIMEOUT_MS)\n } finally {\n await close()\n }\n\n const apiKey = await exchangeCodeForKey(appUrl, code, codeVerifier)\n\n const envPath = join(rootDir, '.env')\n const apiUrl = `${appUrl}/api/v1`\n writeEnv(envPath, { ZODIAC_API_KEY: apiKey, ZODIAC_API_URL: apiUrl })\n\n // So a same-run `pull` targets the org we just authorized, not the default.\n process.env.ZODIAC_API_KEY = apiKey\n process.env.ZODIAC_API_URL = apiUrl\n\n console.log(`✅ API key written to ${envPath}`)\n\n const configPath = join(rootDir, 'zodiac.config.ts')\n if (ensureConfigStub(configPath)) {\n console.log(`✅ Created ${configPath}`)\n }\n\n return apiKey\n}\n\nconst exchangeCodeForKey = async (\n appUrl: string,\n code: string,\n codeVerifier: string\n): Promise<string> => {\n const response = await fetch(`${appUrl}/cli-auth/exchange`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ code, code_verifier: codeVerifier }),\n })\n\n if (!response.ok) {\n let detail = ''\n try {\n const body = (await response.json()) as { error?: string }\n detail = body.error ? `: ${body.error}` : ''\n } catch {\n // ignore — fall through to a generic message\n }\n throw new Error(\n `Failed to exchange auth code (${response.status})${detail}`\n )\n }\n\n const { key } = (await response.json()) as { key: unknown }\n if (typeof key !== 'string' || !key.startsWith('zodiac_')) {\n throw new Error(\n 'Exchange endpoint returned an unexpected response (missing or malformed key)'\n )\n }\n return key\n}\n\ntype StartServerOptions = {\n expectedState: string\n appUrl: string\n}\n\ntype StartServerResult = {\n port: number\n waitForCode: (timeoutMs: number) => Promise<string>\n close: () => Promise<void>\n}\n\nconst startCallbackServer = async ({\n expectedState,\n appUrl,\n}: StartServerOptions): Promise<StartServerResult> => {\n let resolveCode: (code: string) => void = () => {}\n let rejectCode: (err: Error) => void = () => {}\n const codePromise = new Promise<string>((res, rej) => {\n resolveCode = res\n rejectCode = rej\n })\n\n const handler = (req: IncomingMessage, res: ServerResponse) => {\n if (req.method !== 'GET' || !req.url?.startsWith('/callback')) {\n res.statusCode = 404\n res.end()\n return\n }\n\n const url = new URL(req.url, `http://127.0.0.1`)\n const code = url.searchParams.get('code')\n const state = url.searchParams.get('state')\n\n if (state !== expectedState) {\n // A stray request — could be a stale browser tab, a malicious\n // local process, etc. Silently 403 and keep waiting for the right\n // one. Don't reject the CLI promise.\n respondHtml(\n res,\n 403,\n renderHtml(\n 'Mismatched state',\n `<p>This callback didn't match the running <code>zodiac init</code> session. You can close this tab.</p>`\n )\n )\n return\n }\n\n if (typeof code !== 'string' || code.length === 0) {\n respondHtml(\n res,\n 400,\n renderHtml(\n 'Missing code',\n `<p>The callback URL didn't include an auth <code>code</code>. Please re-run <code>zodiac init</code>.</p>`\n )\n )\n return\n }\n\n respondHtml(\n res,\n 200,\n renderHtml(\n 'Authorized',\n `<p>Your CLI now has its API key. You can close this tab and return to your terminal.</p>\n <p><a href=\"${escapeHtml(appUrl)}\">Back to Zodiac</a></p>`\n )\n )\n resolveCode(code)\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 waitForCode = (timeoutMs: number) =>\n Promise.race<string>([\n codePromise,\n new Promise<string>((_, rej) =>\n setTimeout(() => {\n rejectCode(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, waitForCode, close }\n}\n\nconst respondHtml = (res: ServerResponse, status: number, body: string) => {\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.setHeader('content-type', 'text/html; charset=utf-8')\n res.statusCode = status\n res.end(body)\n}\n\nconst renderHtml = (title: string, bodyHtml: string) => `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${escapeHtml(title)} — Zodiac CLI</title>\n <style>\n body { font: 16px/1.5 -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; max-width: 32rem; margin: 4rem auto; padding: 0 1rem; color: #1f2937; }\n h1 { font-weight: 300; font-size: 1.75rem; margin-bottom: 1rem; }\n code { background: #f3f4f6; padding: 0.125rem 0.375rem; border-radius: 0.25rem; }\n a { color: #1d4ed8; }\n </style>\n </head>\n <body>\n <h1>${escapeHtml(title)}</h1>\n ${bodyHtml}\n </body>\n</html>\n`\n\nconst escapeHtml = (value: string) =>\n value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\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 {\n Address,\n ChainId,\n ResolveConstellationPayload,\n} from '@zodiaceco/api-types'\nimport type { ResolvedConfig } from '../config'\nimport { ApiClient } from '../../api'\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}${isBareKey(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\ntype NodeType = 'SAFE' | 'ROLES' | 'DELAY'\n\n/**\n * An account written as the specification node that names it and declares\n * nothing else.\n *\n * The `ref` is required of every node and has to be distinct, but nothing\n * reads it back — the result comes aligned with the request.\n *\n * Spelled out per type rather than passed through: `/accounts` answers with\n * the same three names as an enum, whose members are types of their own, and\n * a node is only a node once it says which of the three it is.\n */\nconst asNodeReference = (\n {\n type,\n chain,\n address,\n }: { type: string; chain: ChainId; address: Lowercase<Address> },\n index: number\n): ResolveConstellationPayload['specification'][number] => {\n const node = { ref: `account_${index}`, chain, address }\n\n switch (asNodeType(type)) {\n case 'SAFE':\n return { ...node, type: 'SAFE' }\n case 'ROLES':\n return { ...node, type: 'ROLES' }\n case 'DELAY':\n return { ...node, type: 'DELAY' }\n }\n}\n\nconst asNodeType = (type: string): NodeType => {\n if (type === 'SAFE' || type === 'ROLES' || type === 'DELAY') {\n return type\n }\n\n throw new Error(`Cannot describe an account of type \"${type}\"`)\n}\n\n/**\n * Whether a key can be written without quotes. Chain ids are grouping keys in\n * the generated data, and an unquoted number keys the `as const` type by chain\n * id — quoted, it would key by a string the callers never hold.\n */\nconst isBareKey = (key: string): boolean =>\n /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) || /^\\d+$/.test(key)\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 // The generated accounts carry their onchain state so a node reads as the\n // account it names and completes in an editor. Only what a constellation\n // declares is ever pushed back, so this is for reading, not a declaration.\n //\n // Each account is named by its address and nothing else. `/resolve`\n // resolves a node it has no address for against the setup safe of whoever\n // holds the API key — which describes the account *that* caller would\n // deploy, not the one being asked about — and it merges declarations over\n // what it reads. Sending an address and no declarations leaves it nothing\n // to derive and nothing to merge, so the answer is the account itself, the\n // same for every key.\n //\n // Asking about every listed account is safe for the same reason: `/accounts`\n // admits one only once it has been seen to hold code, so every address here\n // is one that can be read from chain.\n const allAccounts = workspaceAccounts.flatMap((ws) => ws.accounts)\n const resolved = new Map<\n string,\n Awaited<ReturnType<typeof client.resolveConstellation>>['result'][number]\n >()\n\n if (allAccounts.length > 0) {\n const { result } = await client.resolveConstellation(\n workspaceAccounts[0].workspaceId, // any workspace works for the resolve route\n {\n specification: allAccounts.map(asNodeReference),\n }\n )\n\n // Aligned with the request, entry for entry.\n allAccounts.forEach((account, index) => {\n resolved.set(account.id, result[index])\n })\n }\n\n // Group accounts by type and then by chain into separate bracket-access\n // namespaces: `safes[chain]`, `rolesMods[chain]`, `delays[chain]`. This way\n // `eth.safe[...]` IntelliSense only suggests SAFE labels deployed on the\n // constellation's own chain, and the label-collision suffix only kicks in\n // where a label is genuinely ambiguous — same type, same chain.\n type AccountsByChain = Record<number, Record<string, unknown>>\n\n const accountsRecord: Record<string, unknown> = {}\n for (const ws of workspaceAccounts) {\n const safes: AccountsByChain = {}\n const rolesMods: AccountsByChain = {}\n const delays: AccountsByChain = {}\n\n const bucketsByType = {\n SAFE: safes,\n ROLES: rolesMods,\n DELAY: delays,\n } as const\n\n const isNodeType = (type: string): type is NodeType =>\n type === 'SAFE' || type === 'ROLES' || type === 'DELAY'\n\n // Accounts we only know by address — Safe owners, external role\n // members, route targets — carry no label. They can't key a\n // label-addressed namespace, so they stay out of the codegen.\n // Vault entries always carry one.\n\n // Count labels per namespace so we only suffix collisions a constellation\n // could actually run into. Two safes named `Treasury` on different chains\n // never compete for a key, because a constellation only ever sees one of\n // those chains.\n const namespaceKey = (type: NodeType, chain: number) => `${type}:${chain}`\n const labelCounts = new Map<string, Map<string, number>>()\n\n for (const account of ws.accounts) {\n const { label } = account\n if (label == null || !isNodeType(account.type)) continue\n const key = namespaceKey(account.type, account.chain)\n let counts = labelCounts.get(key)\n if (!counts) {\n counts = new Map()\n labelCounts.set(key, counts)\n }\n counts.set(label, (counts.get(label) ?? 0) + 1)\n }\n\n for (const account of ws.accounts) {\n const { label } = account\n if (label == null || !isNodeType(account.type)) continue\n const onChain = resolved.get(account.id)\n const counts = labelCounts.get(namespaceKey(account.type, account.chain))\n const key =\n (counts?.get(label) ?? 0) > 1\n ? `${label} (${getAddress(account.address)})`\n : label\n const byChain = bucketsByType[account.type]\n const byLabel = (byChain[account.chain] ??= {})\n byLabel[key] = {\n id: account.id,\n 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\ntype Implementation = { abi?: unknown }\ntype Account = Implementation & {\n proxy?: unknown\n implementation?: Implementation\n}\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}/accounts/${address}`\n let account: Account\n try {\n const resp = await fetch(url)\n if (!resp.ok) return null\n account = (await resp.json()) as Account\n } catch {\n return null\n }\n if (account == null || typeof account !== 'object') return null\n\n // For a proxy the endpoint reports the proxy under `proxy` and the contract\n // that actually holds the functions under `implementation`. Permissions are\n // written against the implementation ABI.\n const abi = account.proxy ? account.implementation?.abi : account.abi\n if (!Array.isArray(abi) || abi.length === 0) return null\n return abi as Abi\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 { Scoping, Options, EVERYTHING, FunctionPermission, TargetPermission } from \"@zodiaceco/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 members.push(\n `${indent} [EVERYTHING]: (options?: Options) => TargetPermission;`\n )\n\n // Members mirror what the runtime proxy can resolve, which is whatever\n // `Interface.getFunction` accepts. A name it would find ambiguous is not a\n // member there — the proxy returns undefined for it — so an overloaded name\n // is emitted once per full signature and never bare. Declaring the bare name\n // anyway is what used to make `allow.eth.susds.deposit(…)` type-check and\n // then fail at runtime.\n for (const overloads of groupByName(abi).values()) {\n const overloaded = overloads.length > 1\n for (const fragment of overloads) {\n const key = overloaded\n ? signatureFor(fragment)\n : (fragment.name as string)\n members.push(renderFunctionSignature(fragment, key, indent + ' '))\n }\n }\n return ['{', ...members, `${indent}}`].join('\\n')\n}\n\n/**\n * Permissionable fragments grouped by function name, in ABI order. Two\n * fragments that render the same signature are the same function listed twice,\n * so they collapse — emitting the key twice would not compile.\n */\nfunction groupByName(abi: Abi): Map<string, AbiFragment[]> {\n const byName = new Map<string, AbiFragment[]>()\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) continue\n const overloads = byName.get(name) ?? []\n if (overloads.some((f) => signatureFor(f) === signatureFor(fragment))) {\n continue\n }\n byName.set(name, [...overloads, fragment])\n }\n return byName\n}\n\n/** The canonical `name(type,…)` form, which is how `Interface.getFunction`\n * addresses one overload of a name. */\nfunction signatureFor(fragment: AbiFragment): string {\n const inputs = (fragment.inputs as AbiFragment[]) ?? []\n return `${fragment.name}(${inputs.map(canonicalTypeFor).join(',')})`\n}\n\nfunction canonicalTypeFor(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 `${canonicalTypeFor(inner)}[${arrayMatch[2]}]`\n }\n\n if (type === 'tuple') {\n const components = (fragment.components as AbiFragment[]) ?? []\n return `(${components.map(canonicalTypeFor).join(',')})`\n }\n\n return type\n}\n\nfunction renderFunctionSignature(\n fragment: AbiFragment,\n key: string,\n indent: string\n): string {\n const name = renderPropKey(key)\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 createIfMissing: true,\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('pull')\n .description(\n 'Fetch Zodiac org data and contract ABIs, generating typed SDK functions. Authorizes this directory automatically on first run.'\n )\n .action(async (_opts, cmd) => {\n const config = await loadConfigOrInit(cmd.optsWithGlobals().config)\n await Promise.all([pullOrg(config), pullContracts(config)])\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('init')\n .description(\n 'Re-authorize this directory / mint a new API key (run automatically by `pull` on first use).'\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 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference path="./zodiac-os-codegen.d.ts" />
|
|
2
|
-
import { a as PermissionEntry, c as ConstellationNode, l as constellation, s as Permissions } from "./actions-
|
|
2
|
+
import { a as PermissionEntry, c as ConstellationNode, l as constellation, s as Permissions } from "./actions-tA_Oe_kC.mjs";
|
|
3
3
|
import { a as EVERYTHING, c as Scoping, i as ConditionFunction, l as TargetPermission, n as ChainPrefix, o as FunctionPermission, s as Options, u as buildAllowKit } from "./index-CnH26Z_A.mjs";
|
|
4
4
|
import { Annotation, Permission, PermissionSet, c, encodeKey, forAll } from "zodiac-roles-sdk";
|
|
5
5
|
import { ApplyConstellationPayload, ApplyConstellationResult, ListAccountsResult, ListUsersResult, ResolveConstellationPayload, ResolveConstellationResult } from "@zodiaceco/api-types";
|
|
@@ -26,14 +26,28 @@ declare class ApiClient {
|
|
|
26
26
|
}?: Options$1);
|
|
27
27
|
protected postJson(endpoint: string, payload: unknown): Promise<any>;
|
|
28
28
|
protected get(endpoint: string): Promise<any>;
|
|
29
|
+
/**
|
|
30
|
+
* Every account the workspaces of this org hold, grouped by workspace.
|
|
31
|
+
*
|
|
32
|
+
* Only accounts that exist on chain are listed, so every entry carries an
|
|
33
|
+
* address something was deployed to.
|
|
34
|
+
*/
|
|
29
35
|
listAccounts(): Promise<ListAccountsResult>;
|
|
36
|
+
/** Every member of this org, with the personal safes they have activated. */
|
|
30
37
|
listUsers(): Promise<ListUsersResult>;
|
|
31
38
|
/**
|
|
32
|
-
*
|
|
39
|
+
* Stores a specification as the constellation's next revision, and answers
|
|
40
|
+
* with a url to review and deploy it. Applying does not deploy anything, and
|
|
41
|
+
* re-applying an undeployed revision replaces it rather than stacking.
|
|
33
42
|
*/
|
|
34
43
|
applyConstellation(workspaceId: UUID, payload: ApplyConstellationPayload): Promise<ApplyConstellationResult>;
|
|
35
44
|
/**
|
|
36
|
-
*
|
|
45
|
+
* Reports what the accounts a specification names look like on chain.
|
|
46
|
+
*
|
|
47
|
+
* Declarations are merged over what is read, and a node with no address is
|
|
48
|
+
* resolved against the setup safe of whoever's API key asks — describing the
|
|
49
|
+
* account *that* caller would deploy. Name accounts by address and declare
|
|
50
|
+
* nothing to be told only what is there, the same for every key.
|
|
37
51
|
*/
|
|
38
52
|
resolveConstellation(workspaceId: UUID, payload: ResolveConstellationPayload): Promise<ResolveConstellationResult>;
|
|
39
53
|
}
|
|
@@ -43,15 +57,25 @@ type PushOpts = {
|
|
|
43
57
|
/** API client instance. Defaults to a client configured from environment variables. */api?: ApiClient;
|
|
44
58
|
};
|
|
45
59
|
/**
|
|
46
|
-
*
|
|
60
|
+
* Applies the given nodes as the next revision of the constellations they
|
|
61
|
+
* belong to, and answers with a url per constellation to review and deploy.
|
|
62
|
+
*
|
|
63
|
+
* Each node travels with what it was actually given, never with the onchain
|
|
64
|
+
* state a `pull` filled it with for reading — so a node passed as a bare
|
|
65
|
+
* reference declares nothing and leaves the account it names alone. Nodes
|
|
66
|
+
* naming each other travel as references and are substituted for addresses
|
|
67
|
+
* once those are derived at deploy.
|
|
47
68
|
*
|
|
69
|
+
* Passing an object names each node by its key; passing an array names them by
|
|
70
|
+
* position. Either way the name is the node's `ref`, which is how the app
|
|
71
|
+
* follows a node from one revision to the next.
|
|
48
72
|
*
|
|
49
73
|
* ```ts
|
|
50
74
|
* const eth = constellation({ workspace: 'GG', label: 'my constellation', chain: 1 })
|
|
51
75
|
* const dao = eth.safe['GG DAO']
|
|
52
76
|
* const roles = eth.roles['New Roles']({ nonce: 0n, target: dao, owner: dao, avatar: dao })
|
|
53
77
|
*
|
|
54
|
-
* await push(
|
|
78
|
+
* await push({ dao, roles })
|
|
55
79
|
* ```
|
|
56
80
|
*/
|
|
57
81
|
declare function push(nodes: ConstellationNode[] | {
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as resolveZodiacDir, t as ApiClient } from "./api-
|
|
1
|
+
import { n as resolveZodiacDir, t as ApiClient } from "./api-kKgzHQlu.mjs";
|
|
2
2
|
import { i as toAnnotation, n as isDeFiKitEntry, t as assertRecipientChains } from "./permissionEntries-D3fSVhwO.mjs";
|
|
3
3
|
import { n as EVERYTHING, t as buildAllowKit } from "./allow-CrDzDtxx.mjs";
|
|
4
4
|
import { createRequire } from "module";
|
|
@@ -11,15 +11,21 @@ function loadCodegen() {
|
|
|
11
11
|
/**
|
|
12
12
|
* Creates a constellation scoped to a workspace and chain.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Bracket access names an account of that workspace **on that chain** — a
|
|
15
|
+
* vault, or a node an earlier constellation deployed. A label the chain has no
|
|
16
|
+
* account for reads as a new node instead, so it has to be given the config it
|
|
17
|
+
* is deployed with.
|
|
18
|
+
*
|
|
16
19
|
* ```ts
|
|
17
20
|
* const eth = constellation({ workspace: 'GG', label: 'my constellation', chain: 1 })
|
|
18
21
|
*
|
|
19
|
-
* const dao = eth.safe['GG DAO'] //
|
|
20
|
-
* const roles = eth.roles['GG DAO'] //
|
|
22
|
+
* const dao = eth.safe['GG DAO'] // the safe of that name on chain 1
|
|
23
|
+
* const roles = eth.roles['GG DAO'] // the roles mod of that name
|
|
21
24
|
* const newSafe = eth.safe['New Safe']({ nonce: 0n, threshold: 2, owners: [...], modules: [...] })
|
|
22
25
|
* ```
|
|
26
|
+
*
|
|
27
|
+
* Names come from the last `pull-org`, so an account deployed since then is
|
|
28
|
+
* not one yet.
|
|
23
29
|
*/
|
|
24
30
|
function constellation(opts, internal) {
|
|
25
31
|
const codegen = internal?.codegen ?? loadCodegen();
|
|
@@ -27,19 +33,21 @@ function constellation(opts, internal) {
|
|
|
27
33
|
const safesByLabel = {};
|
|
28
34
|
const rolesByLabel = {};
|
|
29
35
|
if (ws) {
|
|
30
|
-
|
|
31
|
-
for (const [label, account] of Object.entries(ws.
|
|
36
|
+
const onThisChain = (byChain) => byChain[opts.chain] ?? {};
|
|
37
|
+
for (const [label, account] of Object.entries(onThisChain(ws.safes))) safesByLabel[label] = account;
|
|
38
|
+
for (const [label, account] of Object.entries(onThisChain(ws.rolesMods))) rolesByLabel[label] = account;
|
|
32
39
|
}
|
|
33
40
|
const meta = {
|
|
34
41
|
label: opts.label,
|
|
35
42
|
chain: opts.chain,
|
|
36
43
|
workspaceId: ws?.workspaceId ?? ""
|
|
37
44
|
};
|
|
38
|
-
function makeNodeRef(data) {
|
|
45
|
+
function makeNodeRef(data, declared) {
|
|
39
46
|
return Object.freeze({
|
|
40
47
|
...data,
|
|
41
48
|
chain: opts.chain,
|
|
42
|
-
_constellation: meta
|
|
49
|
+
_constellation: meta,
|
|
50
|
+
_declared: declared
|
|
43
51
|
});
|
|
44
52
|
}
|
|
45
53
|
function entityAccessor(registry, type) {
|
|
@@ -55,13 +63,14 @@ function constellation(opts, internal) {
|
|
|
55
63
|
...existing || {},
|
|
56
64
|
...overrides,
|
|
57
65
|
label: specLabel
|
|
58
|
-
});
|
|
66
|
+
}, Object.keys(overrides ?? {}));
|
|
59
67
|
Object.assign(fn, {
|
|
60
68
|
type,
|
|
61
69
|
...existing || {},
|
|
62
70
|
label: specLabel,
|
|
63
71
|
chain: opts.chain,
|
|
64
|
-
_constellation: meta
|
|
72
|
+
_constellation: meta,
|
|
73
|
+
_declared: []
|
|
65
74
|
});
|
|
66
75
|
cache.set(name, fn);
|
|
67
76
|
return fn;
|
|
@@ -85,15 +94,25 @@ function constellation(opts, internal) {
|
|
|
85
94
|
//#endregion
|
|
86
95
|
//#region src/push.ts
|
|
87
96
|
/**
|
|
88
|
-
*
|
|
97
|
+
* Applies the given nodes as the next revision of the constellations they
|
|
98
|
+
* belong to, and answers with a url per constellation to review and deploy.
|
|
89
99
|
*
|
|
100
|
+
* Each node travels with what it was actually given, never with the onchain
|
|
101
|
+
* state a `pull` filled it with for reading — so a node passed as a bare
|
|
102
|
+
* reference declares nothing and leaves the account it names alone. Nodes
|
|
103
|
+
* naming each other travel as references and are substituted for addresses
|
|
104
|
+
* once those are derived at deploy.
|
|
105
|
+
*
|
|
106
|
+
* Passing an object names each node by its key; passing an array names them by
|
|
107
|
+
* position. Either way the name is the node's `ref`, which is how the app
|
|
108
|
+
* follows a node from one revision to the next.
|
|
90
109
|
*
|
|
91
110
|
* ```ts
|
|
92
111
|
* const eth = constellation({ workspace: 'GG', label: 'my constellation', chain: 1 })
|
|
93
112
|
* const dao = eth.safe['GG DAO']
|
|
94
113
|
* const roles = eth.roles['New Roles']({ nonce: 0n, target: dao, owner: dao, avatar: dao })
|
|
95
114
|
*
|
|
96
|
-
* await push(
|
|
115
|
+
* await push({ dao, roles })
|
|
97
116
|
* ```
|
|
98
117
|
*/
|
|
99
118
|
async function push(nodes, opts) {
|
|
@@ -126,6 +145,13 @@ async function push(nodes, opts) {
|
|
|
126
145
|
}
|
|
127
146
|
return results;
|
|
128
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* What a ref may be spelled like. Refs come from export names and object keys,
|
|
150
|
+
* so any identifier a node can be declared under has to survive the push —
|
|
151
|
+
* except one opening with `$`, which is how a node references another. A list
|
|
152
|
+
* of nodes refs them by position, so a plain index counts too.
|
|
153
|
+
*/
|
|
154
|
+
const IDENTIFIER = /^(?:[a-zA-Z_][a-zA-Z0-9_$]*|\d+)$/;
|
|
129
155
|
function labelKey(node) {
|
|
130
156
|
return `${node._constellation.workspaceId}:${node.type}:${node.label}`;
|
|
131
157
|
}
|
|
@@ -133,7 +159,7 @@ function deriveRefs(nodes) {
|
|
|
133
159
|
const byIdentity = /* @__PURE__ */ new Map();
|
|
134
160
|
const byLabel = /* @__PURE__ */ new Map();
|
|
135
161
|
const register = (node, ref) => {
|
|
136
|
-
invariant(
|
|
162
|
+
invariant(IDENTIFIER.test(ref), `Invalid ref "${ref}": a ref is a JavaScript identifier, and cannot start with "$"`);
|
|
137
163
|
byIdentity.set(node, ref);
|
|
138
164
|
byLabel.set(labelKey(node), ref);
|
|
139
165
|
};
|
|
@@ -151,10 +177,31 @@ function deriveRefs(nodes) {
|
|
|
151
177
|
byLabel
|
|
152
178
|
};
|
|
153
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* What situates a node rather than configures it: which account it is, and how
|
|
182
|
+
* the workspace holds it. Always sent, even for a reference that declares
|
|
183
|
+
* nothing — none of it reaches the onchain diff, and leaving it out would lose
|
|
184
|
+
* the label and the vault the node belongs to.
|
|
185
|
+
*
|
|
186
|
+
* `address` above all. It is what the pull resolved the label to, and the only
|
|
187
|
+
* thing that says which account this is: a node without one is a creation, and
|
|
188
|
+
* its address is derived from whoever deploys. Drop it and a second deployer
|
|
189
|
+
* provisions a duplicate under the same label rather than reconfiguring the
|
|
190
|
+
* account that is already there.
|
|
191
|
+
*/
|
|
192
|
+
const IDENTITY_FIELDS = new Set([
|
|
193
|
+
"type",
|
|
194
|
+
"chain",
|
|
195
|
+
"address",
|
|
196
|
+
"label",
|
|
197
|
+
"vault"
|
|
198
|
+
]);
|
|
154
199
|
function nodeToSpec(node, refs) {
|
|
155
|
-
const { id, _constellation, ...rest } = node;
|
|
200
|
+
const { id, _constellation, _declared, ...rest } = node;
|
|
201
|
+
const declared = Array.isArray(_declared) ? new Set(_declared) : null;
|
|
156
202
|
const spec = {};
|
|
157
203
|
for (const [key, value] of Object.entries(rest)) {
|
|
204
|
+
if (declared != null && !IDENTITY_FIELDS.has(key) && !declared.has(key)) continue;
|
|
158
205
|
if (node.type === "ROLES" && key === "roles" && value != null) {
|
|
159
206
|
spec.roles = describeRoles(value, node.chain, refs);
|
|
160
207
|
continue;
|
package/dist/index.mjs.map
CHANGED
|
@@ -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 '@zodiaceco/api-types'\nimport type { PermissionEntry } from './actions'\nimport type { AllowanceSpec } from './types'\nimport { createRequire } from 'module'\nimport { resolveZodiacDir } from './paths'\nimport { UUID } from 'crypto'\n\n/**\n * A role definition keyed by role name. Entries describe what the role may do —\n * parameters and labels, never compiled permissions. They are expanded into\n * `{ targets, annotations }` when the constellation is deployed, so that a\n * stored revision always compiles through the current compilers.\n */\nexport type RoleDef = {\n members: readonly AddressOrRef[]\n permissions: readonly PermissionEntry[]\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. */\nexport type NodeRef = Readonly<{\n type: NodeType\n label: string\n chain: ChainId\n}>\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 or bound by address), absent for new\n * nodes. Accepts checksummed or lowercase; normalized to lowercase on push. */\n address?: 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. Pass `null` for a key to\n * clear that role; unmentioned roles are left untouched. */\n roles?: Record<string, RoleDef | null>\n /** Spending allowances configured on this modifier, keyed by name. Pass\n * `null` for a key to clear that allowance; unmentioned allowances are\n * left untouched. */\n allowances?: Record<string, AllowanceSpec | null>\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\n/** Configuration shared by both ways of declaring a roles modifier. */\ntype RolesConfig = {\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. Pass `null` for a key to\n * clear that role; unmentioned roles are left untouched. */\n roles?: Record<string, RoleDef | null>\n /** Spending allowances to configure on this modifier, keyed by name. Pass\n * `null` for a key to clear that allowance; unmentioned allowances are\n * left untouched. */\n allowances?: Record<string, AllowanceSpec | null>\n}\n\n/** Declare a brand-new roles modifier (address derived via CREATE2 from `nonce`). */\ntype NewRolesByNonce = RolesConfig & {\n /** Deployment nonce for CREATE2 address derivation. */\n nonce: bigint\n}\n\n/** Bind to a roles modifier already deployed on-chain at a known address, to\n * reconfigure its roles/allowances. Use this for mods not (yet) imported into\n * the workspace — pass the address instead of a deployment `nonce`. */\ntype ExistingRolesByAddress = RolesConfig & {\n /** Address of the existing Roles mod to bind to and reconfigure. */\n address: Address\n}\n\ntype NewRolesProps = NewRolesByNonce | ExistingRolesByAddress\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 (<P extends NP>(\n props: P\n ) => Readonly<Prettify<P & { 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 ChainId,\n} from '@zodiaceco/api-types'\nimport { invariant } from '@epic-web/invariant'\nimport {\n assertRecipientChains,\n isDeFiKitEntry,\n toAnnotation,\n} from './permissionEntries'\nimport { ApiClient } from './api'\nimport type {\n ConstellationMeta,\n ConstellationNode,\n ConstellationNodeInternal,\n RoleDef,\n} from './constellation'\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 = groupNodes.map((n) => nodeToSpec(n, refs))\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 invariant(\n /^[a-z0-9_]+$/.test(ref),\n `Invalid ref \"${ref}\": refs must contain only lowercase letters, numbers, or underscores`\n )\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\nfunction nodeToSpec(\n node: ConstellationNodeInternal,\n refs: RefsIndex\n): 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 = describeRoles(\n value as Record<string, RoleDef | null>,\n node.chain,\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\n/**\n * Entries travel to the API as they were written — parameters and labels — and\n * are compiled when the constellation is deployed. Only a DeFi Kit entry is\n * rendered here, into the annotation uri that names the chain it was written\n * for; the permissions behind it are fetched from that uri at deploy.\n */\nfunction describeRoles(\n roles: Record<string, RoleDef | null>,\n chain: ChainId,\n refs: RefsIndex\n): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(roles).map(([key, def]) => {\n if (def == null) {\n return [key, null] as const\n }\n\n return [\n key,\n {\n key,\n members: resolveRefs(def.members, refs),\n permissions: def.permissions.map((entry) => {\n assertRecipientChains(entry, chain)\n\n return resolveRefs(\n isDeFiKitEntry(entry)\n ? { label: entry.label, annotation: toAnnotation(entry, chain) }\n : entry,\n refs\n )\n }),\n },\n ] as const\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 but 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 '@zodiaceco/api-types'\nimport type { PermissionEntry } from './actions'\nimport type { AllowanceSpec } from './types'\nimport { createRequire } from 'module'\nimport { resolveZodiacDir } from './paths'\nimport { UUID } from 'crypto'\n\n/**\n * A role definition keyed by role name. Entries describe what the role may do —\n * parameters and labels, never compiled permissions. They are expanded into\n * `{ targets, annotations }` when the constellation is deployed, so that a\n * stored revision always compiles through the current compilers.\n */\nexport type RoleDef = {\n members: readonly AddressOrRef[]\n permissions: readonly PermissionEntry[]\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\ntype AccountsByLabel = Readonly<Record<string, Account>>\n\n/**\n * A workspace's accounts, grouped by node type and then by chain.\n *\n * Both groupings keep bracket-accessor namespaces apart, so that a label is\n * only ever ambiguous between accounts a constellation could actually choose\n * between: a SAFE and a ROLES mod sharing a label don't collide, and neither\n * do two safes of the same name on different chains.\n */\ntype WorkspaceAccounts = {\n workspaceId: UUID\n workspaceName: string\n safes: AccountsByChain\n rolesMods: AccountsByChain\n delays: AccountsByChain\n}\n\ntype AccountsByChain = { readonly [chain in ChainId]?: AccountsByLabel }\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\n/**\n * The accounts of one type that a constellation on `Ch` can name. Accounts on\n * every other chain are not in scope: their addresses mean nothing here, so\n * offering them would only invite a node that carries a foreign address under\n * this constellation's chain.\n */\ntype ChainEntries<A, Ch extends ChainId> = Ch extends keyof A\n ? NonNullable<A[Ch]>\n : {}\n\ntype SafeEntries<\n C extends CodegenData,\n W extends keyof C['accounts'],\n Ch extends ChainId,\n> = ChainEntries<C['accounts'][W]['safes'], Ch>\n\ntype RolesEntries<\n C extends CodegenData,\n W extends keyof C['accounts'],\n Ch extends ChainId,\n> = ChainEntries<C['accounts'][W]['rolesMods'], Ch>\n\ntype NodeType = 'SAFE' | 'ROLES' | 'DELAY'\n\n/** A reference to a node used in `owners`, `modules`, `target`, etc. */\nexport type NodeRef = Readonly<{\n type: NodeType\n label: string\n chain: ChainId\n}>\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 or bound by address), absent for new\n * nodes. Accepts checksummed or lowercase; normalized to lowercase on push. */\n address?: Address\n /** Deployment nonce — required for new nodes, optional for existing. */\n nonce?: bigint\n}>\n\n/** A safe node — a reference to one the workspace already has, or a new one\n * with the config it is deployed with. */\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 — a reference to one the workspace already has, or a\n * new one with the config it is deployed with. */\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. Pass `null` for a key to\n * clear that role; unmentioned roles are left untouched. */\n roles?: Record<string, RoleDef | null>\n /** Spending allowances configured on this modifier, keyed by name. Pass\n * `null` for a key to clear that allowance; unmentioned allowances are\n * left untouched. */\n allowances?: Record<string, AllowanceSpec | null>\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 * The state fields this node actually declares.\n *\n * A referenced node carries everything `pull` read from chain so it reads\n * well and completes in an editor, but a reference is not a declaration —\n * only what was passed explicitly is pushed. Empty for a bare reference.\n */\n _declared: readonly string[]\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\n/** Configuration shared by both ways of declaring a roles modifier. */\ntype RolesConfig = {\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. Pass `null` for a key to\n * clear that role; unmentioned roles are left untouched. */\n roles?: Record<string, RoleDef | null>\n /** Spending allowances to configure on this modifier, keyed by name. Pass\n * `null` for a key to clear that allowance; unmentioned allowances are\n * left untouched. */\n allowances?: Record<string, AllowanceSpec | null>\n}\n\n/** Declare a brand-new roles modifier (address derived via CREATE2 from `nonce`). */\ntype NewRolesByNonce = RolesConfig & {\n /** Deployment nonce for CREATE2 address derivation. */\n nonce: bigint\n}\n\n/** Bind to a roles modifier already deployed on-chain at a known address, to\n * reconfigure its roles/allowances. Use this for mods not (yet) imported into\n * the workspace — pass the address instead of a deployment `nonce`. */\ntype ExistingRolesByAddress = RolesConfig & {\n /** Address of the existing Roles mod to bind to and reconfigure. */\n address: Address\n}\n\ntype NewRolesProps = NewRolesByNonce | ExistingRolesByAddress\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 const 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 // `const` so a node reads back the values it was written with — `threshold: 2`\n // rather than `number`. Every prop it widens into is already `readonly`, so\n // the tuples this infers stay assignable.\n (<const P extends NP>(\n props: P\n ) => Readonly<Prettify<P & { 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>, 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>, 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 * Bracket access names an account of that workspace **on that chain** — a\n * vault, or a node an earlier constellation deployed. A label the chain has no\n * account for reads as a new node instead, so it has to be given the config it\n * is deployed with.\n *\n * ```ts\n * const eth = constellation({ workspace: 'GG', label: 'my constellation', chain: 1 })\n *\n * const dao = eth.safe['GG DAO'] // the safe of that name on chain 1\n * const roles = eth.roles['GG DAO'] // the roles mod of that name\n * const newSafe = eth.safe['New Safe']({ nonce: 0n, threshold: 2, owners: [...], modules: [...] })\n * ```\n *\n * Names come from the last `pull-org`, so an account deployed since then is\n * not one yet.\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 // Only this chain's accounts are in scope. A label resolved from another\n // chain would hand back an address that names nothing here, and the node\n // would still be pushed under this constellation's chain.\n const onThisChain = (byChain: AccountsByChain): AccountsByLabel =>\n byChain[opts.chain] ?? {}\n\n for (const [label, account] of Object.entries(onThisChain(ws.safes))) {\n safesByLabel[label] = account\n }\n for (const [label, account] of Object.entries(onThisChain(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 declared: readonly string[]\n ): Readonly<Record<string, any>> {\n return Object.freeze({\n ...data,\n chain: opts.chain,\n _constellation: meta,\n _declared: declared,\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 {\n type,\n ...(existing || {}),\n ...overrides,\n label: specLabel,\n },\n Object.keys(overrides ?? {})\n )\n // Bare access — `eth.safe['Treasury']` rather than a call — declares\n // nothing. It reads as the account it names, and pushes as a reference.\n Object.assign(fn, {\n type,\n ...(existing || {}),\n label: specLabel,\n chain: opts.chain,\n _constellation: meta,\n _declared: [],\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 ChainId,\n} from '@zodiaceco/api-types'\nimport { invariant } from '@epic-web/invariant'\nimport {\n assertRecipientChains,\n isDeFiKitEntry,\n toAnnotation,\n} from './permissionEntries'\nimport { ApiClient } from './api'\nimport type {\n ConstellationMeta,\n ConstellationNode,\n ConstellationNodeInternal,\n RoleDef,\n} from './constellation'\n\ntype PushOpts = {\n /** API client instance. Defaults to a client configured from environment variables. */\n api?: ApiClient\n}\n\n/**\n * Applies the given nodes as the next revision of the constellations they\n * belong to, and answers with a url per constellation to review and deploy.\n *\n * Each node travels with what it was actually given, never with the onchain\n * state a `pull` filled it with for reading — so a node passed as a bare\n * reference declares nothing and leaves the account it names alone. Nodes\n * naming each other travel as references and are substituted for addresses\n * once those are derived at deploy.\n *\n * Passing an object names each node by its key; passing an array names them by\n * position. Either way the name is the node's `ref`, which is how the app\n * follows a node from one revision to the next.\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 = groupNodes.map((n) => nodeToSpec(n, refs))\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\n/**\n * What a ref may be spelled like. Refs come from export names and object keys,\n * so any identifier a node can be declared under has to survive the push —\n * except one opening with `$`, which is how a node references another. A list\n * of nodes refs them by position, so a plain index counts too.\n */\nconst IDENTIFIER = /^(?:[a-zA-Z_][a-zA-Z0-9_$]*|\\d+)$/\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 invariant(\n IDENTIFIER.test(ref),\n `Invalid ref \"${ref}\": a ref is a JavaScript identifier, and cannot start with \"$\"`\n )\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\n/**\n * What situates a node rather than configures it: which account it is, and how\n * the workspace holds it. Always sent, even for a reference that declares\n * nothing — none of it reaches the onchain diff, and leaving it out would lose\n * the label and the vault the node belongs to.\n *\n * `address` above all. It is what the pull resolved the label to, and the only\n * thing that says which account this is: a node without one is a creation, and\n * its address is derived from whoever deploys. Drop it and a second deployer\n * provisions a duplicate under the same label rather than reconfiguring the\n * account that is already there.\n */\nconst IDENTITY_FIELDS = new Set(['type', 'chain', 'address', 'label', 'vault'])\n\nfunction nodeToSpec(\n node: ConstellationNodeInternal,\n refs: RefsIndex\n): ApplyConstellationPayload['specification'][number] {\n const { id, _constellation, _declared, ...rest } = node as Record<string, any>\n // A node built by hand rather than through an accessor says nothing about\n // what it declares, so there is nothing to go on and everything travels.\n const declared = Array.isArray(_declared) ? new Set<string>(_declared) : null\n const spec: Record<string, any> = {}\n\n for (const [key, value] of Object.entries(rest)) {\n // A referenced node carries the state `pull` read from chain. Sending it\n // back would declare it: an owner added since the pull would be removed\n // again by the deployment, without anyone having written that down. So\n // state travels only when this node actually declares it.\n if (declared != null && !IDENTITY_FIELDS.has(key) && !declared.has(key)) {\n continue\n }\n\n if (node.type === 'ROLES' && key === 'roles' && value != null) {\n spec.roles = describeRoles(\n value as Record<string, RoleDef | null>,\n node.chain,\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\n/**\n * Entries travel to the API as they were written — parameters and labels — and\n * are compiled when the constellation is deployed. Only a DeFi Kit entry is\n * rendered here, into the annotation uri that names the chain it was written\n * for; the permissions behind it are fetched from that uri at deploy.\n */\nfunction describeRoles(\n roles: Record<string, RoleDef | null>,\n chain: ChainId,\n refs: RefsIndex\n): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(roles).map(([key, def]) => {\n if (def == null) {\n return [key, null] as const\n }\n\n return [\n key,\n {\n key,\n members: resolveRefs(def.members, refs),\n permissions: def.permissions.map((entry) => {\n assertRecipientChains(entry, chain)\n\n return resolveRefs(\n isDeFiKitEntry(entry)\n ? { label: entry.label, annotation: toAnnotation(entry, chain) }\n : entry,\n refs\n )\n }),\n },\n ] as const\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 but 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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zodiaceco/sdk",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.3",
|
|
4
4
|
"author": "Zodiac Labs GmbH",
|
|
5
5
|
"license": "LGPL-3.0-only",
|
|
6
6
|
"homepage": "https://github.com/gnosisguild/zodiac-sdk",
|
|
@@ -51,13 +51,13 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@epic-web/invariant": "1.0.0",
|
|
54
|
-
"@zodiaceco/api-types": "^1.9.
|
|
54
|
+
"@zodiaceco/api-types": "^1.9.2",
|
|
55
55
|
"commander": "^14.0.3",
|
|
56
56
|
"defi-kit": "^2.26.12",
|
|
57
57
|
"dotenv": "^17.4.2",
|
|
58
58
|
"open": "^11.0.0",
|
|
59
59
|
"ts-morph": "^27.0.2",
|
|
60
|
-
"zodiac-roles-sdk": "^4.
|
|
60
|
+
"zodiac-roles-sdk": "^4.1.3"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"ethers": "^6.13.0"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"api-C49DoaPC.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 '@zodiaceco/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 DEFAULT_API_URL = 'https://app.zodiac.eco/api/v1'\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 // Read `ZODIAC_API_URL`/`ZODIAC_API_KEY` lazily at construction time\n // rather than at module load: the CLI loads `.env` (and `init` writes a\n // fresh one) before the first client is created, so capturing them at\n // import time would bake in the default and ignore the user's config.\n baseUrl = process.env.ZODIAC_API_URL ?? DEFAULT_API_URL,\n fetch: customFetch = fetch,\n headers = {},\n apiKey = process.env.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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"}
|