@zodiaceco/sdk 2.2.0 → 2.4.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.
Files changed (34) hide show
  1. package/README.md +97 -6
  2. package/dist/{actions-tA_Oe_kC.d.mts → actions-Cri7InV-.d.mts} +57 -4
  3. package/dist/actions.d.mts +1 -1
  4. package/dist/actions.mjs +18 -2
  5. package/dist/actions.mjs.map +1 -1
  6. package/dist/allow/index.d.mts +1 -1
  7. package/dist/allow/index.mjs +2 -2
  8. package/dist/{allow-CrDzDtxx.mjs → allow-DX3iyizB.mjs} +2 -2
  9. package/dist/{allow-CrDzDtxx.mjs.map → allow-DX3iyizB.mjs.map} +1 -1
  10. package/dist/cli/config.mjs +1 -1
  11. package/dist/cli/push.d.mts +24 -0
  12. package/dist/cli/push.mjs +36 -0
  13. package/dist/cli/push.mjs.map +1 -0
  14. package/dist/cli.mjs +15 -6
  15. package/dist/cli.mjs.map +1 -1
  16. package/dist/{config-D0FqysME.mjs → config-BaChz2bq.mjs} +10 -5
  17. package/dist/config-BaChz2bq.mjs.map +1 -0
  18. package/dist/{index-CnH26Z_A.d.mts → index-D49x1ozw.d.mts} +1 -1
  19. package/dist/index.d.mts +34 -5
  20. package/dist/index.mjs +8 -180
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/{networks-B0H4Uoyh.mjs → networks-UKtJ8QDG.mjs} +1 -1
  23. package/dist/{networks-B0H4Uoyh.mjs.map → networks-UKtJ8QDG.mjs.map} +1 -1
  24. package/dist/paths-LsD6ULk0.mjs +34 -0
  25. package/dist/paths-LsD6ULk0.mjs.map +1 -0
  26. package/dist/{permissionEntries-D3fSVhwO.mjs → permissionEntries-C4dAwZBb.mjs} +3 -1
  27. package/dist/permissionEntries-C4dAwZBb.mjs.map +1 -0
  28. package/dist/push-DdnHsLpO.mjs +356 -0
  29. package/dist/push-DdnHsLpO.mjs.map +1 -0
  30. package/package.json +7 -3
  31. package/dist/api-kKgzHQlu.mjs +0 -158
  32. package/dist/api-kKgzHQlu.mjs.map +0 -1
  33. package/dist/config-D0FqysME.mjs.map +0 -1
  34. package/dist/permissionEntries-D3fSVhwO.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-DdnHsLpO.mjs","names":[],"sources":["../src/api.ts","../src/push.ts"],"sourcesContent":["import type {\n ApplyConstellationPayload,\n ApplyConstellationResult,\n ResolveConstellationPayload,\n ResolveConstellationResult,\n ApiError as ApiErrorResponse,\n ApiErrorIssue,\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 /** What the API refused, when it could name each problem. */\n public readonly issues: ApiErrorIssue[]\n /** The API's own summary of the problem. */\n public readonly reason: string\n\n constructor(\n message: string,\n opts: {\n status: number\n statusText: string\n details?: unknown\n cause?: unknown\n }\n ) {\n const issues = readIssues(opts.details)\n\n super(\n issues.length > 0\n ? formatIssues(message, issues)\n : ApiRequestError.composeMessage(message, opts.details)\n )\n this.name = 'ApiRequestError'\n this.status = opts.status\n this.statusText = opts.statusText\n this.details = opts.details\n this.issues = issues\n this.reason = message\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/**\n * The issues of an error response, each naming where in the request it is.\n * Anything that does not look like one is left to the raw details.\n */\nconst readIssues = (details: unknown): ApiErrorIssue[] => {\n if (\n typeof details !== 'object' ||\n details == null ||\n !('issues' in details)\n ) {\n return []\n }\n\n const { issues } = details\n\n if (!Array.isArray(issues)) {\n return []\n }\n\n return issues.flatMap((issue): ApiErrorIssue[] =>\n typeof issue === 'object' &&\n issue != null &&\n Array.isArray(issue.path) &&\n typeof issue.message === 'string'\n ? [{ path: issue.path, message: issue.message }]\n : []\n )\n}\n\n/** A headline and one line per issue, the way a terminal reads best. */\nexport const formatIssues = (\n headline: string,\n issues: { path: string; message: string }[] | ApiErrorIssue[]\n) =>\n [\n `${headline}:`,\n ...issues.map(({ path, message }) => {\n const where = Array.isArray(path) ? path.join(' › ') : path\n\n return where === '' ? ` • ${message}` : ` • ${where}: ${message}`\n }),\n ].join('\\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","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, ApiRequestError, formatIssues } 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 /** API key for the default client, instead of `ZODIAC_API_KEY`. */\n apiKey?: string\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({ apiKey: opts?.apiKey })\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\n .applyConstellation(meta.workspaceId, {\n label: meta.label,\n chain: meta.chain,\n specification,\n })\n .catch((error: unknown) => {\n if (error instanceof ApiRequestError && error.issues.length > 0) {\n throw new ConstellationRejectedError(meta.label, error, (index) => {\n const node = groupNodes[index]\n\n return node == null\n ? `node ${index}`\n : `${refs.byIdentity.get(node)} (\"${node.label}\")`\n })\n }\n\n throw error\n })\n results.push(result)\n }\n\n return results\n}\n\n/**\n * Zodiac refused a constellation as it was pushed. Each issue names the node\n * it concerns by the name it was pushed under, followed by the path within it.\n */\nexport class ConstellationRejectedError extends Error {\n readonly issues: { path: string; message: string }[]\n\n constructor(\n label: string,\n error: ApiRequestError,\n nameNode: (index: number) => string\n ) {\n const issues = error.issues.map(({ path, message }) => {\n const [root, index, ...rest] = path\n\n return {\n path:\n root === 'specification' && typeof index === 'number'\n ? [nameNode(index), ...rest].join(' › ')\n : path.join(' › '),\n message,\n }\n })\n\n super(\n formatIssues(\n `Zodiac refused the constellation \"${label}\". ${error.reason}`,\n issues\n )\n )\n\n this.name = 'ConstellationRejectedError'\n this.issues = issues\n }\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 ...(def.policy != null && { policy: def.policy }),\n ...(def.description != null && { description: def.description }),\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.2.0",
3
+ "version": "2.4.0",
4
4
  "author": "Zodiac Labs GmbH",
5
5
  "license": "LGPL-3.0-only",
6
6
  "homepage": "https://github.com/gnosisguild/zodiac-sdk",
@@ -28,6 +28,10 @@
28
28
  "./cli/config": {
29
29
  "types": "./dist/cli/config.d.mts",
30
30
  "import": "./dist/cli/config.mjs"
31
+ },
32
+ "./cli/push": {
33
+ "types": "./dist/cli/push.d.mts",
34
+ "import": "./dist/cli/push.mjs"
31
35
  }
32
36
  },
33
37
  "files": [
@@ -46,12 +50,12 @@
46
50
  "check:format": "prettier --list-different .",
47
51
  "check:types": "tsc -p tsconfig.json",
48
52
  "fix:format": "prettier --write .",
49
- "test": "bun test",
53
+ "test": "bun scripts/test.ts",
50
54
  "install-local-api-types": "cd ../zodiac-os/public-packages/api-types && pnpm pack-tmp && cd ../../../zodiac-sdk && tar -xzf /tmp/zodiac-os-api-types.tgz --strip-components=1 -C node_modules/@zodiaceco/api-types"
51
55
  },
52
56
  "dependencies": {
53
57
  "@epic-web/invariant": "1.0.0",
54
- "@zodiaceco/api-types": "^1.9.2",
58
+ "@zodiaceco/api-types": "^1.11.0",
55
59
  "commander": "^14.0.3",
56
60
  "defi-kit": "^2.26.12",
57
61
  "dotenv": "^17.4.2",
@@ -1,158 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import assert from "assert";
4
- //#region src/paths.ts
5
- /**
6
- * Resolve the project's `.zodiac/` directory — the shared home for SDK
7
- * codegen (`pull-org` emits the importable module here, `pull-contracts`
8
- * writes `allow.d.ts` alongside it).
9
- *
10
- * Pass `rootDir` to anchor explicitly (CLI commands do this using the
11
- * directory of the loaded `zodiac.config.ts`). Without a `rootDir`, walks
12
- * up from `cwd` to the nearest `zodiac.config.{ts,js,mjs,cjs}` so runtime
13
- * callers like `constellation()` work even when invoked from a subdirectory.
14
- */
15
- function resolveZodiacDir(rootDir) {
16
- return join(rootDir ?? findProjectRoot(), ".zodiac");
17
- }
18
- const CONFIG_FILENAMES = [
19
- "zodiac.config.ts",
20
- "zodiac.config.js",
21
- "zodiac.config.mjs",
22
- "zodiac.config.cjs"
23
- ];
24
- function findProjectRoot() {
25
- let dir = process.cwd();
26
- while (dir !== dirname(dir)) {
27
- if (CONFIG_FILENAMES.some((name) => existsSync(join(dir, name)))) return dir;
28
- dir = dirname(dir);
29
- }
30
- return process.cwd();
31
- }
32
- //#endregion
33
- //#region src/api.ts
34
- const DEFAULT_API_URL = "https://app.zodiac.eco/api/v1";
35
- var ApiClient = class {
36
- apiKey;
37
- baseUrl;
38
- _fetch;
39
- headers;
40
- constructor({ baseUrl = process.env.ZODIAC_API_URL ?? DEFAULT_API_URL, fetch: customFetch = fetch, headers = {}, apiKey = process.env.ZODIAC_API_KEY } = {}) {
41
- this.baseUrl = baseUrl.replace(/\/$/, "");
42
- this._fetch = customFetch;
43
- this.headers = headers;
44
- assert(apiKey, "No API key provided to the API client. Either pass it as the \"apiKey\" option or set the ZODIAC_API_KEY environment variable.");
45
- this.apiKey = apiKey;
46
- }
47
- async postJson(endpoint, payload) {
48
- const res = await this._fetch(`${this.baseUrl}/${endpoint}`, {
49
- method: "POST",
50
- headers: {
51
- ...this.headers,
52
- "content-type": "application/json",
53
- authorization: `Bearer ${this.apiKey}`
54
- },
55
- body: jsonStringify(payload)
56
- });
57
- if (!res.ok) await handleApiError(res);
58
- return res.json();
59
- }
60
- async get(endpoint) {
61
- const res = await this._fetch(`${this.baseUrl}/${endpoint}`, { headers: {
62
- ...this.headers,
63
- authorization: `Bearer ${this.apiKey}`
64
- } });
65
- if (!res.ok) await handleApiError(res);
66
- return res.json();
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
- */
74
- listAccounts() {
75
- return this.get("accounts");
76
- }
77
- /** Every member of this org, with the personal safes they have activated. */
78
- listUsers() {
79
- return this.get("users");
80
- }
81
- /**
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.
85
- */
86
- applyConstellation(workspaceId, payload) {
87
- return this.postJson(`workspace/${workspaceId}/constellation/apply`, payload);
88
- }
89
- /**
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.
96
- */
97
- resolveConstellation(workspaceId, payload) {
98
- return this.postJson(`workspace/${workspaceId}/constellation/resolve`, payload);
99
- }
100
- };
101
- var ApiRequestError = class ApiRequestError extends Error {
102
- status;
103
- statusText;
104
- details;
105
- constructor(message, opts) {
106
- super(ApiRequestError.composeMessage(message, opts.details));
107
- this.name = "ApiRequestError";
108
- this.status = opts.status;
109
- this.statusText = opts.statusText;
110
- this.details = opts.details;
111
- if (opts.cause !== void 0) this.cause = opts.cause;
112
- }
113
- static composeMessage(message, details) {
114
- if (details == null) return message;
115
- let detailsString;
116
- try {
117
- detailsString = typeof details === "string" ? details : jsonStringify(details, 2);
118
- } catch (_err) {
119
- detailsString = String(details);
120
- }
121
- return `${message}\nDetails: ${detailsString}`;
122
- }
123
- toString() {
124
- return `${this.name}: ${this.message}`;
125
- }
126
- };
127
- async function handleApiError(response) {
128
- if (response.headers.get("content-type")?.includes("application/json")) {
129
- const errorData = await response.json();
130
- let error;
131
- try {
132
- error = new ApiRequestError(errorData.error.message, {
133
- status: response.status,
134
- statusText: response.statusText,
135
- details: errorData.error.details
136
- });
137
- } catch (jsonShapeError) {
138
- error = new ApiRequestError(`Failed parsing error response: ${jsonShapeError}`, {
139
- status: response.status,
140
- statusText: response.statusText,
141
- details: errorData
142
- });
143
- }
144
- throw error;
145
- } else throw new ApiRequestError(`${response.status} ${response.statusText}: ${response.url}`, {
146
- status: response.status,
147
- statusText: response.statusText
148
- });
149
- }
150
- /** JSON.stringify with bigint support */
151
- const jsonStringify = (value, indent) => JSON.stringify(value, (_, value) => {
152
- if (typeof value === "bigint") return value.toString();
153
- return value;
154
- }, indent);
155
- //#endregion
156
- export { resolveZodiacDir as n, ApiClient as t };
157
-
158
- //# sourceMappingURL=api-kKgzHQlu.mjs.map
@@ -1 +0,0 @@
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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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"config-D0FqysME.mjs","names":["resolve","dirname"],"sources":["../src/cli/projectRoot.ts","../src/cli/config.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\n\n/**\n * Walk up from `startDir` looking for a `package.json`. Returns the\n * directory that contains it. Falls back to `startDir` if no ancestor\n * has a `package.json` (e.g. when running outside any project).\n */\nexport const findProjectRoot = (startDir: string = process.cwd()): string => {\n const absoluteStart = resolve(startDir)\n let dir = absoluteStart\n\n while (true) {\n if (existsSync(resolve(dir, 'package.json'))) {\n return dir\n }\n const parent = dirname(dir)\n if (parent === dir) {\n return absoluteStart\n }\n dir = parent\n }\n}\n","import { existsSync, writeFileSync } from 'node:fs'\nimport { pathToFileURL } from 'url'\nimport { dirname, resolve } from 'path'\nimport { findProjectRoot } from './projectRoot'\n\nexport type Contracts = {\n [chain: string]: ContractsNode\n}\nexport type ContractsNode = `0x${string}` | { [name: string]: ContractsNode }\n\nexport interface ZodiacConfig {\n /**\n * API key authorizing this directory against a Zodiac org.\n *\n * Optional — defaults to `process.env.ZODIAC_API_KEY` (populated by\n * `zodiac init`). Set this explicitly only when you need to override\n * the env var from inside the config file.\n */\n apiKey?: `zodiac_${string}`\n /**\n * Contracts the `allow` kit should know about, keyed by chain prefix.\n * Nested objects are allowed for grouping related addresses.\n */\n contracts?: Contracts\n /**\n * Directory where fetched ABIs are stored and read from.\n * Resolved relative to the project root (config file's directory).\n * Defaults to `./abis`.\n */\n abisDir?: string\n}\n\n/** User-provided config plus the resolved API key + project root. */\nexport interface ResolvedConfig extends Omit<ZodiacConfig, 'apiKey'> {\n apiKey: `zodiac_${string}`\n rootDir: string\n}\n\n/**\n * Loose base used as the *inference* constraint for `defineConfig`.\n * `contracts` is `Record<string, unknown>` here so `const T` can preserve the\n * caller's exact address literals rather than collapsing them into the\n * recursive `ContractsNode` union.\n */\ntype DefineConfigInput = {\n apiKey?: `zodiac_${string}`\n contracts?: Record<string, unknown>\n abisDir?: string\n}\n\n/**\n * Recursive leaf-level check: every leaf in `contracts` must be\n * `` `0x${string}` ``; any other value collapses the branch to `never`,\n * which surfaces as a type error at the call site.\n */\ntype ValidateContracts<C> = {\n [K in keyof C]: C[K] extends `0x${string}`\n ? C[K]\n : C[K] extends object\n ? ValidateContracts<C[K]>\n : never\n}\n\nexport const defineConfig = <const T extends DefineConfigInput>(\n config: T & {\n contracts?: ValidateContracts<NonNullable<T['contracts']>>\n }\n): T => config\n\nconst CONFIG_BASENAME = 'zodiac.config'\n\n/**\n * Extensions probed (in priority order) when the user doesn't pass an\n * explicit `--config` path. Mirrors what vite / vitest / tailwind accept.\n */\nconst CONFIG_EXTENSIONS = [\n '.ts',\n '.mts',\n '.cts',\n '.js',\n '.mjs',\n '.cjs',\n] as const\n\nconst DEFAULT_CONFIG_PATH = `${CONFIG_BASENAME}.ts`\n\nconst CONFIG_STUB = `import { defineConfig } from \"@zodiaceco/sdk/cli/config\";\n\nexport default defineConfig({\n contracts: {\n // Add contracts the \\`allow\\` kit should know about, keyed by chain prefix.\n // Run \\`zodiac pull-contracts\\` after editing.\n //\n // eth: {\n // weth: \"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\",\n // },\n },\n});\n`\n\ntype LoadConfigOptions = {\n /**\n * Called when neither `config.apiKey` nor `process.env.ZODIAC_API_KEY` is\n * set. Receives the resolved project root and must return a freshly minted\n * API key. Typically wired to the interactive `init()` flow.\n *\n * If omitted, `loadConfig` throws when no key is found.\n */\n onMissingKey?: (rootDir: string) => Promise<string>\n /**\n * When the config file doesn't exist, write a minimal stub before\n * loading. Used by the CLI's `pull` commands so first-time setup\n * works without the user having to scaffold the file themselves.\n */\n createIfMissing?: boolean\n}\n\n/**\n * Write a starter `zodiac.config.ts` if no file exists at `absolutePath`.\n * Returns `true` if a file was written.\n */\nexport const ensureConfigStub = (absolutePath: string): boolean => {\n if (existsSync(absolutePath)) return false\n writeFileSync(absolutePath, CONFIG_STUB, 'utf8')\n return true\n}\n\n/**\n * Turn a (possibly-default) config path into an absolute path under\n * `projectRoot`. When the caller didn't pass an explicit path, probe\n * for `zodiac.config.{ts,mts,cts,js,mjs,cjs}` and use whichever exists;\n * fall back to the canonical `.ts` name so a not-found error is\n * still phrased in terms users recognise.\n */\nconst resolveConfigPath = (projectRoot: string, configPath: string): string => {\n const explicit = resolve(projectRoot, configPath)\n if (configPath !== DEFAULT_CONFIG_PATH) return explicit\n\n for (const ext of CONFIG_EXTENSIONS) {\n const candidate = resolve(projectRoot, `${CONFIG_BASENAME}${ext}`)\n if (existsSync(candidate)) return candidate\n }\n return explicit\n}\n\nexport async function loadConfig(\n configPath: string = DEFAULT_CONFIG_PATH,\n options: LoadConfigOptions = {}\n): Promise<ResolvedConfig> {\n // Resolve relative paths (including the default) against the nearest\n // package.json ancestor — that's where users expect the config to live,\n // regardless of which subdir they ran the CLI from.\n const projectRoot = findProjectRoot()\n const absolutePath = resolveConfigPath(projectRoot, configPath)\n\n if (options.createIfMissing && ensureConfigStub(absolutePath)) {\n console.log(`✅ Created ${absolutePath}`)\n }\n\n let mod: Record<string, unknown>\n try {\n mod = await import(pathToFileURL(absolutePath).href)\n } catch (error: any) {\n if (error?.code === 'ERR_MODULE_NOT_FOUND' || error?.code === 'ENOENT') {\n throw new Error(`Config file not found: ${absolutePath}`)\n }\n throw error\n }\n\n const config = (mod.default ?? mod.config) as ZodiacConfig | undefined\n if (!config) {\n throw new Error(\n `Config file must export a default value or a named \"config\" export: ${absolutePath}`\n )\n }\n\n const rootDir = dirname(absolutePath)\n const apiKey = await resolveApiKey(config, rootDir, options)\n\n return { ...config, apiKey, rootDir }\n}\n\nconst resolveApiKey = async (\n config: ZodiacConfig,\n rootDir: string,\n { onMissingKey }: LoadConfigOptions\n): Promise<`zodiac_${string}`> => {\n if (config.apiKey != null) {\n if (!isApiKey(config.apiKey)) {\n throw new Error(\n '`apiKey` in zodiac.config.ts is malformed: a valid Zodiac API key starts with \"zodiac_\". Either remove the field to use ZODIAC_API_KEY, or run `zodiac init` to mint a fresh key.'\n )\n }\n return config.apiKey\n }\n\n const fromEnv = process.env.ZODIAC_API_KEY\n if (fromEnv != null && fromEnv !== '') {\n if (!isApiKey(fromEnv)) {\n throw new Error(\n 'ZODIAC_API_KEY is set but malformed: a valid Zodiac API key starts with \"zodiac_\". Run `zodiac init` to mint a fresh key.'\n )\n }\n return fromEnv\n }\n\n if (onMissingKey == null) {\n throw new Error(\n 'No Zodiac API key found. Set ZODIAC_API_KEY in your environment, or run `zodiac init` to generate one.'\n )\n }\n\n const minted = await onMissingKey(rootDir)\n if (!isApiKey(minted)) {\n throw new Error(\n `onMissingKey returned an invalid Zodiac API key (expected a value starting with \"zodiac_\").`\n )\n }\n return minted\n}\n\nconst isApiKey = (value: string | undefined): value is `zodiac_${string}` =>\n value != null && value.startsWith('zodiac_')\n\nexport const DEFAULT_ABIS_DIR = 'abis'\n\n/**\n * Resolves the absolute path to the ABIs directory. Only `rootDir` (and\n * optionally `abisDir`) are read, so the parameter is intentionally narrower\n * than `ResolvedConfig`: userland code can pass `{ ...config, rootDir }`\n * without first satisfying `apiKey` (normally supplied via the\n * `ZODIAC_API_KEY` env var at runtime).\n */\nexport function resolveAbisDir(config: {\n rootDir: string\n abisDir?: string\n}): string {\n return resolve(config.rootDir, config.abisDir ?? DEFAULT_ABIS_DIR)\n}\n"],"mappings":";;;;;;;;;;AAQA,MAAa,mBAAmB,WAAmB,QAAQ,KAAK,KAAa;CAC3E,MAAM,gBAAgB,QAAQ,SAAS;CACvC,IAAI,MAAM;AAEV,QAAO,MAAM;AACX,MAAI,WAAW,QAAQ,KAAK,eAAe,CAAC,CAC1C,QAAO;EAET,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IACb,QAAO;AAET,QAAM;;;;;AC2CV,MAAa,gBACX,WAGM;AAER,MAAM,kBAAkB;;;;;AAMxB,MAAM,oBAAoB;CACxB;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,sBAAsB,GAAG,gBAAgB;AAE/C,MAAM,cAAc;;;;;;;;;;;;;;;;;AAmCpB,MAAa,oBAAoB,iBAAkC;AACjE,KAAI,WAAW,aAAa,CAAE,QAAO;AACrC,eAAc,cAAc,aAAa,OAAO;AAChD,QAAO;;;;;;;;;AAUT,MAAM,qBAAqB,aAAqB,eAA+B;CAC7E,MAAM,WAAWA,UAAQ,aAAa,WAAW;AACjD,KAAI,eAAe,oBAAqB,QAAO;AAE/C,MAAK,MAAM,OAAO,mBAAmB;EACnC,MAAM,YAAYA,UAAQ,aAAa,GAAG,kBAAkB,MAAM;AAClE,MAAI,WAAW,UAAU,CAAE,QAAO;;AAEpC,QAAO;;AAGT,eAAsB,WACpB,aAAqB,qBACrB,UAA6B,EAAE,EACN;CAKzB,MAAM,eAAe,kBADD,iBAAiB,EACe,WAAW;AAE/D,KAAI,QAAQ,mBAAmB,iBAAiB,aAAa,CAC3D,SAAQ,IAAI,aAAa,eAAe;CAG1C,IAAI;AACJ,KAAI;AACF,QAAM,MAAM,OAAO,cAAc,aAAa,CAAC;UACxC,OAAY;AACnB,MAAI,OAAO,SAAS,0BAA0B,OAAO,SAAS,SAC5D,OAAM,IAAI,MAAM,0BAA0B,eAAe;AAE3D,QAAM;;CAGR,MAAM,SAAU,IAAI,WAAW,IAAI;AACnC,KAAI,CAAC,OACH,OAAM,IAAI,MACR,uEAAuE,eACxE;CAGH,MAAM,UAAUC,UAAQ,aAAa;CACrC,MAAM,SAAS,MAAM,cAAc,QAAQ,SAAS,QAAQ;AAE5D,QAAO;EAAE,GAAG;EAAQ;EAAQ;EAAS;;AAGvC,MAAM,gBAAgB,OACpB,QACA,SACA,EAAE,mBAC8B;AAChC,KAAI,OAAO,UAAU,MAAM;AACzB,MAAI,CAAC,SAAS,OAAO,OAAO,CAC1B,OAAM,IAAI,MACR,sLACD;AAEH,SAAO,OAAO;;CAGhB,MAAM,UAAU,QAAQ,IAAI;AAC5B,KAAI,WAAW,QAAQ,YAAY,IAAI;AACrC,MAAI,CAAC,SAAS,QAAQ,CACpB,OAAM,IAAI,MACR,8HACD;AAEH,SAAO;;AAGT,KAAI,gBAAgB,KAClB,OAAM,IAAI,MACR,yGACD;CAGH,MAAM,SAAS,MAAM,aAAa,QAAQ;AAC1C,KAAI,CAAC,SAAS,OAAO,CACnB,OAAM,IAAI,MACR,8FACD;AAEH,QAAO;;AAGT,MAAM,YAAY,UAChB,SAAS,QAAQ,MAAM,WAAW,UAAU;AAE9C,MAAa,mBAAmB;;;;;;;;AAShC,SAAgB,eAAe,QAGpB;AACT,QAAOD,UAAQ,OAAO,SAAS,OAAO,WAAA,OAA4B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"permissionEntries-D3fSVhwO.mjs","names":[],"sources":["../src/permissionEntries.ts"],"sourcesContent":["import type { Address, ChainId } from '@zodiaceco/api-types'\nimport type { Permission } from 'zodiac-roles-sdk'\nimport type { NodeRef } from './constellation'\n\n/**\n * The shapes a role's `permissions` travel to the API in, and the one piece of\n * rendering `push()` still does. Internal: the helpers that build these live in\n * `./actions`, and nothing here is part of the published interface beyond the\n * `PermissionEntry` type that module re-exports.\n */\n\n/**\n * The base url of the DeFi Kit API. Annotations point back at it so that a role\n * can be read as presets again, and so that a DeFi Kit entry written in code is\n * indistinguishable from one configured in the app.\n */\nconst DEFI_KIT_BASE_URL = 'https://kit.kpk.io/api/v1'\n\n/** The chains DeFi Kit serves, by the prefix it addresses them with. */\nconst DEFI_KIT_CHAIN_PREFIXES: Record<number, string> = {\n 1: 'eth',\n 10: 'oeth',\n 100: 'gno',\n 8453: 'base',\n 42161: 'arb1',\n}\n\n/** Renders a DeFi Kit entry into the annotation it stands for on `chain`. */\nexport const toAnnotation = (\n { defiKit: { protocol, verb, params } }: DeFiKitEntry,\n chain: ChainId\n) => {\n const prefix = DEFI_KIT_CHAIN_PREFIXES[chain]\n\n if (prefix == null) {\n throw new Error(`DeFi Kit does not serve chain \"${chain}\"`)\n }\n\n return {\n uri: `${DEFI_KIT_BASE_URL}/permissions/${prefix}/${protocol}/${verb}?${searchParams(params)}`,\n schema: `${DEFI_KIT_BASE_URL}/openapi.json`,\n }\n}\n\nexport const isDeFiKitEntry = (entry: PermissionEntry): entry is DeFiKitEntry =>\n typeof entry === 'object' && entry !== null && 'defiKit' in entry\n\n/**\n * The chains of the node recipients a `transfer()`'s same-chain `to` named.\n * Symbol-keyed so it never serializes; `push()` checks it against the chain of\n * the roles node the entry ends up on. A transfer to a node on another chain\n * would permit sending funds to an address that may not exist there.\n */\nexport const recipientChains = Symbol('recipientChains')\n\nexport const assertRecipientChains = (\n entry: PermissionEntry,\n chain: ChainId\n): void => {\n const chains = (entry as { [recipientChains]?: readonly ChainId[] })[\n recipientChains\n ]\n\n for (const recipientChain of chains ?? []) {\n if (recipientChain !== chain) {\n const label = 'label' in entry ? `\"${entry.label}\"` : 'a transfer'\n throw new Error(\n `A recipient of ${label} lives on chain \"${recipientChain}\", but the role is on chain \"${chain}\". Use \\`bridge\\` for cross-chain recipients.`\n )\n }\n }\n}\n\n/**\n * Search params are appended one value at a time, the form annotation uris are\n * normalized to before they are matched back against a preset.\n */\nconst searchParams = (params: Record<string, DeFiKitParamValue>) => {\n const searchParams = new URLSearchParams()\n\n for (const [name, value] of Object.entries(params)) {\n if (value == null) {\n continue\n }\n\n for (const entry of Array.isArray(value) ? value : [value]) {\n searchParams.append(name, String(entry))\n }\n }\n\n return searchParams\n}\n\n/**\n * An allowance key. Authored as a plain label, the way a role key is, and\n * encoded to bytes32 when the constellation is deployed. An already-encoded\n * key still works — `encodeKey` passes 32-byte hex through unchanged.\n */\nexport type AllowanceKey = string\n\nexport type SwapEntry = {\n label: string\n action: { type: 'swap'; sell: Address[]; buy: Address[] }\n}\n\nexport type TransferEntry = {\n label: string\n action: {\n type: 'transfer'\n tokens: Address[]\n to: Address[]\n bridge?: { chain: ChainId; to: Address[]; receive: Address[] }[]\n allowance?: AllowanceKey\n }\n}\n\n/**\n * A permission's target, written either as an address or as another node in the\n * same constellation.\n *\n * A policy is usually pushed alongside the accounts it governs, so the address\n * a permission points at often does not exist yet. Naming the node instead\n * sends a reference, and the address is substituted once it is derived at\n * deploy — the same way a node is named as an owner, a module or an avatar.\n *\n * `NodeRef` for the same reason those fields use it: an uninvoked accessor is\n * a forward reference to a node, and is how two nodes that need each other are\n * written. It carries the label a reference resolves by without being a\n * complete node.\n */\nexport type PermissionTarget = `0x${string}` | NodeRef\n\ntype WithNodeTarget<P> = P extends { targetAddress: `0x${string}` }\n ? Omit<P, 'targetAddress'> & { targetAddress: PermissionTarget }\n : P\n\n/** A `zodiac-roles-sdk` permission that may name a node as its target. */\nexport type ConstellationPermission = WithNodeTarget<Permission>\n\nexport type LabelledPermissions = {\n label: string\n permissions: ConstellationPermission[]\n}\n\nexport type DeFiKitEntry = {\n label: string\n defiKit: {\n protocol: string\n verb: string\n params: Record<string, DeFiKitParamValue>\n }\n}\n\nexport type DeFiKitParamValue =\n | string\n | number\n | boolean\n | readonly (string | number | boolean)[]\n | null\n | undefined\n\n/**\n * One entry of a role's `permissions`. A bare permission stays valid, but every\n * labelled form names something the app can show as a single card.\n */\nexport type PermissionEntry =\n | ConstellationPermission\n | LabelledPermissions\n | SwapEntry\n | TransferEntry\n | DeFiKitEntry\n\n/** A role's full permission list — the type `permissions.ts` files satisfy. */\nexport type Permissions = readonly PermissionEntry[]\n"],"mappings":";;;;;;;;;;;;AAgBA,MAAM,oBAAoB;;AAG1B,MAAM,0BAAkD;CACtD,GAAG;CACH,IAAI;CACJ,KAAK;CACL,MAAM;CACN,OAAO;CACR;;AAGD,MAAa,gBACX,EAAE,SAAS,EAAE,UAAU,MAAM,YAC7B,UACG;CACH,MAAM,SAAS,wBAAwB;AAEvC,KAAI,UAAU,KACZ,OAAM,IAAI,MAAM,kCAAkC,MAAM,GAAG;AAG7D,QAAO;EACL,KAAK,GAAG,kBAAkB,eAAe,OAAO,GAAG,SAAS,GAAG,KAAK,GAAG,aAAa,OAAO;EAC3F,QAAQ,GAAG,kBAAkB;EAC9B;;AAGH,MAAa,kBAAkB,UAC7B,OAAO,UAAU,YAAY,UAAU,QAAQ,aAAa;;;;;;;AAQ9D,MAAa,kBAAkB,OAAO,kBAAkB;AAExD,MAAa,yBACX,OACA,UACS;CACT,MAAM,SAAU,MACd;AAGF,MAAK,MAAM,kBAAkB,UAAU,EAAE,CACvC,KAAI,mBAAmB,OAAO;EAC5B,MAAM,QAAQ,WAAW,QAAQ,IAAI,MAAM,MAAM,KAAK;AACtD,QAAM,IAAI,MACR,kBAAkB,MAAM,mBAAmB,eAAe,+BAA+B,MAAM,+CAChG;;;;;;;AASP,MAAM,gBAAgB,WAA8C;CAClE,MAAM,eAAe,IAAI,iBAAiB;AAE1C,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,OAAO,EAAE;AAClD,MAAI,SAAS,KACX;AAGF,OAAK,MAAM,SAAS,MAAM,QAAQ,MAAM,GAAG,QAAQ,CAAC,MAAM,CACxD,cAAa,OAAO,MAAM,OAAO,MAAM,CAAC;;AAI5C,QAAO"}