@zodiac-os/sdk 1.1.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,18 +1,179 @@
1
1
  # Zodiac OS SDK
2
2
 
3
- Programmatically manage Safe + Zodiac account constellations with [Zodiac OS](https://pilot.gnosisguild.org)
3
+ Programmatically manage [Zodiac](https://www.zodiac.eco) account constellations.
4
4
 
5
5
  ## Getting started
6
6
 
7
- ### Generate a Zodiac OS API key
7
+ ### 1. Install
8
8
 
9
- Sign in to https://app.pilot.gnosisguild.org and create an API key at https://app.pilot.gnosisguild.org/admin/api-keys
9
+ ```bash
10
+ npm install @zodiac-os/sdk
11
+ ```
10
12
 
11
- ## API Client options
13
+ ### 2. Generate a Zodiac OS API key
12
14
 
13
- | Option | Default | Description |
14
- | ----------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
15
- | `apiKey` | `ZODIAC_OS_API_KEY` | Api key you created to access your Zodiac OS workspace. Can either be specified directly in code or through the `ZODIAC_OS_API_KEY` environment variable |
16
- | `workspace` | `ZODIAC_OS_WORKSPACE` | The Zodiac OS workspace you want to access with the client. Can also be specified via the `ZODIAC_OS_WORKSPACE` environment variable. |
17
- | `baseUrl` | `https://app.pilor.gnosisguild.org/api/v1` | The URL the API client will be pointed at. This can also be specified via the `ZODIAC_OS_API_URL` environment variable. You probably do not need to specify this. |
18
- | `fetch` | `global.fetch` | The `fetch` client that is used by the API client. You probably do not need to specify this. However, this can be useful for testing. |
15
+ Sign in to [app.zodiac.eco](https://app.zodiac.eco) and create an API key at [app.zodiac.eco/admin/api-keys](https://app.zodiac.eco/admin/api-keys).
16
+
17
+ ### 3. Create a config file
18
+
19
+ Create a `zodiac.config.ts` in your project root:
20
+
21
+ ```ts
22
+ import { defineConfig } from '@zodiac-os/sdk/cli/config'
23
+
24
+ export default defineConfig({
25
+ apiKey: 'zodiac_...',
26
+ // Optional: contracts to fetch for permissions authoring
27
+ contracts: {
28
+ mainnet: {
29
+ dai: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
30
+ },
31
+ },
32
+ })
33
+ ```
34
+
35
+ ### 4. Pull your org data
36
+
37
+ ```bash
38
+ # Pull everything (org data + contract ABIs)
39
+ zodiac-os pull
40
+ ```
41
+
42
+ This generates typed data in `node_modules/.zodiac-os/` with your org's users and vaults.
43
+
44
+ ## Constellation API
45
+
46
+ 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.
47
+
48
+ ```ts
49
+ import { constellation } from '@zodiac-os/sdk'
50
+ ```
51
+
52
+ ### Scoping to a workspace and chain
53
+
54
+ Each constellation is scoped to a single workspace and chain. The `workspace` option must be a valid workspace name from your org.
55
+
56
+ ```ts
57
+ const eth = constellation({
58
+ workspace: 'GG',
59
+ label: 'Production',
60
+ chain: 1,
61
+ })
62
+ ```
63
+
64
+ ### Referencing existing vaults
65
+
66
+ Bracket access gives you existing Safes and Roles mods from the selected workspace. Names auto-complete from the codegen output.
67
+
68
+ ```ts
69
+ // Reference an existing Safe — no invocation needed
70
+ const ggDao = eth.safe['GG DAO']
71
+
72
+ // Reference the canonical Roles mod for that Safe
73
+ const ggDaoRoles = eth.roles['GG DAO']
74
+
75
+ // Optionally invoke with overrides
76
+ const ggDaoOverridden = eth.safe['GG DAO']({ threshold: 5 })
77
+ ```
78
+
79
+ ### Creating new accounts
80
+
81
+ Use bracket access with a new label to create new nodes. Required fields are enforced by the type system:
82
+
83
+ ```ts
84
+ // New Safe — threshold, owners are required
85
+ const newSafe = eth.safe['New Safe']({
86
+ nonce: 0n,
87
+ threshold: 2,
88
+ owners: [
89
+ eth.user['Alice Sample'],
90
+ '0xb8e48df6818d3cbc648b3e8ec248a4f547135f7a',
91
+ ],
92
+ modules: [ggDaoRoles],
93
+ })
94
+
95
+ // New Roles mod targeting an existing Safe
96
+ const newRoles = eth.roles['New Roles']({
97
+ nonce: 0n,
98
+ target: ggDao,
99
+ })
100
+ ```
101
+
102
+ ### Canonical Roles mods
103
+
104
+ Every Safe has a canonical Roles mod hosting policies applied through the app. When you use the Safe label on the `roles` accessor, it resolves to that Safe's canonical Roles mod automatically.
105
+
106
+ ```ts
107
+ // Enable roles on an existing Safe in your org
108
+ const daoRoles = eth.roles['GG DAO']({
109
+ roles: [
110
+ /* ... */
111
+ ],
112
+ })
113
+ ```
114
+
115
+ ### Circular references between new nodes
116
+
117
+ New nodes can reference each other before either has been invoked — use the uninvoked factory as a forward reference:
118
+
119
+ ```ts
120
+ const safe = eth.safe['New Safe']({
121
+ nonce: 0n,
122
+ threshold: 1,
123
+ owners: [eth.user['Alice Sample']],
124
+ // Forward reference to a Roles mod that doesn't exist yet
125
+ modules: [eth.roles['New Roles']],
126
+ })
127
+
128
+ const roles = eth.roles['New Roles']({
129
+ nonce: 0n,
130
+ target: safe,
131
+ })
132
+ ```
133
+
134
+ References are resolved by label at `apply()` time, so both sides of the cycle must be included in the call.
135
+
136
+ ### Referencing users
137
+
138
+ `eth.user[handle]` resolves a user to their personal Safe address on the current chain:
139
+
140
+ ```ts
141
+ const aliceAddress = eth.user['Alice Sample']
142
+ ```
143
+
144
+ ### Applying the constellation
145
+
146
+ The `apply()` function takes all nodes and sends them to the Zodiac OS API. Pass either a named object (keys become refs) or an array:
147
+
148
+ ```ts
149
+ import { apply } from '@zodiac-os/sdk'
150
+
151
+ await apply({ ggDao, ggDaoRoles, newSafe, newRoles })
152
+ ```
153
+
154
+ All referenced nodes must be included in the `apply()` call.
155
+
156
+ By default, `apply()` creates an API client from the `ZODIAC_OS_API_KEY` environment variable. You can pass a custom client:
157
+
158
+ ```ts
159
+ await apply({ ggDao, newRoles }, { api: new ApiClient({ apiKey: '...' }) })
160
+ ```
161
+
162
+ ## CLI reference
163
+
164
+ ```
165
+ Usage: zodiac-os [options] [command]
166
+
167
+ Zodiac OS SDK CLI – pull org data and contract ABIs
168
+
169
+ Options:
170
+ -V, --version output the version number
171
+ -c, --config <path> path to the config file (default: "zodiac.config.ts")
172
+ -h, --help display help for command
173
+
174
+ Commands:
175
+ pull-org Fetch Zodiac users and vaults, generate TypeScript types
176
+ pull-contracts Fetch contract ABIs, generate typed permissions kit
177
+ pull Fetch Zodiac org and contracts ABI, generate SDK functions
178
+ help [command] display help for command
179
+ ```
@@ -0,0 +1,2 @@
1
+ import { a as EVERYTHING, c as Scoping, i as ConditionFunction, l as TargetPermission, n as ChainPrefix, o as FunctionPermission, r as chainIdFor, s as Options, t as CHAIN_IDS, u as buildAllowKit } from "../index-DTBaxN7b.mjs";
2
+ export { CHAIN_IDS, ChainPrefix, ConditionFunction, EVERYTHING, FunctionPermission, Options, Scoping, TargetPermission, buildAllowKit, chainIdFor };
@@ -0,0 +1,3 @@
1
+ import { n as chainIdFor, t as CHAIN_IDS } from "../networks-BTW1qAAa.mjs";
2
+ import { n as EVERYTHING, t as buildAllowKit } from "../allow-Dzh6t_l8.mjs";
3
+ export { CHAIN_IDS, EVERYTHING, buildAllowKit, chainIdFor };
@@ -0,0 +1,122 @@
1
+ import { a as walkContracts, i as readAbi } from "./networks-BTW1qAAa.mjs";
2
+ import { c, coercePermission } from "zodiac-roles-sdk";
3
+ import { Interface, isError } from "ethers";
4
+ import { Operator, ParameterType } from "zodiac-roles-deployments";
5
+ //#region src/allow/types.ts
6
+ const EVERYTHING = Symbol.for("@zodiac-os/allow-kit/EVERYTHING");
7
+ //#endregion
8
+ //#region src/allow/runtime.ts
9
+ function buildAllowKit(abisDir, contractsConfig) {
10
+ const kit = {};
11
+ for (const node of walkContracts(contractsConfig)) {
12
+ const abi = readAbi(abisDir, node);
13
+ if (!abi) {
14
+ attachAt(kit, [node.chain, ...node.segments], missingAbiProxy(node));
15
+ continue;
16
+ }
17
+ attachAt(kit, [node.chain, ...node.segments], makeAllowContract(node.address, abi));
18
+ }
19
+ return kit;
20
+ }
21
+ function attachAt(root, segments, value) {
22
+ let cursor = root;
23
+ for (let i = 0; i < segments.length - 1; i++) {
24
+ const seg = segments[i];
25
+ if (!(seg in cursor)) cursor[seg] = {};
26
+ cursor = cursor[seg];
27
+ }
28
+ cursor[segments[segments.length - 1]] = value;
29
+ }
30
+ function missingAbiProxy(node) {
31
+ const explain = () => {
32
+ throw new Error(`ABI missing for ${node.chain}.${node.segments.join(".")} (${node.address}). Run \`zodiac-os pull-contracts\` to fetch it, or paste the ABI JSON manually at <abisDir>/${node.chain}/${node.segments.join("/")}.json`);
33
+ };
34
+ return new Proxy({}, {
35
+ get: explain,
36
+ has: explain
37
+ });
38
+ }
39
+ function makeAllowContract(address, abi) {
40
+ const iface = Interface.from(abi);
41
+ const lowerAddr = address.toLowerCase();
42
+ const allowEverything = (options) => ({
43
+ targetAddress: lowerAddr,
44
+ send: options?.send,
45
+ delegatecall: options?.delegatecall
46
+ });
47
+ const has = (name) => {
48
+ try {
49
+ const fn = iface.getFunction(name);
50
+ if (!fn) return false;
51
+ return fn.stateMutability !== "view" && fn.stateMutability !== "pure";
52
+ } catch (error) {
53
+ if (!isError(error, "INVALID_ARGUMENT")) throw error;
54
+ return false;
55
+ }
56
+ };
57
+ return new Proxy({}, {
58
+ get: (_target, prop) => {
59
+ if (prop === EVERYTHING) return allowEverything;
60
+ if (typeof prop !== "string") return void 0;
61
+ if (!has(prop)) return void 0;
62
+ return makeAllowFunction(iface.getFunction(prop), lowerAddr);
63
+ },
64
+ has: (_target, prop) => {
65
+ if (prop === EVERYTHING) return true;
66
+ return typeof prop === "string" && has(prop);
67
+ }
68
+ });
69
+ }
70
+ function makeAllowFunction(fn, targetAddress) {
71
+ const inputs = fn.inputs;
72
+ return (...args) => {
73
+ const scopings = args.slice(0, inputs.length);
74
+ const hasScopings = scopings.some((s) => s !== void 0 && s !== null);
75
+ const options = args[inputs.length] ?? {};
76
+ const condition = hasScopings ? c.calldataMatches(scopings, inputs)() : void 0;
77
+ return applyOptions(coercePermission({
78
+ targetAddress,
79
+ signature: fn.format("sighash"),
80
+ condition
81
+ }), options);
82
+ };
83
+ }
84
+ const emptyCalldataMatches = {
85
+ paramType: ParameterType.Calldata,
86
+ operator: Operator.Matches,
87
+ children: []
88
+ };
89
+ const applyGlobalAllowance = (condition, allowanceCondition) => {
90
+ const base = condition ?? emptyCalldataMatches;
91
+ if (base.paramType !== ParameterType.Calldata || base.operator !== Operator.Matches) throw new Error("Global allowance can only be applied to calldata matches nodes");
92
+ return {
93
+ ...base,
94
+ children: [...base.children ?? [], allowanceCondition]
95
+ };
96
+ };
97
+ const applyOptions = (permission, options) => {
98
+ let condition = permission.condition;
99
+ if (options.etherWithinAllowance) {
100
+ if (!options.send) throw new Error("`etherWithinAllowance` can only be used if `send` is allowed");
101
+ condition = applyGlobalAllowance(condition, {
102
+ paramType: ParameterType.None,
103
+ operator: Operator.EtherWithinAllowance,
104
+ compValue: options.etherWithinAllowance
105
+ });
106
+ }
107
+ if (options.callWithinAllowance) condition = applyGlobalAllowance(condition, {
108
+ paramType: ParameterType.None,
109
+ operator: Operator.CallWithinAllowance,
110
+ compValue: options.callWithinAllowance
111
+ });
112
+ return {
113
+ ...permission,
114
+ send: options.send,
115
+ delegatecall: options.delegatecall,
116
+ condition
117
+ };
118
+ };
119
+ //#endregion
120
+ export { EVERYTHING as n, buildAllowKit as t };
121
+
122
+ //# sourceMappingURL=allow-Dzh6t_l8.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allow-Dzh6t_l8.mjs","names":[],"sources":["../src/allow/types.ts","../src/allow/runtime.ts"],"sourcesContent":["import type { BigNumberish, BytesLike, ParamType } from 'ethers'\nimport type {\n Condition,\n FunctionPermission,\n TargetPermission,\n} from 'zodiac-roles-sdk'\n\nexport type Options = {\n send?: boolean\n delegatecall?: boolean\n etherWithinAllowance?: `0x${string}`\n callWithinAllowance?: `0x${string}`\n}\n\nexport type PrimitiveValue = BigNumberish | BytesLike | string | boolean\n\n// Signature matches `zodiac-roles-sdk` so values from `c.*` are assignable.\nexport type ConditionFunction<T = unknown> = (\n abiType: ParamType,\n _?: T\n) => Condition\n\ntype RequireAtLeastOne<T> = {\n [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>\n}[keyof T]\n\ntype ArrayElement<T extends readonly unknown[]> = T extends readonly (infer U)[]\n ? U\n : never\n\nexport type PrimitiveScoping<T extends PrimitiveValue> =\n | T\n | ConditionFunction<T>\n\nexport type ArrayScoping<T extends readonly any[]> =\n | readonly Scoping<ArrayElement<T>>[]\n | ConditionFunction<T>\n\nexport type StructScoping<T extends { [key: string]: any }> =\n | RequireAtLeastOne<{ [K in keyof T]?: Scoping<T[K]> }>\n | ConditionFunction<T>\n\nexport type Scoping<T> = T extends PrimitiveValue\n ? PrimitiveScoping<T>\n : T extends readonly any[]\n ? ArrayScoping<T>\n : T extends { [key: string]: any }\n ? StructScoping<T>\n : unknown\n\nexport type { FunctionPermission, TargetPermission }\n\nexport const EVERYTHING = Symbol.for('@zodiac-os/allow-kit/EVERYTHING')\nexport type EVERYTHING = typeof EVERYTHING\n","import { Interface, FunctionFragment, isError, type InterfaceAbi } from 'ethers'\nimport { c, coercePermission } from 'zodiac-roles-sdk'\nimport type {\n Condition,\n FunctionPermission,\n TargetPermission,\n} from 'zodiac-roles-sdk'\nimport { ParameterType, Operator } from 'zodiac-roles-deployments'\nimport { readAbi, walkContracts, type ContractNode } from './abi'\nimport { EVERYTHING, type Options } from './types'\n\nexport function buildAllowKit(\n abisDir: string,\n contractsConfig: Record<string, any>\n): Record<string, any> {\n const kit: Record<string, any> = {}\n for (const node of walkContracts(contractsConfig)) {\n const abi = readAbi(abisDir, node)\n if (!abi) {\n // Defer the error until the user touches this contract — otherwise an\n // ABI missing for one chain crashes all unrelated role definitions.\n attachAt(kit, [node.chain, ...node.segments], missingAbiProxy(node))\n continue\n }\n attachAt(\n kit,\n [node.chain, ...node.segments],\n makeAllowContract(node.address, abi as InterfaceAbi)\n )\n }\n return kit\n}\n\nfunction attachAt(root: Record<string, any>, segments: string[], value: any) {\n let cursor = root\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i]!\n if (!(seg in cursor)) cursor[seg] = {}\n cursor = cursor[seg]\n }\n cursor[segments[segments.length - 1]!] = value\n}\n\nfunction missingAbiProxy(node: ContractNode) {\n const explain = () => {\n throw new Error(\n `ABI missing for ${node.chain}.${node.segments.join('.')} ` +\n `(${node.address}). Run \\`zodiac-os pull-contracts\\` to fetch it, or ` +\n `paste the ABI JSON manually at <abisDir>/${node.chain}/${node.segments.join('/')}.json`\n )\n }\n return new Proxy(\n {},\n {\n get: explain,\n has: explain,\n }\n )\n}\n\nfunction makeAllowContract(\n address: `0x${string}`,\n abi: InterfaceAbi\n): Record<string | symbol, any> {\n const iface = Interface.from(abi)\n const lowerAddr = address.toLowerCase() as `0x${string}`\n\n const allowEverything = (options?: Options): TargetPermission => ({\n targetAddress: lowerAddr,\n send: options?.send,\n delegatecall: options?.delegatecall,\n })\n\n const has = (name: string) => {\n try {\n const fn = iface.getFunction(name)\n if (!fn) return false\n return fn.stateMutability !== 'view' && fn.stateMutability !== 'pure'\n } catch (error) {\n if (!isError(error as any, 'INVALID_ARGUMENT')) throw error\n return false\n }\n }\n\n return new Proxy(\n {},\n {\n get: (_target, prop) => {\n if (prop === EVERYTHING) return allowEverything\n if (typeof prop !== 'string') return undefined\n if (!has(prop)) return undefined\n const fn = iface.getFunction(prop)!\n return makeAllowFunction(fn, lowerAddr)\n },\n has: (_target, prop) => {\n if (prop === EVERYTHING) return true\n return typeof prop === 'string' && has(prop)\n },\n }\n )\n}\n\nfunction makeAllowFunction(\n fn: FunctionFragment,\n targetAddress: `0x${string}`\n): (...args: any[]) => FunctionPermission {\n const inputs = fn.inputs\n return (...args: any[]) => {\n const scopings = args.slice(0, inputs.length)\n const hasScopings = scopings.some((s) => s !== undefined && s !== null)\n const options: Options = args[inputs.length] ?? {}\n const condition = hasScopings\n ? c.calldataMatches(scopings, inputs)()\n : undefined\n const preset = {\n targetAddress,\n signature: fn.format('sighash'),\n condition,\n }\n return applyOptions(coercePermission(preset as any) as any, options)\n }\n}\n\nconst emptyCalldataMatches: Condition = {\n paramType: ParameterType.Calldata,\n operator: Operator.Matches,\n children: [],\n}\n\nconst applyGlobalAllowance = (\n condition: Condition | undefined,\n allowanceCondition: Condition\n): Condition => {\n const base = condition ?? emptyCalldataMatches\n if (\n base.paramType !== ParameterType.Calldata ||\n base.operator !== Operator.Matches\n ) {\n throw new Error(\n 'Global allowance can only be applied to calldata matches nodes'\n )\n }\n return {\n ...base,\n children: [...(base.children ?? []), allowanceCondition],\n }\n}\n\nconst applyOptions = (\n permission: FunctionPermission & { condition?: Condition },\n options: Options\n): FunctionPermission => {\n let condition = permission.condition\n if (options.etherWithinAllowance) {\n if (!options.send) {\n throw new Error(\n '`etherWithinAllowance` can only be used if `send` is allowed'\n )\n }\n condition = applyGlobalAllowance(condition, {\n paramType: ParameterType.None,\n operator: Operator.EtherWithinAllowance,\n compValue: options.etherWithinAllowance,\n })\n }\n if (options.callWithinAllowance) {\n condition = applyGlobalAllowance(condition, {\n paramType: ParameterType.None,\n operator: Operator.CallWithinAllowance,\n compValue: options.callWithinAllowance,\n })\n }\n return {\n ...permission,\n send: options.send,\n delegatecall: options.delegatecall,\n condition,\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,113 @@
1
+ import assert from "assert";
2
+ //#region src/api.ts
3
+ const { ZODIAC_OS_API_KEY, ZODIAC_OS_API_URL = "https://app.zodiac.eco/api/v1" } = process.env;
4
+ var ApiClient = class {
5
+ apiKey;
6
+ baseUrl;
7
+ _fetch;
8
+ headers;
9
+ constructor({ baseUrl = ZODIAC_OS_API_URL, fetch: customFetch = fetch, headers = {}, apiKey = ZODIAC_OS_API_KEY } = {}) {
10
+ this.baseUrl = baseUrl.replace(/\/$/, "");
11
+ this._fetch = customFetch;
12
+ this.headers = headers;
13
+ assert(apiKey, "No API key provided to the API client. Either pass it as the \"apiKey\" option or set the ZODIAC_OS_API_KEY environment variable.");
14
+ this.apiKey = apiKey;
15
+ }
16
+ async postJson(endpoint, payload) {
17
+ const res = await this._fetch(`${this.baseUrl}/${endpoint}`, {
18
+ method: "POST",
19
+ headers: {
20
+ ...this.headers,
21
+ "content-type": "application/json",
22
+ authorization: `Bearer ${this.apiKey}`
23
+ },
24
+ body: jsonStringify(payload)
25
+ });
26
+ if (!res.ok) await handleApiError(res);
27
+ return res.json();
28
+ }
29
+ async get(endpoint) {
30
+ const res = await this._fetch(`${this.baseUrl}/${endpoint}`, { headers: {
31
+ ...this.headers,
32
+ authorization: `Bearer ${this.apiKey}`
33
+ } });
34
+ if (!res.ok) await handleApiError(res);
35
+ return res.json();
36
+ }
37
+ listVaults() {
38
+ return this.get("vaults");
39
+ }
40
+ listUsers() {
41
+ return this.get("users");
42
+ }
43
+ /**
44
+ * Applies an accounts specification to Zodiac OS.
45
+ */
46
+ applyConstellation(workspaceId, payload) {
47
+ return this.postJson(`workspace/${workspaceId}/constellation/apply`, payload);
48
+ }
49
+ /**
50
+ * Resolves an accounts specification to Zodiac OS.
51
+ */
52
+ resolveConstellation(workspaceId, payload) {
53
+ return this.postJson(`workspace/${workspaceId}/constellation/resolve`, payload);
54
+ }
55
+ };
56
+ var ApiRequestError = class ApiRequestError extends Error {
57
+ status;
58
+ statusText;
59
+ details;
60
+ constructor(message, opts) {
61
+ super(ApiRequestError.composeMessage(message, opts.details));
62
+ this.name = "ApiRequestError";
63
+ this.status = opts.status;
64
+ this.statusText = opts.statusText;
65
+ this.details = opts.details;
66
+ if (opts.cause !== void 0) this.cause = opts.cause;
67
+ }
68
+ static composeMessage(message, details) {
69
+ if (details == null) return message;
70
+ let detailsString;
71
+ try {
72
+ detailsString = typeof details === "string" ? details : jsonStringify(details, 2);
73
+ } catch (_err) {
74
+ detailsString = String(details);
75
+ }
76
+ return `${message}\nDetails: ${detailsString}`;
77
+ }
78
+ toString() {
79
+ return `${this.name}: ${this.message}`;
80
+ }
81
+ };
82
+ async function handleApiError(response) {
83
+ if (response.headers.get("content-type")?.includes("application/json")) {
84
+ const errorData = await response.json();
85
+ let error;
86
+ try {
87
+ error = new ApiRequestError(errorData.error.message, {
88
+ status: response.status,
89
+ statusText: response.statusText,
90
+ details: errorData.error.details
91
+ });
92
+ } catch (jsonShapeError) {
93
+ error = new ApiRequestError(`Failed parsing error response: ${jsonShapeError}`, {
94
+ status: response.status,
95
+ statusText: response.statusText,
96
+ details: errorData
97
+ });
98
+ }
99
+ throw error;
100
+ } else throw new ApiRequestError(await response.text() || "Unexpected error", {
101
+ status: response.status,
102
+ statusText: response.statusText
103
+ });
104
+ }
105
+ /** JSON.stringify with bigint support */
106
+ const jsonStringify = (value, indent) => JSON.stringify(value, (_, value) => {
107
+ if (typeof value === "bigint") return value.toString();
108
+ return value;
109
+ }, indent);
110
+ //#endregion
111
+ export { ApiClient as t };
112
+
113
+ //# sourceMappingURL=api-D6ee2Q2b.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-D6ee2Q2b.mjs","names":[],"sources":["../src/api.ts"],"sourcesContent":["import type {\n ApplyConstellationPayload,\n ApplyConstellationResult,\n ResolveConstellationPayload,\n ResolveConstellationResult,\n ApiError as ApiErrorResponse,\n ListVaultsResult,\n ListUsersResult,\n} from '@zodiac-os/api-types'\nimport assert from 'assert'\nimport { UUID } from 'crypto'\n\nexport type Options = {\n workspace?: string\n apiKey?: string\n baseUrl?: string\n fetch?: typeof globalThis.fetch\n headers?: Record<string, string>\n}\n\nconst {\n ZODIAC_OS_API_KEY,\n ZODIAC_OS_API_URL = 'https://app.zodiac.eco/api/v1',\n} = process.env\n\nexport class ApiClient {\n private apiKey: string\n private baseUrl: string\n private _fetch: typeof fetch\n private headers: Record<string, string>\n\n constructor({\n baseUrl = ZODIAC_OS_API_URL,\n fetch: customFetch = fetch,\n headers = {},\n apiKey = ZODIAC_OS_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_OS_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 listVaults(): Promise<ListVaultsResult> {\n return this.get('vaults')\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 // Not JSON, read as text directly\n const text = await response.text()\n throw new ApiRequestError(text || 'Unexpected error', {\n status: response.status,\n statusText: response.statusText,\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":";;AAoBA,MAAM,EACJ,mBACA,oBAAoB,oCAClB,QAAQ;AAEZ,IAAa,YAAb,MAAuB;CACrB;CACA;CACA;CACA;CAEA,YAAY,EACV,UAAU,mBACV,OAAO,cAAc,OACrB,UAAU,EAAE,EACZ,SAAS,sBACE,EAAE,EAAE;AACf,OAAK,UAAU,QAAQ,QAAQ,OAAO,GAAG;AACzC,OAAK,SAAS;AACd,OAAK,UAAU;AAEf,SACE,QACA,oIACD;AAED,OAAK,SAAS;;CAGhB,MAAgB,SAAS,UAAkB,SAAkB;EAC3D,MAAM,MAAM,MAAM,KAAK,OAAO,GAAG,KAAK,QAAQ,GAAG,YAAY;GAC3D,QAAQ;GACR,SAAS;IACP,GAAG,KAAK;IACR,gBAAgB;IAChB,eAAe,UAAU,KAAK;IAC/B;GACD,MAAM,cAAc,QAAQ;GAC7B,CAAC;AACF,MAAI,CAAC,IAAI,GACP,OAAM,eAAe,IAAI;AAG3B,SAAO,IAAI,MAAM;;CAGnB,MAAgB,IAAI,UAAkB;EACpC,MAAM,MAAM,MAAM,KAAK,OAAO,GAAG,KAAK,QAAQ,GAAG,YAAY,EAC3D,SAAS;GAAE,GAAG,KAAK;GAAS,eAAe,UAAU,KAAK;GAAU,EACrE,CAAC;AAEF,MAAI,CAAC,IAAI,GACP,OAAM,eAAe,IAAI;AAG3B,SAAO,IAAI,MAAM;;CAGnB,aAAwC;AACtC,SAAO,KAAK,IAAI,SAAS;;CAG3B,YAAsC;AACpC,SAAO,KAAK,IAAI,QAAQ;;;;;CAM1B,mBACE,aACA,SACmC;AACnC,SAAO,KAAK,SACV,aAAa,YAAY,uBACzB,QACD;;;;;CAMH,qBACE,aACA,SACqC;AACrC,SAAO,KAAK,SACV,aAAa,YAAY,yBACzB,QACD;;;AAIL,IAAa,kBAAb,MAAa,wBAAwB,MAAM;CACzC;CACA;CACA;CAEA,YACE,SACA,MAMA;AACA,QAAM,gBAAgB,eAAe,SAAS,KAAK,QAAQ,CAAC;AAC5D,OAAK,OAAO;AACZ,OAAK,SAAS,KAAK;AACnB,OAAK,aAAa,KAAK;AACvB,OAAK,UAAU,KAAK;AACpB,MAAI,KAAK,UAAU,KAAA,EACf,MAAa,QAAQ,KAAK;;CAIhC,OAAe,eAAe,SAAiB,SAAmB;AAChE,MAAI,WAAW,KAAM,QAAO;EAC5B,IAAI;AACJ,MAAI;AACF,mBACE,OAAO,YAAY,WAAW,UAAU,cAAc,SAAS,EAAE;WAC5D,MAAM;AACb,mBAAgB,OAAO,QAAQ;;AAEjC,SAAO,GAAG,QAAQ,aAAa;;CAGjC,WAAW;AACT,SAAO,GAAG,KAAK,KAAK,IAAI,KAAK;;;AAIjC,eAAe,eAAe,UAAoC;AAEhE,KADoB,SAAS,QAAQ,IAAI,eAAe,EACvC,SAAS,mBAAmB,EAAE;EAC7C,MAAM,YAAa,MAAM,SAAS,MAAM;EACxC,IAAI;AACJ,MAAI;AACF,WAAQ,IAAI,gBAAgB,UAAU,MAAM,SAAS;IACnD,QAAQ,SAAS;IACjB,YAAY,SAAS;IACrB,SAAS,UAAU,MAAM;IAC1B,CAAC;WACK,gBAAgB;AACvB,WAAQ,IAAI,gBACV,kCAAkC,kBAClC;IACE,QAAQ,SAAS;IACjB,YAAY,SAAS;IACrB,SAAS;IACV,CACF;;AAEH,QAAM;OAIN,OAAM,IAAI,gBADG,MAAM,SAAS,MAAM,IACA,oBAAoB;EACpD,QAAQ,SAAS;EACjB,YAAY,SAAS;EACtB,CAAC;;;AAKN,MAAM,iBAAiB,OAAgB,WACrC,KAAK,UACH,QACC,GAAG,UAAU;AACZ,KAAI,OAAO,UAAU,SACnB,QAAO,MAAM,UAAU;AAGzB,QAAO;GAET,OACD"}
@@ -0,0 +1,35 @@
1
+ //#region src/cli/config.d.ts
2
+ type Contracts = {
3
+ [chain: string]: ContractsNode;
4
+ };
5
+ type ContractsNode = `0x${string}` | {
6
+ [name: string]: ContractsNode;
7
+ };
8
+ interface ZodiacConfig {
9
+ apiKey: `zodiac_${string}`;
10
+ /**
11
+ * Contracts the `allow` kit should know about, keyed by chain prefix.
12
+ * Nested objects are allowed for grouping related addresses.
13
+ */
14
+ contracts?: Contracts;
15
+ /**
16
+ * Directory where fetched ABIs are stored and read from.
17
+ * Resolved relative to the project root (cwd). Defaults to `./abis`.
18
+ */
19
+ abisDir?: string;
20
+ /**
21
+ * Directory where generated type declarations (e.g. `allow.d.ts`) are
22
+ * written. Resolved relative to the project root (cwd).
23
+ * Defaults to `./.zodiac-os`.
24
+ */
25
+ typesDir?: string;
26
+ }
27
+ declare const defineConfig: (config: ZodiacConfig) => ZodiacConfig;
28
+ declare function loadConfig(configPath?: string): Promise<ZodiacConfig>;
29
+ declare const DEFAULT_ABIS_DIR = "abis";
30
+ declare const DEFAULT_TYPES_DIR = ".zodiac-os";
31
+ declare function resolveAbisDir(config: ZodiacConfig): string;
32
+ declare function resolveTypesDir(config: ZodiacConfig): string;
33
+ //#endregion
34
+ export { Contracts, ContractsNode, DEFAULT_ABIS_DIR, DEFAULT_TYPES_DIR, ZodiacConfig, defineConfig, loadConfig, resolveAbisDir, resolveTypesDir };
35
+ //# sourceMappingURL=config.d.mts.map
@@ -0,0 +1,31 @@
1
+ import { pathToFileURL } from "url";
2
+ import { resolve } from "path";
3
+ //#region src/cli/config.ts
4
+ const defineConfig = (config) => config;
5
+ const DEFAULT_CONFIG_PATH = "zodiac.config.ts";
6
+ async function loadConfig(configPath = DEFAULT_CONFIG_PATH) {
7
+ const absolutePath = resolve(process.cwd(), configPath);
8
+ let mod;
9
+ try {
10
+ mod = await import(pathToFileURL(absolutePath).href);
11
+ } catch (error) {
12
+ if (error?.code === "ERR_MODULE_NOT_FOUND" || error?.code === "ENOENT") throw new Error(`Config file not found: ${absolutePath}`);
13
+ throw error;
14
+ }
15
+ const config = mod.default ?? mod.config;
16
+ if (!config) throw new Error(`Config file must export a default value or a named "config" export: ${absolutePath}`);
17
+ if (!config.apiKey) throw new Error(`Config is missing required field "apiKey"`);
18
+ return config;
19
+ }
20
+ const DEFAULT_ABIS_DIR = "abis";
21
+ const DEFAULT_TYPES_DIR = ".zodiac-os";
22
+ function resolveAbisDir(config) {
23
+ return resolve(process.cwd(), config.abisDir ?? "abis");
24
+ }
25
+ function resolveTypesDir(config) {
26
+ return resolve(process.cwd(), config.typesDir ?? ".zodiac-os");
27
+ }
28
+ //#endregion
29
+ export { DEFAULT_ABIS_DIR, DEFAULT_TYPES_DIR, defineConfig, loadConfig, resolveAbisDir, resolveTypesDir };
30
+
31
+ //# sourceMappingURL=config.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.mjs","names":[],"sources":["../../src/cli/config.ts"],"sourcesContent":["import { pathToFileURL } from 'url'\nimport { resolve } from 'path'\n\nexport type Contracts = {\n [chain: string]: ContractsNode\n}\nexport type ContractsNode = `0x${string}` | { [name: string]: ContractsNode }\n\nexport interface ZodiacConfig {\n apiKey: `zodiac_${string}`\n /**\n * Contracts the `allow` kit should know about, keyed by chain prefix.\n * Nested objects are allowed for grouping related addresses.\n */\n contracts?: Contracts\n /**\n * Directory where fetched ABIs are stored and read from.\n * Resolved relative to the project root (cwd). Defaults to `./abis`.\n */\n abisDir?: string\n /**\n * Directory where generated type declarations (e.g. `allow.d.ts`) are\n * written. Resolved relative to the project root (cwd).\n * Defaults to `./.zodiac-os`.\n */\n typesDir?: string\n}\n\nexport const defineConfig = (config: ZodiacConfig): ZodiacConfig => config\n\nconst DEFAULT_CONFIG_PATH = 'zodiac.config.ts'\n\nexport async function loadConfig(\n configPath: string = DEFAULT_CONFIG_PATH\n): Promise<ZodiacConfig> {\n const absolutePath = resolve(process.cwd(), configPath)\n\n let mod: Record<string, unknown>\n try {\n mod = await import(pathToFileURL(absolutePath).href)\n } catch (error: any) {\n if (error?.code === 'ERR_MODULE_NOT_FOUND' || error?.code === 'ENOENT') {\n throw new Error(`Config file not found: ${absolutePath}`)\n }\n throw error\n }\n\n const config = (mod.default ?? mod.config) as ZodiacConfig | undefined\n if (!config) {\n throw new Error(\n `Config file must export a default value or a named \"config\" export: ${absolutePath}`\n )\n }\n\n if (!config.apiKey) {\n throw new Error(`Config is missing required field \"apiKey\"`)\n }\n\n return config\n}\n\nexport const DEFAULT_ABIS_DIR = 'abis'\nexport const DEFAULT_TYPES_DIR = '.zodiac-os'\n\nexport function resolveAbisDir(config: ZodiacConfig): string {\n return resolve(process.cwd(), config.abisDir ?? DEFAULT_ABIS_DIR)\n}\n\nexport function resolveTypesDir(config: ZodiacConfig): string {\n return resolve(process.cwd(), config.typesDir ?? DEFAULT_TYPES_DIR)\n}\n"],"mappings":";;;AA4BA,MAAa,gBAAgB,WAAuC;AAEpE,MAAM,sBAAsB;AAE5B,eAAsB,WACpB,aAAqB,qBACE;CACvB,MAAM,eAAe,QAAQ,QAAQ,KAAK,EAAE,WAAW;CAEvD,IAAI;AACJ,KAAI;AACF,QAAM,MAAM,OAAO,cAAc,aAAa,CAAC;UACxC,OAAY;AACnB,MAAI,OAAO,SAAS,0BAA0B,OAAO,SAAS,SAC5D,OAAM,IAAI,MAAM,0BAA0B,eAAe;AAE3D,QAAM;;CAGR,MAAM,SAAU,IAAI,WAAW,IAAI;AACnC,KAAI,CAAC,OACH,OAAM,IAAI,MACR,uEAAuE,eACxE;AAGH,KAAI,CAAC,OAAO,OACV,OAAM,IAAI,MAAM,4CAA4C;AAG9D,QAAO;;AAGT,MAAa,mBAAmB;AAChC,MAAa,oBAAoB;AAEjC,SAAgB,eAAe,QAA8B;AAC3D,QAAO,QAAQ,QAAQ,KAAK,EAAE,OAAO,WAAA,OAA4B;;AAGnE,SAAgB,gBAAgB,QAA8B;AAC5D,QAAO,QAAQ,QAAQ,KAAK,EAAE,OAAO,YAAA,aAA8B"}
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };