@zodiac-os/sdk 1.6.0 → 1.7.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 +18 -27
- package/dist/allow-Dzh6t_l8.mjs.map +1 -1
- package/dist/{api-D6ee2Q2b.mjs → api-CygEDU4N.mjs} +39 -8
- package/dist/api-CygEDU4N.mjs.map +1 -0
- package/dist/cli/config.d.mts +9 -4
- package/dist/cli/config.mjs +6 -3
- package/dist/cli/config.mjs.map +1 -1
- package/dist/cli.mjs +77 -50
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +57 -41
- package/dist/index.mjs +34 -44
- package/dist/index.mjs.map +1 -1
- package/dist/zodiac-os-codegen.d.ts +14 -52
- package/package.json +3 -3
- package/dist/api-D6ee2Q2b.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ export default defineConfig({
|
|
|
39
39
|
zodiac-os pull
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
This generates typed data in
|
|
42
|
+
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`.
|
|
43
43
|
|
|
44
44
|
## Constellation API
|
|
45
45
|
|
|
@@ -61,27 +61,29 @@ const eth = constellation({
|
|
|
61
61
|
})
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
### Referencing existing
|
|
64
|
+
### Referencing existing accounts
|
|
65
65
|
|
|
66
|
-
Bracket access gives you existing Safes and Roles mods from the selected workspace. Names auto-complete from the codegen output.
|
|
66
|
+
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.
|
|
67
67
|
|
|
68
68
|
```ts
|
|
69
69
|
// Reference an existing Safe — no invocation needed
|
|
70
70
|
const ggDao = eth.safe['GG DAO']
|
|
71
71
|
|
|
72
|
-
// Reference
|
|
73
|
-
const ggDaoRoles = eth.roles['GG DAO']
|
|
72
|
+
// Reference an existing Roles mod
|
|
73
|
+
const ggDaoRoles = eth.roles['GG DAO Roles']
|
|
74
74
|
|
|
75
75
|
// Optionally invoke with overrides
|
|
76
76
|
const ggDaoOverridden = eth.safe['GG DAO']({ threshold: 5 })
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
> `bun push` runs `pull-org` first via the `prepush` hook, so re-pushing always sees the freshest existing-account values from your org.
|
|
80
|
+
|
|
79
81
|
### Creating new accounts
|
|
80
82
|
|
|
81
|
-
Use bracket access with a new label to create new nodes.
|
|
83
|
+
Use bracket access with a new label to create new nodes. Every mandatory field (`nonce`, `threshold`, `owners` for Safes; `nonce` for Roles mods) must be supplied explicitly — the SDK does not inject any runtime defaults. The type system surfaces a missing field as a compile-time error so you can't ship an incomplete spec.
|
|
82
84
|
|
|
83
85
|
```ts
|
|
84
|
-
// New Safe — threshold, owners are required
|
|
86
|
+
// New Safe — nonce, threshold, owners are required
|
|
85
87
|
const newSafe = eth.safe['New Safe']({
|
|
86
88
|
nonce: 0n,
|
|
87
89
|
threshold: 2,
|
|
@@ -99,18 +101,7 @@ const newRoles = eth.roles['New Roles']({
|
|
|
99
101
|
})
|
|
100
102
|
```
|
|
101
103
|
|
|
102
|
-
|
|
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
|
-
```
|
|
104
|
+
When a bracket label matches an existing account from your codegen, all overrides become optional — you pass only the fields you want to change against the live configuration.
|
|
114
105
|
|
|
115
106
|
### Circular references between new nodes
|
|
116
107
|
|
|
@@ -131,7 +122,7 @@ const roles = eth.roles['New Roles']({
|
|
|
131
122
|
})
|
|
132
123
|
```
|
|
133
124
|
|
|
134
|
-
References are resolved by label at `
|
|
125
|
+
References are resolved by label at `push()` time, so both sides of the cycle must be included in the call.
|
|
135
126
|
|
|
136
127
|
### Referencing users
|
|
137
128
|
|
|
@@ -141,22 +132,22 @@ References are resolved by label at `apply()` time, so both sides of the cycle m
|
|
|
141
132
|
const aliceAddress = eth.user['Alice Sample']
|
|
142
133
|
```
|
|
143
134
|
|
|
144
|
-
###
|
|
135
|
+
### Pushing the constellation
|
|
145
136
|
|
|
146
|
-
The `
|
|
137
|
+
The `push()` function takes all nodes and sends them to the Zodiac OS API. Pass either a named object (keys become refs) or an array:
|
|
147
138
|
|
|
148
139
|
```ts
|
|
149
|
-
import {
|
|
140
|
+
import { push } from '@zodiac-os/sdk'
|
|
150
141
|
|
|
151
|
-
await
|
|
142
|
+
await push({ ggDao, ggDaoRoles, newSafe, newRoles })
|
|
152
143
|
```
|
|
153
144
|
|
|
154
|
-
All referenced nodes must be included in the `
|
|
145
|
+
All referenced nodes must be included in the `push()` call.
|
|
155
146
|
|
|
156
|
-
By default, `
|
|
147
|
+
By default, `push()` creates an API client from the `ZODIAC_API_KEY` environment variable. You can pass a custom client:
|
|
157
148
|
|
|
158
149
|
```ts
|
|
159
|
-
await
|
|
150
|
+
await push({ ggDao, newRoles }, { api: new ApiClient({ apiKey: '...' }) })
|
|
160
151
|
```
|
|
161
152
|
|
|
162
153
|
## CLI reference
|
|
@@ -1 +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":";;;;;AAoDA,MAAa,aAAa,OAAO,IAAI,kCAAkC;;;ACzCvE,SAAgB,cACd,SACA,iBACqB;CACrB,MAAM,MAA2B,EAAE;AACnC,MAAK,MAAM,QAAQ,cAAc,gBAAgB,EAAE;EACjD,MAAM,MAAM,QAAQ,SAAS,KAAK;AAClC,MAAI,CAAC,KAAK;AAGR,YAAS,KAAK,CAAC,KAAK,OAAO,GAAG,KAAK,SAAS,EAAE,gBAAgB,KAAK,CAAC;AACpE;;AAEF,WACE,KACA,CAAC,KAAK,OAAO,GAAG,KAAK,SAAS,EAC9B,kBAAkB,KAAK,SAAS,IAAoB,CACrD;;AAEH,QAAO;;AAGT,SAAS,SAAS,MAA2B,UAAoB,OAAY;CAC3E,IAAI,SAAS;AACb,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,SAAS,GAAG,KAAK;EAC5C,MAAM,MAAM,SAAS;AACrB,MAAI,EAAE,OAAO,QAAS,QAAO,OAAO,EAAE;AACtC,WAAS,OAAO;;AAElB,QAAO,SAAS,SAAS,SAAS,MAAO;;AAG3C,SAAS,gBAAgB,MAAoB;CAC3C,MAAM,gBAAgB;AACpB,QAAM,IAAI,MACR,mBAAmB,KAAK,MAAM,GAAG,KAAK,SAAS,KAAK,IAAI,CAAC,IACnD,KAAK,QAAQ,+FAC2B,KAAK,MAAM,GAAG,KAAK,SAAS,KAAK,IAAI,CAAC,OACrF;;AAEH,QAAO,IAAI,MACT,EAAE,EACF;EACE,KAAK;EACL,KAAK;EACN,CACF;;AAGH,SAAS,kBACP,SACA,KAC8B;CAC9B,MAAM,QAAQ,UAAU,KAAK,IAAI;CACjC,MAAM,YAAY,QAAQ,aAAa;CAEvC,MAAM,mBAAmB,aAAyC;EAChE,eAAe;EACf,MAAM,SAAS;EACf,cAAc,SAAS;EACxB;CAED,MAAM,OAAO,SAAiB;AAC5B,MAAI;GACF,MAAM,KAAK,MAAM,YAAY,KAAK;AAClC,OAAI,CAAC,GAAI,QAAO;AAChB,UAAO,GAAG,oBAAoB,UAAU,GAAG,oBAAoB;WACxD,OAAO;AACd,OAAI,CAAC,QAAQ,OAAc,mBAAmB,CAAE,OAAM;AACtD,UAAO;;;AAIX,QAAO,IAAI,MACT,EAAE,EACF;EACE,MAAM,SAAS,SAAS;AACtB,OAAI,SAAS,WAAY,QAAO;AAChC,OAAI,OAAO,SAAS,SAAU,QAAO,KAAA;AACrC,OAAI,CAAC,IAAI,KAAK,CAAE,QAAO,KAAA;AAEvB,UAAO,kBADI,MAAM,YAAY,
|
|
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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"}
|
|
@@ -1,16 +1,47 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
1
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
|
|
2
33
|
//#region src/api.ts
|
|
3
|
-
const {
|
|
34
|
+
const { ZODIAC_API_KEY, ZODIAC_API_URL = "https://app.zodiac.eco/api/v1" } = process.env;
|
|
4
35
|
var ApiClient = class {
|
|
5
36
|
apiKey;
|
|
6
37
|
baseUrl;
|
|
7
38
|
_fetch;
|
|
8
39
|
headers;
|
|
9
|
-
constructor({ baseUrl =
|
|
40
|
+
constructor({ baseUrl = ZODIAC_API_URL, fetch: customFetch = fetch, headers = {}, apiKey = ZODIAC_API_KEY } = {}) {
|
|
10
41
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
11
42
|
this._fetch = customFetch;
|
|
12
43
|
this.headers = headers;
|
|
13
|
-
assert(apiKey, "No API key provided to the API client. Either pass it as the \"apiKey\" option or set the
|
|
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.");
|
|
14
45
|
this.apiKey = apiKey;
|
|
15
46
|
}
|
|
16
47
|
async postJson(endpoint, payload) {
|
|
@@ -34,8 +65,8 @@ var ApiClient = class {
|
|
|
34
65
|
if (!res.ok) await handleApiError(res);
|
|
35
66
|
return res.json();
|
|
36
67
|
}
|
|
37
|
-
|
|
38
|
-
return this.get("
|
|
68
|
+
listAccounts() {
|
|
69
|
+
return this.get("accounts");
|
|
39
70
|
}
|
|
40
71
|
listUsers() {
|
|
41
72
|
return this.get("users");
|
|
@@ -97,7 +128,7 @@ async function handleApiError(response) {
|
|
|
97
128
|
});
|
|
98
129
|
}
|
|
99
130
|
throw error;
|
|
100
|
-
} else throw new ApiRequestError(
|
|
131
|
+
} else throw new ApiRequestError(`${response.status} ${response.statusText}: ${response.url}`, {
|
|
101
132
|
status: response.status,
|
|
102
133
|
statusText: response.statusText
|
|
103
134
|
});
|
|
@@ -108,6 +139,6 @@ const jsonStringify = (value, indent) => JSON.stringify(value, (_, value) => {
|
|
|
108
139
|
return value;
|
|
109
140
|
}, indent);
|
|
110
141
|
//#endregion
|
|
111
|
-
export { ApiClient as t };
|
|
142
|
+
export { resolveZodiacDir as n, ApiClient as t };
|
|
112
143
|
|
|
113
|
-
//# sourceMappingURL=api-
|
|
144
|
+
//# sourceMappingURL=api-CygEDU4N.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-CygEDU4N.mjs","names":[],"sources":["../src/paths.ts","../src/api.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\n/**\n * Resolve the project's `.zodiac/` directory — the shared home for SDK\n * codegen (`pull-org` emits the importable module here, `pull-contracts`\n * writes `allow.d.ts` alongside it).\n *\n * Pass `rootDir` to anchor explicitly (CLI commands do this using the\n * directory of the loaded `zodiac.config.ts`). Without a `rootDir`, walks\n * up from `cwd` to the nearest `zodiac.config.{ts,js,mjs,cjs}` so runtime\n * callers like `constellation()` work even when invoked from a subdirectory.\n */\nexport function resolveZodiacDir(rootDir?: string): string {\n return join(rootDir ?? findProjectRoot(), '.zodiac')\n}\n\nconst CONFIG_FILENAMES = [\n 'zodiac.config.ts',\n 'zodiac.config.js',\n 'zodiac.config.mjs',\n 'zodiac.config.cjs',\n]\n\nfunction findProjectRoot(): string {\n let dir = process.cwd()\n while (dir !== dirname(dir)) {\n if (CONFIG_FILENAMES.some((name) => existsSync(join(dir, name)))) return dir\n dir = dirname(dir)\n }\n return process.cwd()\n}\n","import type {\n ApplyConstellationPayload,\n ApplyConstellationResult,\n ResolveConstellationPayload,\n ResolveConstellationResult,\n ApiError as ApiErrorResponse,\n ListAccountsResult,\n ListUsersResult,\n} from '@zodiac-os/api-types'\nimport assert from 'assert'\nimport { UUID } from 'crypto'\n\nexport type Options = {\n workspace?: string\n apiKey?: string\n baseUrl?: string\n fetch?: typeof globalThis.fetch\n headers?: Record<string, string>\n}\n\nconst { ZODIAC_API_KEY, ZODIAC_API_URL = 'https://app.zodiac.eco/api/v1' } =\n process.env\n\nexport class ApiClient {\n private apiKey: string\n private baseUrl: string\n private _fetch: typeof fetch\n private headers: Record<string, string>\n\n constructor({\n baseUrl = ZODIAC_API_URL,\n fetch: customFetch = fetch,\n headers = {},\n apiKey = ZODIAC_API_KEY,\n }: Options = {}) {\n this.baseUrl = baseUrl.replace(/\\/$/, '')\n this._fetch = customFetch\n this.headers = headers\n\n assert(\n apiKey,\n 'No API key provided to the API client. Either pass it as the \"apiKey\" option or set the ZODIAC_API_KEY environment variable.'\n )\n\n this.apiKey = apiKey\n }\n\n protected async postJson(endpoint: string, payload: unknown) {\n const res = await this._fetch(`${this.baseUrl}/${endpoint}`, {\n method: 'POST',\n headers: {\n ...this.headers,\n 'content-type': 'application/json',\n authorization: `Bearer ${this.apiKey}`,\n },\n body: jsonStringify(payload),\n })\n if (!res.ok) {\n await handleApiError(res)\n }\n\n return res.json()\n }\n\n protected async get(endpoint: string) {\n const res = await this._fetch(`${this.baseUrl}/${endpoint}`, {\n headers: { ...this.headers, authorization: `Bearer ${this.apiKey}` },\n })\n\n if (!res.ok) {\n await handleApiError(res)\n }\n\n return res.json()\n }\n\n listAccounts(): Promise<ListAccountsResult> {\n return this.get('accounts')\n }\n\n listUsers(): Promise<ListUsersResult> {\n return this.get('users')\n }\n\n /**\n * Applies an accounts specification to Zodiac OS.\n */\n applyConstellation(\n workspaceId: UUID,\n payload: ApplyConstellationPayload\n ): Promise<ApplyConstellationResult> {\n return this.postJson(\n `workspace/${workspaceId}/constellation/apply`,\n payload\n )\n }\n\n /**\n * Resolves an accounts specification to Zodiac OS.\n */\n resolveConstellation(\n workspaceId: UUID,\n payload: ResolveConstellationPayload\n ): Promise<ResolveConstellationResult> {\n return this.postJson(\n `workspace/${workspaceId}/constellation/resolve`,\n payload\n )\n }\n}\n\nexport class ApiRequestError extends Error {\n public readonly status: number\n public readonly statusText: string\n public readonly details?: unknown\n\n constructor(\n message: string,\n opts: {\n status: number\n statusText: string\n details?: unknown\n cause?: unknown\n }\n ) {\n super(ApiRequestError.composeMessage(message, opts.details))\n this.name = 'ApiRequestError'\n this.status = opts.status\n this.statusText = opts.statusText\n this.details = opts.details\n if (opts.cause !== undefined) {\n ;(this as any).cause = opts.cause\n }\n }\n\n private static composeMessage(message: string, details?: unknown) {\n if (details == null) return message\n let detailsString: string\n try {\n detailsString =\n typeof details === 'string' ? details : jsonStringify(details, 2)\n } catch (_err) {\n detailsString = String(details)\n }\n return `${message}\\nDetails: ${detailsString}`\n }\n\n toString() {\n return `${this.name}: ${this.message}`\n }\n}\n\nasync function handleApiError(response: Response): Promise<never> {\n const contentType = response.headers.get('content-type')\n if (contentType?.includes('application/json')) {\n const errorData = (await response.json()) as ApiErrorResponse\n let error: ApiRequestError\n try {\n error = new ApiRequestError(errorData.error.message, {\n status: response.status,\n statusText: response.statusText,\n details: errorData.error.details,\n })\n } catch (jsonShapeError) {\n error = new ApiRequestError(\n `Failed parsing error response: ${jsonShapeError}`,\n {\n status: response.status,\n statusText: response.statusText,\n details: errorData,\n }\n )\n }\n throw error\n } else {\n throw new ApiRequestError(\n `${response.status} ${response.statusText}: ${response.url}`,\n {\n status: response.status,\n statusText: response.statusText,\n }\n )\n }\n}\n\n/** JSON.stringify with bigint support */\nconst jsonStringify = (value: unknown, indent?: number) =>\n JSON.stringify(\n value,\n (_, value) => {\n if (typeof value === 'bigint') {\n return value.toString()\n }\n\n return value\n },\n indent\n )\n"],"mappings":"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"}
|
package/dist/cli/config.d.mts
CHANGED
|
@@ -14,10 +14,15 @@ interface ZodiacConfig {
|
|
|
14
14
|
contracts?: Contracts;
|
|
15
15
|
/**
|
|
16
16
|
* Directory where fetched ABIs are stored and read from.
|
|
17
|
-
* Resolved relative to the project root (
|
|
17
|
+
* Resolved relative to the project root (config file's directory).
|
|
18
|
+
* Defaults to `./abis`.
|
|
18
19
|
*/
|
|
19
20
|
abisDir?: string;
|
|
20
21
|
}
|
|
22
|
+
/** User-provided config plus the directory it was loaded from. */
|
|
23
|
+
interface ResolvedConfig extends ZodiacConfig {
|
|
24
|
+
rootDir: string;
|
|
25
|
+
}
|
|
21
26
|
/**
|
|
22
27
|
* Loose base used as the *inference* constraint for `defineConfig`.
|
|
23
28
|
* `contracts` is `Record<string, unknown>` here so `const T` can preserve the
|
|
@@ -38,9 +43,9 @@ type ValidateContracts<C> = { [K in keyof C]: C[K] extends `0x${string}` ? C[K]
|
|
|
38
43
|
declare const defineConfig: <const T extends DefineConfigInput>(config: T & {
|
|
39
44
|
contracts?: ValidateContracts<NonNullable<T["contracts"]>>;
|
|
40
45
|
}) => T;
|
|
41
|
-
declare function loadConfig(configPath?: string): Promise<
|
|
46
|
+
declare function loadConfig(configPath?: string): Promise<ResolvedConfig>;
|
|
42
47
|
declare const DEFAULT_ABIS_DIR = "abis";
|
|
43
|
-
declare function resolveAbisDir(config:
|
|
48
|
+
declare function resolveAbisDir(config: ResolvedConfig): string;
|
|
44
49
|
//#endregion
|
|
45
|
-
export { Contracts, ContractsNode, DEFAULT_ABIS_DIR, ZodiacConfig, defineConfig, loadConfig, resolveAbisDir };
|
|
50
|
+
export { Contracts, ContractsNode, DEFAULT_ABIS_DIR, ResolvedConfig, ZodiacConfig, defineConfig, loadConfig, resolveAbisDir };
|
|
46
51
|
//# sourceMappingURL=config.d.mts.map
|
package/dist/cli/config.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { pathToFileURL } from "url";
|
|
2
|
-
import { resolve } from "path";
|
|
2
|
+
import { dirname, resolve } from "path";
|
|
3
3
|
//#region src/cli/config.ts
|
|
4
4
|
const defineConfig = (config) => config;
|
|
5
5
|
const DEFAULT_CONFIG_PATH = "zodiac.config.ts";
|
|
@@ -15,11 +15,14 @@ async function loadConfig(configPath = DEFAULT_CONFIG_PATH) {
|
|
|
15
15
|
const config = mod.default ?? mod.config;
|
|
16
16
|
if (!config) throw new Error(`Config file must export a default value or a named "config" export: ${absolutePath}`);
|
|
17
17
|
if (!config.apiKey) throw new Error(`Config is missing required field "apiKey"`);
|
|
18
|
-
return
|
|
18
|
+
return {
|
|
19
|
+
...config,
|
|
20
|
+
rootDir: dirname(absolutePath)
|
|
21
|
+
};
|
|
19
22
|
}
|
|
20
23
|
const DEFAULT_ABIS_DIR = "abis";
|
|
21
24
|
function resolveAbisDir(config) {
|
|
22
|
-
return resolve(
|
|
25
|
+
return resolve(config.rootDir, config.abisDir ?? "abis");
|
|
23
26
|
}
|
|
24
27
|
//#endregion
|
|
25
28
|
export { DEFAULT_ABIS_DIR, defineConfig, loadConfig, resolveAbisDir };
|
package/dist/cli/config.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.mjs","names":[],"sources":["../../src/cli/config.ts"],"sourcesContent":["import { pathToFileURL } from 'url'\nimport { resolve } from 'path'\n\nexport type Contracts = {\n [chain: string]: ContractsNode\n}\nexport type ContractsNode = `0x${string}` | { [name: string]: ContractsNode }\n\nexport interface ZodiacConfig {\n apiKey: `zodiac_${string}`\n /**\n * Contracts the `allow` kit should know about, keyed by chain prefix.\n * Nested objects are allowed for grouping related addresses.\n */\n contracts?: Contracts\n /**\n * Directory where fetched ABIs are stored and read from.\n * Resolved relative to the project root (
|
|
1
|
+
{"version":3,"file":"config.mjs","names":[],"sources":["../../src/cli/config.ts"],"sourcesContent":["import { pathToFileURL } from 'url'\nimport { dirname, resolve } from 'path'\n\nexport type Contracts = {\n [chain: string]: ContractsNode\n}\nexport type ContractsNode = `0x${string}` | { [name: string]: ContractsNode }\n\nexport interface ZodiacConfig {\n apiKey: `zodiac_${string}`\n /**\n * Contracts the `allow` kit should know about, keyed by chain prefix.\n * Nested objects are allowed for grouping related addresses.\n */\n contracts?: Contracts\n /**\n * Directory where fetched ABIs are stored and read from.\n * Resolved relative to the project root (config file's directory).\n * Defaults to `./abis`.\n */\n abisDir?: string\n}\n\n/** User-provided config plus the directory it was loaded from. */\nexport interface ResolvedConfig extends ZodiacConfig {\n rootDir: string\n}\n\n/**\n * Loose base used as the *inference* constraint for `defineConfig`.\n * `contracts` is `Record<string, unknown>` here so `const T` can preserve the\n * caller's exact address literals rather than collapsing them into the\n * recursive `ContractsNode` union.\n */\ntype DefineConfigInput = {\n apiKey: `zodiac_${string}`\n contracts?: Record<string, unknown>\n abisDir?: string\n}\n\n/**\n * Recursive leaf-level check: every leaf in `contracts` must be\n * `` `0x${string}` ``; any other value collapses the branch to `never`,\n * which surfaces as a type error at the call site.\n */\ntype ValidateContracts<C> = {\n [K in keyof C]: C[K] extends `0x${string}`\n ? C[K]\n : C[K] extends object\n ? ValidateContracts<C[K]>\n : never\n}\n\nexport const defineConfig = <const T extends DefineConfigInput>(\n config: T & {\n contracts?: ValidateContracts<NonNullable<T['contracts']>>\n }\n): T => config\n\nconst DEFAULT_CONFIG_PATH = 'zodiac.config.ts'\n\nexport async function loadConfig(\n configPath: string = DEFAULT_CONFIG_PATH\n): Promise<ResolvedConfig> {\n const absolutePath = resolve(process.cwd(), configPath)\n\n let mod: Record<string, unknown>\n try {\n mod = await import(pathToFileURL(absolutePath).href)\n } catch (error: any) {\n if (error?.code === 'ERR_MODULE_NOT_FOUND' || error?.code === 'ENOENT') {\n throw new Error(`Config file not found: ${absolutePath}`)\n }\n throw error\n }\n\n const config = (mod.default ?? mod.config) as ZodiacConfig | undefined\n if (!config) {\n throw new Error(\n `Config file must export a default value or a named \"config\" export: ${absolutePath}`\n )\n }\n\n if (!config.apiKey) {\n throw new Error(`Config is missing required field \"apiKey\"`)\n }\n\n return { ...config, rootDir: dirname(absolutePath) }\n}\n\nexport const DEFAULT_ABIS_DIR = 'abis'\n\nexport function resolveAbisDir(config: ResolvedConfig): string {\n return resolve(config.rootDir, config.abisDir ?? DEFAULT_ABIS_DIR)\n}\n"],"mappings":";;;AAqDA,MAAa,gBACX,WAGM;AAER,MAAM,sBAAsB;AAE5B,eAAsB,WACpB,aAAqB,qBACI;CACzB,MAAM,eAAe,QAAQ,QAAQ,KAAK,EAAE,WAAW;CAEvD,IAAI;AACJ,KAAI;AACF,QAAM,MAAM,OAAO,cAAc,aAAa,CAAC;UACxC,OAAY;AACnB,MAAI,OAAO,SAAS,0BAA0B,OAAO,SAAS,SAC5D,OAAM,IAAI,MAAM,0BAA0B,eAAe;AAE3D,QAAM;;CAGR,MAAM,SAAU,IAAI,WAAW,IAAI;AACnC,KAAI,CAAC,OACH,OAAM,IAAI,MACR,uEAAuE,eACxE;AAGH,KAAI,CAAC,OAAO,OACV,OAAM,IAAI,MAAM,4CAA4C;AAG9D,QAAO;EAAE,GAAG;EAAQ,SAAS,QAAQ,aAAa;EAAE;;AAGtD,MAAa,mBAAmB;AAEhC,SAAgB,eAAe,QAAgC;AAC7D,QAAO,QAAQ,OAAO,SAAS,OAAO,WAAA,OAA4B"}
|
package/dist/cli.mjs
CHANGED
|
@@ -1,30 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as ApiClient } from "./api-
|
|
2
|
+
import { n as resolveZodiacDir, t as ApiClient } from "./api-CygEDU4N.mjs";
|
|
3
3
|
import { a as walkContracts, i as readAbi, n as chainIdFor, o as writeAbi, r as abiFilePath } from "./networks-BTW1qAAa.mjs";
|
|
4
4
|
import { loadConfig, resolveAbisDir } from "./cli/config.mjs";
|
|
5
|
-
import { invariant } from "@epic-web/invariant";
|
|
6
5
|
import fs from "node:fs";
|
|
7
|
-
import path
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { invariant } from "@epic-web/invariant";
|
|
8
|
+
import { getAddress } from "ethers";
|
|
8
9
|
import { Command } from "commander";
|
|
9
10
|
import { join as join$1 } from "path";
|
|
10
11
|
import { ModuleKind, Project, ScriptTarget, VariableDeclarationKind } from "ts-morph";
|
|
11
12
|
import { mkdirSync, writeFileSync } from "fs";
|
|
12
|
-
import { fileURLToPath } from "node:url";
|
|
13
|
-
//#region src/cli/paths.ts
|
|
14
|
-
/**
|
|
15
|
-
* Resolve the consumer's `node_modules/.zodiac-os/` directory — the shared
|
|
16
|
-
* home for SDK codegen (`pull-org` emits the importable module here,
|
|
17
|
-
* `pull-contracts` writes `allow.d.ts` alongside it).
|
|
18
|
-
*
|
|
19
|
-
* Prefers walking up from this module's own path when the SDK is installed
|
|
20
|
-
* under a real `node_modules`; falls back to `<cwd>/node_modules` when the
|
|
21
|
-
* SDK is being run from source (dev / linked builds).
|
|
22
|
-
*/
|
|
23
|
-
function resolveZodiacOsDir() {
|
|
24
|
-
const match = fileURLToPath(import.meta.url).match(/^(.+[/\\]node_modules)[/\\]/);
|
|
25
|
-
return join(match ? match[1] : join(process.cwd(), "node_modules"), ".zodiac-os");
|
|
26
|
-
}
|
|
27
|
-
//#endregion
|
|
28
13
|
//#region src/cli/commands/pullOrg.ts
|
|
29
14
|
const toLiteral = (value, indent = 0) => {
|
|
30
15
|
const pad = " ".repeat(indent);
|
|
@@ -46,34 +31,65 @@ const toLiteral = (value, indent = 0) => {
|
|
|
46
31
|
};
|
|
47
32
|
const pullOrg = async (config) => {
|
|
48
33
|
const client = new ApiClient({ apiKey: config.apiKey });
|
|
49
|
-
const [users,
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
34
|
+
const [users, workspaceAccounts] = await Promise.all([client.listUsers(), client.listAccounts()]);
|
|
35
|
+
const resolvableAccounts = workspaceAccounts.flatMap((ws) => ws.accounts).filter((a) => a.spec != null || a.vault);
|
|
36
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
37
|
+
if (resolvableAccounts.length > 0) {
|
|
38
|
+
const response = await client.resolveConstellation(workspaceAccounts[0].workspaceId, { specification: resolvableAccounts.map((account, i) => account.spec != null ? account.spec : {
|
|
39
|
+
ref: `vault_${i}`,
|
|
40
|
+
type: "SAFE",
|
|
41
|
+
chain: account.chain,
|
|
42
|
+
address: account.address
|
|
43
|
+
}) });
|
|
44
|
+
invariant(response?.result?.length === resolvableAccounts.length, `resolveConstellation returned ${response?.result?.length ?? 0} accounts for ${resolvableAccounts.length} accounts`);
|
|
45
|
+
resolvableAccounts.forEach((account, i) => {
|
|
46
|
+
resolved.set(account.id, response.result[i]);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const accountsRecord = {};
|
|
50
|
+
for (const ws of workspaceAccounts) {
|
|
51
|
+
const safes = {};
|
|
52
|
+
const rolesMods = {};
|
|
53
|
+
const delays = {};
|
|
54
|
+
const bucketsByType = {
|
|
55
|
+
SAFE: safes,
|
|
56
|
+
ROLES: rolesMods,
|
|
57
|
+
DELAY: delays
|
|
58
|
+
};
|
|
59
|
+
const isNodeType = (type) => type === "SAFE" || type === "ROLES" || type === "DELAY";
|
|
60
|
+
const labelCountByType = {
|
|
61
|
+
SAFE: /* @__PURE__ */ new Map(),
|
|
62
|
+
ROLES: /* @__PURE__ */ new Map(),
|
|
63
|
+
DELAY: /* @__PURE__ */ new Map()
|
|
64
|
+
};
|
|
65
|
+
for (const account of ws.accounts) {
|
|
66
|
+
if (!isNodeType(account.type)) continue;
|
|
67
|
+
const counts = labelCountByType[account.type];
|
|
68
|
+
counts.set(account.label, (counts.get(account.label) ?? 0) + 1);
|
|
69
|
+
}
|
|
70
|
+
for (const account of ws.accounts) {
|
|
71
|
+
if (!isNodeType(account.type)) continue;
|
|
72
|
+
const onChain = resolved.get(account.id);
|
|
73
|
+
const key = (labelCountByType[account.type].get(account.label) ?? 0) > 1 ? `${account.label} (${getAddress(account.address)})` : account.label;
|
|
74
|
+
bucketsByType[account.type][key] = {
|
|
75
|
+
id: account.id,
|
|
76
|
+
label: account.label,
|
|
66
77
|
address: account.address,
|
|
67
|
-
chain:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
chain: account.chain,
|
|
79
|
+
vault: account.vault,
|
|
80
|
+
...onChain?.type === "SAFE" && {
|
|
81
|
+
threshold: onChain.threshold,
|
|
82
|
+
owners: [...onChain.owners],
|
|
83
|
+
modules: [...onChain.modules]
|
|
84
|
+
}
|
|
71
85
|
};
|
|
72
86
|
}
|
|
73
|
-
|
|
87
|
+
accountsRecord[ws.workspaceName] = {
|
|
74
88
|
workspaceId: ws.workspaceId,
|
|
75
89
|
workspaceName: ws.workspaceName,
|
|
76
|
-
|
|
90
|
+
safes,
|
|
91
|
+
rolesMods,
|
|
92
|
+
delays
|
|
77
93
|
};
|
|
78
94
|
}
|
|
79
95
|
const nameCount = /* @__PURE__ */ new Map();
|
|
@@ -87,18 +103,17 @@ const pullOrg = async (config) => {
|
|
|
87
103
|
personalSafes: user.personalSafes
|
|
88
104
|
};
|
|
89
105
|
}
|
|
90
|
-
const outDir =
|
|
106
|
+
const outDir = resolveZodiacDir(config.rootDir);
|
|
91
107
|
mkdirSync(outDir, { recursive: true });
|
|
92
108
|
writeFileSync(join$1(outDir, "package.json"), JSON.stringify({
|
|
93
|
-
|
|
94
|
-
type: "module",
|
|
109
|
+
type: "commonjs",
|
|
95
110
|
main: "index.js",
|
|
96
111
|
types: "index.d.ts"
|
|
97
112
|
}, null, 2));
|
|
98
113
|
const sourceFile = new Project({
|
|
99
114
|
compilerOptions: {
|
|
100
115
|
declaration: true,
|
|
101
|
-
module: ModuleKind.
|
|
116
|
+
module: ModuleKind.CommonJS,
|
|
102
117
|
target: ScriptTarget.ESNext,
|
|
103
118
|
outDir
|
|
104
119
|
},
|
|
@@ -116,12 +131,24 @@ const pullOrg = async (config) => {
|
|
|
116
131
|
isExported: true,
|
|
117
132
|
declarationKind: VariableDeclarationKind.Const,
|
|
118
133
|
declarations: [{
|
|
119
|
-
name: "
|
|
120
|
-
initializer: `${toLiteral(
|
|
134
|
+
name: "accounts",
|
|
135
|
+
initializer: `${toLiteral(accountsRecord)} as const`
|
|
121
136
|
}]
|
|
122
137
|
});
|
|
123
138
|
const emitResult = sourceFile.getEmitOutput();
|
|
124
|
-
for (const outputFile of emitResult.getOutputFiles())
|
|
139
|
+
for (const outputFile of emitResult.getOutputFiles()) {
|
|
140
|
+
const fileName = outputFile.getFilePath().includes(".d.ts") ? "index.d.ts" : "index.js";
|
|
141
|
+
let contents = outputFile.getText();
|
|
142
|
+
if (fileName === "index.d.ts") contents += `
|
|
143
|
+
declare global {
|
|
144
|
+
interface ZodiacGeneratedCodegen {
|
|
145
|
+
users: typeof users;
|
|
146
|
+
accounts: typeof accounts;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
`;
|
|
150
|
+
writeFileSync(join$1(outDir, fileName), contents);
|
|
151
|
+
}
|
|
125
152
|
};
|
|
126
153
|
//#endregion
|
|
127
154
|
//#region src/allow/fetch.ts
|
|
@@ -297,7 +324,7 @@ const pullContracts = async (config) => {
|
|
|
297
324
|
return;
|
|
298
325
|
}
|
|
299
326
|
const abisDir = resolveAbisDir(config);
|
|
300
|
-
const generatedFile = path.join(
|
|
327
|
+
const generatedFile = path.join(resolveZodiacDir(config.rootDir), "allow.d.ts");
|
|
301
328
|
let missing = 0;
|
|
302
329
|
let fetched = 0;
|
|
303
330
|
let existing = 0;
|