@zodiaceco/sdk 2.3.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.
- package/README.md +55 -3
- package/dist/{actions-DUOQItr6.d.mts → actions-Cri7InV-.d.mts} +14 -2
- package/dist/actions.d.mts +1 -1
- package/dist/actions.mjs +18 -2
- package/dist/actions.mjs.map +1 -1
- package/dist/allow/index.d.mts +1 -1
- package/dist/allow/index.mjs +2 -2
- package/dist/{allow-CrDzDtxx.mjs → allow-DX3iyizB.mjs} +2 -2
- package/dist/{allow-CrDzDtxx.mjs.map → allow-DX3iyizB.mjs.map} +1 -1
- package/dist/cli/config.mjs +1 -1
- package/dist/cli/push.d.mts +24 -0
- package/dist/cli/push.mjs +36 -0
- package/dist/cli/push.mjs.map +1 -0
- package/dist/cli.mjs +15 -6
- package/dist/cli.mjs.map +1 -1
- package/dist/{config-D0FqysME.mjs → config-BaChz2bq.mjs} +10 -5
- package/dist/config-BaChz2bq.mjs.map +1 -0
- package/dist/{index-CnH26Z_A.d.mts → index-D49x1ozw.d.mts} +1 -1
- package/dist/index.d.mts +34 -5
- package/dist/index.mjs +4 -180
- package/dist/index.mjs.map +1 -1
- package/dist/{networks-B0H4Uoyh.mjs → networks-UKtJ8QDG.mjs} +1 -1
- package/dist/{networks-B0H4Uoyh.mjs.map → networks-UKtJ8QDG.mjs.map} +1 -1
- package/dist/paths-LsD6ULk0.mjs +34 -0
- package/dist/paths-LsD6ULk0.mjs.map +1 -0
- package/dist/{permissionEntries-D3fSVhwO.mjs → permissionEntries-C4dAwZBb.mjs} +3 -1
- package/dist/permissionEntries-C4dAwZBb.mjs.map +1 -0
- package/dist/push-DdnHsLpO.mjs +356 -0
- package/dist/push-DdnHsLpO.mjs.map +1 -0
- package/package.json +7 -3
- package/dist/api-kKgzHQlu.mjs +0 -158
- package/dist/api-kKgzHQlu.mjs.map +0 -1
- package/dist/config-D0FqysME.mjs.map +0 -1
- package/dist/permissionEntries-D3fSVhwO.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -256,7 +256,12 @@ Each helper covers a different kind of action:
|
|
|
256
256
|
name one with `chain` when the recipients are plain addresses. Tokens without
|
|
257
257
|
an Across route to a target are skipped, the same way the app skips them —
|
|
258
258
|
routes change between writing a spec and deploying it — but a target nothing
|
|
259
|
-
can reach at all is refused at deploy rather than deployed half-working.
|
|
259
|
+
can reach at all is refused at deploy rather than deployed half-working. A
|
|
260
|
+
transfer bridges to each chain once: every recipient of a target may receive
|
|
261
|
+
every token it names, so recipients that receive other tokens on the same
|
|
262
|
+
chain get a transfer of their own. `transfer()` throws on a second target for
|
|
263
|
+
a chain, and on `to` recipients that live on different chains; `push()`
|
|
264
|
+
throws on `to` recipients off the role's chain, and on a bridge to it.
|
|
260
265
|
- `defikit` mirrors the DeFi Kit allow kit — same protocols, verbs and
|
|
261
266
|
parameters, plus a `label`. A DeFi Kit entry is nothing but its annotation;
|
|
262
267
|
the permissions behind it are fetched from the annotation's uri at deploy, so
|
|
@@ -272,13 +277,36 @@ it has nowhere to appear in the app beyond the targets it allows.
|
|
|
272
277
|
Allowance keys are plain labels — `key: 'usdc_payouts'` on the declaration, and
|
|
273
278
|
`allowance: usdc_payouts` on the transfer, which reads the key off it. They are
|
|
274
279
|
encoded to bytes32 when the constellation is deployed, so nothing calls
|
|
275
|
-
`encodeKey` by hand.
|
|
280
|
+
`encodeKey` by hand. Allowance and role keys consist of 1 to 31 letters,
|
|
281
|
+
digits, underscores or hyphens.
|
|
276
282
|
|
|
277
283
|
Tokens are named by address, not by symbol. A `transfer()` recipient may also
|
|
278
284
|
be a node — an account from your codegen, or one bound by address — which
|
|
279
285
|
stands for the address it lives at. A node whose address is only known once the
|
|
280
286
|
constellation is deployed is rejected at compile time.
|
|
281
287
|
|
|
288
|
+
### Showing accounts and roles in the Zodiac app
|
|
289
|
+
|
|
290
|
+
A constellation deploys whatever it describes, but only what you mark shows up
|
|
291
|
+
in the Zodiac app for the rest of your org:
|
|
292
|
+
|
|
293
|
+
- **`vault: true`** on a Safe lists it under **Vaults**.
|
|
294
|
+
- **`policy`** on a role lists it under **Policies**, labelled as given, so
|
|
295
|
+
everyone in your org can see what it permits right in the app. A
|
|
296
|
+
`description` is optional. In a template project, export both from the
|
|
297
|
+
role's folder:
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
// constellation/roles/treasury_ops/index.ts
|
|
301
|
+
export const policy = 'Treasury Ops'
|
|
302
|
+
export const description = 'Day-to-day treasury operations'
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The role key (`treasury_ops`) is the policy's identity and its on-chain role
|
|
306
|
+
key, so renaming the label changes nothing on chain. Every Roles mod carrying
|
|
307
|
+
the key has to grant it the same way, and no two of them may act for the
|
|
308
|
+
same Safe.
|
|
309
|
+
|
|
282
310
|
### Pushing the constellation
|
|
283
311
|
|
|
284
312
|
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:
|
|
@@ -297,12 +325,22 @@ By default, `push()` creates an API client from the `ZODIAC_API_KEY` environment
|
|
|
297
325
|
await push({ ggDao, newRoles }, { api: new ApiClient({ apiKey: '...' }) })
|
|
298
326
|
```
|
|
299
327
|
|
|
328
|
+
When Zodiac refuses a constellation as it stands — a policy it could not hold,
|
|
329
|
+
a key it cannot encode — `push()` rejects with a `ConstellationRejectedError`
|
|
330
|
+
that lists every issue, starting at the node you pushed. `zodiac push` prints
|
|
331
|
+
it like this:
|
|
332
|
+
|
|
333
|
+
```
|
|
334
|
+
Zodiac refused the constellation "Production". Some policies of this constellation cannot be held in Zodiac:
|
|
335
|
+
• opsRoles ("Ops Roles") › roles › treasury_ops › policy: "Treasury Ops" is a policy on another Roles modifier. Mark this role as that policy too.
|
|
336
|
+
```
|
|
337
|
+
|
|
300
338
|
## CLI reference
|
|
301
339
|
|
|
302
340
|
```
|
|
303
341
|
Usage: zodiac [options] [command]
|
|
304
342
|
|
|
305
|
-
Zodiac SDK CLI – pull org data and contract ABIs
|
|
343
|
+
Zodiac SDK CLI – pull org data and contract ABIs, push constellations
|
|
306
344
|
|
|
307
345
|
Options:
|
|
308
346
|
-V, --version output the version number
|
|
@@ -314,5 +352,19 @@ Commands:
|
|
|
314
352
|
pull-org Fetch Zodiac users and accounts, generate TypeScript types
|
|
315
353
|
pull-contracts Fetch contract ABIs, generate typed permissions kit
|
|
316
354
|
pull Fetch Zodiac org and contracts ABI, generate SDK functions
|
|
355
|
+
push [entrypoint] Push the nodes an entrypoint exports and open them for review (--no-open to skip the browser)
|
|
317
356
|
help [command] display help for command
|
|
318
357
|
```
|
|
358
|
+
|
|
359
|
+
`zodiac push` imports the entrypoint (`constellation/index.ts` by default),
|
|
360
|
+
pushes every named export as a node, and prints where each constellation can be
|
|
361
|
+
reviewed. When Zodiac refuses a constellation, it prints what was refused and
|
|
362
|
+
exits with code 1. A project whose entrypoint relies on globals it sets up
|
|
363
|
+
itself runs that setup first and then calls the same command:
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
import './globals'
|
|
367
|
+
import { pushEntrypoint } from '@zodiaceco/sdk/cli/push'
|
|
368
|
+
|
|
369
|
+
await pushEntrypoint({ entrypoint: process.argv[2] })
|
|
370
|
+
```
|
|
@@ -23,11 +23,23 @@ type AllowanceSpec = RealBigints<ApiAllowanceSpec>;
|
|
|
23
23
|
* parameters and labels, never compiled permissions. They are expanded into
|
|
24
24
|
* `{ targets, annotations }` when the constellation is deployed, so that a
|
|
25
25
|
* stored revision always compiles through the current compilers.
|
|
26
|
+
*
|
|
27
|
+
* A role carrying `policy` is a policy in Zodiac. Its key is the policy's
|
|
28
|
+
* identity, so the same key on several Roles mods is one policy, active on
|
|
29
|
+
* each of their vaults. Only a policy has a `description`.
|
|
26
30
|
*/
|
|
27
31
|
type RoleDef = {
|
|
28
32
|
members: readonly AddressOrRef[];
|
|
29
33
|
permissions: readonly PermissionEntry[];
|
|
30
|
-
}
|
|
34
|
+
} & ({
|
|
35
|
+
/** Marks the role as a policy, labelled with this value. Never reaches
|
|
36
|
+
* the chain. */
|
|
37
|
+
policy: string; /** Describes the policy in Zodiac. Never reaches the chain. */
|
|
38
|
+
description?: string;
|
|
39
|
+
} | {
|
|
40
|
+
policy?: undefined;
|
|
41
|
+
description?: undefined;
|
|
42
|
+
});
|
|
31
43
|
type User = {
|
|
32
44
|
id: UUID;
|
|
33
45
|
fullName: string;
|
|
@@ -414,4 +426,4 @@ type DeFiKitVerb<Verb> = Verb extends ((params: infer Params) => unknown) ? (par
|
|
|
414
426
|
}) => DeFiKitEntry;
|
|
415
427
|
//#endregion
|
|
416
428
|
export { PermissionEntry as a, ConstellationNode as c, transfer as i, constellation as l, defikit as n, PermissionTarget as o, swap as r, Permissions as s, custom as t };
|
|
417
|
-
//# sourceMappingURL=actions-
|
|
429
|
+
//# sourceMappingURL=actions-Cri7InV-.d.mts.map
|
package/dist/actions.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as PermissionEntry, i as transfer, n as defikit, o as PermissionTarget, r as swap, s as Permissions, t as custom } from "./actions-
|
|
1
|
+
import { a as PermissionEntry, i as transfer, n as defikit, o as PermissionTarget, r as swap, s as Permissions, t as custom } from "./actions-Cri7InV-.mjs";
|
|
2
2
|
export { PermissionEntry, PermissionTarget, Permissions, custom, defikit, swap, transfer };
|
package/dist/actions.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as recipientChains } from "./permissionEntries-
|
|
1
|
+
import { r as recipientChains } from "./permissionEntries-C4dAwZBb.mjs";
|
|
2
2
|
//#region src/actions.ts
|
|
3
3
|
/**
|
|
4
4
|
* Allows signing CoW orders selling any of `sell` for any of `buy`. The
|
|
@@ -20,17 +20,22 @@ const swap = ({ label, sell, buy }) => ({
|
|
|
20
20
|
* optional on their own; a transfer names at least one recipient somewhere.
|
|
21
21
|
*/
|
|
22
22
|
const transfer = ({ label, tokens, to = [], bridge = [], allowance }) => {
|
|
23
|
+
const bridges = bridge.map(toBridgeTarget);
|
|
24
|
+
assertOneTargetPerChain(bridges);
|
|
23
25
|
const entry = {
|
|
24
26
|
label,
|
|
25
27
|
action: {
|
|
26
28
|
type: "transfer",
|
|
27
29
|
tokens: [...tokens],
|
|
28
30
|
to: to.map(toAddress),
|
|
29
|
-
...
|
|
31
|
+
...bridges.length > 0 && { bridge: bridges },
|
|
30
32
|
...allowance != null && { allowance: allowanceKey(allowance) }
|
|
31
33
|
}
|
|
32
34
|
};
|
|
33
35
|
const chains = to.flatMap((recipient) => typeof recipient === "string" || recipient.chain == null ? [] : [recipient.chain]);
|
|
36
|
+
const [chain] = chains;
|
|
37
|
+
const otherChain = chains.find((other) => other !== chain);
|
|
38
|
+
if (otherChain != null) throw new Error(`The recipients of "${label}" span chains "${chain}" and "${otherChain}". \`to\` names recipients on the role's own chain; use \`bridge\` for the others.`);
|
|
34
39
|
if (chains.length > 0) Object.defineProperty(entry, recipientChains, {
|
|
35
40
|
value: chains,
|
|
36
41
|
enumerable: false
|
|
@@ -76,6 +81,17 @@ const toBridgeTarget = ({ chain, to, receive }) => ({
|
|
|
76
81
|
to: to.map(toAddress),
|
|
77
82
|
receive: [...receive]
|
|
78
83
|
});
|
|
84
|
+
/**
|
|
85
|
+
* Every recipient of a bridge target may receive every token it names, so two
|
|
86
|
+
* targets on one chain would merge into one that allows more than either.
|
|
87
|
+
*/
|
|
88
|
+
const assertOneTargetPerChain = (bridges) => {
|
|
89
|
+
const chains = /* @__PURE__ */ new Set();
|
|
90
|
+
for (const { chain } of bridges) {
|
|
91
|
+
if (chains.has(chain)) throw new Error(`A transfer bridges to each chain once, but this one bridges to chain "${chain}" twice. Write a separate transfer for recipients that receive other tokens on the same chain.`);
|
|
92
|
+
chains.add(chain);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
79
95
|
const destinationChain = (chain, recipients) => {
|
|
80
96
|
let destination = chain;
|
|
81
97
|
for (const recipient of recipients) {
|
package/dist/actions.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.mjs","names":[],"sources":["../src/actions.ts"],"sourcesContent":["import type { Address, ChainId } from '@zodiaceco/api-types'\nimport type { allow as ethereumKit } from 'defi-kit/eth'\nimport { recipientChains } from './permissionEntries'\nimport type {\n AllowanceKey,\n ConstellationPermission,\n DeFiKitEntry,\n LabelledPermissions,\n PermissionEntry,\n PermissionTarget,\n SwapEntry,\n TransferEntry,\n} from './permissionEntries'\nimport type { AllowanceSpec } from './types'\n\nexport type { PermissionEntry, PermissionTarget }\nexport type { Permissions } from './permissionEntries'\n\n/**\n * Allows signing CoW orders selling any of `sell` for any of `buy`. The\n * parameters are compiled into permissions when the constellation is deployed,\n * never here — a stored revision that carried its own copy would go stale as\n * soon as the compiler changed.\n */\nexport const swap = ({ label, sell, buy }: SwapParams): SwapEntry => ({\n label,\n action: { type: 'swap', sell: [...sell], buy: [...buy] },\n})\n\n/**\n * Allows transferring any of `tokens` to any of `to` on the role's own chain,\n * and — through `bridge` — to recipients on other chains over Across. Both are\n * optional on their own; a transfer names at least one recipient somewhere.\n */\nexport const transfer = ({\n label,\n tokens,\n to = [],\n bridge = [],\n allowance,\n}: TransferParams): TransferEntry => {\n const entry: TransferEntry = {\n label,\n action: {\n type: 'transfer',\n tokens: [...tokens],\n to: to.map(toAddress),\n ...(
|
|
1
|
+
{"version":3,"file":"actions.mjs","names":[],"sources":["../src/actions.ts"],"sourcesContent":["import type { Address, ChainId } from '@zodiaceco/api-types'\nimport type { allow as ethereumKit } from 'defi-kit/eth'\nimport { recipientChains } from './permissionEntries'\nimport type {\n AllowanceKey,\n ConstellationPermission,\n DeFiKitEntry,\n LabelledPermissions,\n PermissionEntry,\n PermissionTarget,\n SwapEntry,\n TransferEntry,\n} from './permissionEntries'\nimport type { AllowanceSpec } from './types'\n\nexport type { PermissionEntry, PermissionTarget }\nexport type { Permissions } from './permissionEntries'\n\n/**\n * Allows signing CoW orders selling any of `sell` for any of `buy`. The\n * parameters are compiled into permissions when the constellation is deployed,\n * never here — a stored revision that carried its own copy would go stale as\n * soon as the compiler changed.\n */\nexport const swap = ({ label, sell, buy }: SwapParams): SwapEntry => ({\n label,\n action: { type: 'swap', sell: [...sell], buy: [...buy] },\n})\n\n/**\n * Allows transferring any of `tokens` to any of `to` on the role's own chain,\n * and — through `bridge` — to recipients on other chains over Across. Both are\n * optional on their own; a transfer names at least one recipient somewhere.\n */\nexport const transfer = ({\n label,\n tokens,\n to = [],\n bridge = [],\n allowance,\n}: TransferParams): TransferEntry => {\n const bridges = bridge.map(toBridgeTarget)\n\n assertOneTargetPerChain(bridges)\n\n const entry: TransferEntry = {\n label,\n action: {\n type: 'transfer',\n tokens: [...tokens],\n to: to.map(toAddress),\n ...(bridges.length > 0 && { bridge: bridges }),\n ...(allowance != null && { allowance: allowanceKey(allowance) }),\n },\n }\n\n // Remember which chains the `to` nodes live on so push() can refuse a\n // recipient from another chain. Symbol-keyed: never serialized.\n const chains = to.flatMap((recipient) =>\n typeof recipient === 'string' || recipient.chain == null\n ? []\n : [recipient.chain]\n )\n\n const [chain] = chains\n const otherChain = chains.find((other) => other !== chain)\n\n if (otherChain != null) {\n throw new Error(\n `The recipients of \"${label}\" span chains \"${chain}\" and \"${otherChain}\". \\`to\\` names recipients on the role's own chain; use \\`bridge\\` for the others.`\n )\n }\n\n if (chains.length > 0) {\n Object.defineProperty(entry, recipientChains, {\n value: chains,\n enumerable: false,\n })\n }\n\n return entry\n}\n\n/**\n * A labelled bag of `allow`-kit permissions — the code-side counterpart of the\n * app's Custom action, for everything the other action types don't cover. It\n * takes plain permissions, not other actions.\n */\nexport const custom = ({\n label,\n permissions,\n}: CustomParams): LabelledPermissions => ({\n label,\n permissions: [...permissions],\n})\n\n/**\n * The DeFi Kit allow kit — same protocol, verb and parameter surface, plus a\n * `label`. A DeFi Kit entry is nothing but its annotation: the permissions are\n * fetched from the annotation's uri at deploy, so nothing is fetched here.\n *\n * Protocols and parameters are typed against the Ethereum kit, the widest of\n * the chains DeFi Kit serves. Values that only exist on another chain still\n * type-check; the API validates them when the annotation is resolved.\n */\nexport const defikit: DeFiKit = new Proxy({} as DeFiKit, {\n get: (_, protocol: string) =>\n new Proxy(\n {},\n {\n get:\n (_, verb: string) =>\n ({ label, ...params }: { label: string }) => ({\n label,\n defiKit: { protocol, verb, params },\n }),\n }\n ),\n})\n\nconst allowanceKey = (allowance: AllowanceSpec | AllowanceKey) =>\n typeof allowance === 'string' ? allowance : allowance.key\n\nconst toAddress = (recipient: Recipient): Address =>\n typeof recipient === 'string' ? recipient : recipient.address\n\n/**\n * A node knows the chain it lives on, so a bridge target written with nodes\n * needs no `chain` of its own. Naming one anyway is allowed — a plain address\n * carries no chain — but a node that disagrees with it is a mistake worth\n * catching here rather than at deploy.\n */\nconst toBridgeTarget = ({ chain, to, receive }: BridgeTarget) => ({\n chain: destinationChain(chain, to),\n to: to.map(toAddress),\n receive: [...receive],\n})\n\n/**\n * Every recipient of a bridge target may receive every token it names, so two\n * targets on one chain would merge into one that allows more than either.\n */\nconst assertOneTargetPerChain = (bridges: { chain: ChainId }[]) => {\n const chains = new Set<ChainId>()\n\n for (const { chain } of bridges) {\n if (chains.has(chain)) {\n throw new Error(\n `A transfer bridges to each chain once, but this one bridges to chain \"${chain}\" twice. Write a separate transfer for recipients that receive other tokens on the same chain.`\n )\n }\n\n chains.add(chain)\n }\n}\n\nconst destinationChain = (\n chain: ChainId | undefined,\n recipients: readonly Recipient[]\n): ChainId => {\n let destination = chain\n\n for (const recipient of recipients) {\n if (typeof recipient === 'string' || recipient.chain == null) {\n continue\n }\n\n if (destination != null && destination !== recipient.chain) {\n throw new Error(\n `A bridge target has one destination, but this one spans chains \"${destination}\" and \"${recipient.chain}\"`\n )\n }\n\n destination = recipient.chain\n }\n\n if (destination == null) {\n throw new Error(\n 'A bridge target needs a `chain` unless its recipients are nodes that know one'\n )\n }\n\n return destination\n}\n\ntype SwapParams = {\n /** Names the action in the app. Never reaches the chain. */\n label: string\n /** Tokens the role may sell. */\n sell: readonly Address[]\n /** Tokens the role may buy. */\n buy: readonly Address[]\n}\n\ntype TransferParams = {\n /** Names the action in the app. Never reaches the chain. */\n label: string\n /** Tokens the role may transfer. Use the zero address for the native token. */\n tokens: readonly Address[]\n /** Addresses the role may transfer to on the role's own chain. A node stands\n * for its address, so only nodes that already have one — an account from your\n * codegen, or one bound by address — can be named here. */\n to?: readonly Recipient[]\n /** Destinations on other chains, bridged over Across. */\n bridge?: readonly BridgeTarget[]\n /** Allowance capping what may be sent. Pass the allowance declared on the\n * roles modifier, or its key. */\n allowance?: AllowanceSpec | AllowanceKey\n}\n\ntype CustomParams = {\n /** Names the action in the app. Never reaches the chain. */\n label: string\n permissions: readonly ConstellationPermission[]\n}\n\n/** An address, or a node that already knows the address it lives at. */\ntype Recipient =\n | Address\n | { readonly address: Address; readonly chain?: ChainId }\n\ntype BridgeTarget = {\n /** The destination chain. Inferred from the recipients when they are nodes. */\n chain?: ChainId\n /** Addresses the role may bridge to on the destination chain. */\n to: readonly Recipient[]\n /** Tokens the role may receive on the destination chain. */\n receive: readonly Address[]\n}\n\ntype EthereumKit = typeof ethereumKit\n\ntype DeFiKit = {\n readonly [Protocol in keyof EthereumKit]: {\n readonly [Verb in keyof EthereumKit[Protocol]]: DeFiKitVerb<\n EthereumKit[Protocol][Verb]\n >\n }\n}\n\ntype DeFiKitVerb<Verb> = Verb extends (params: infer Params) => unknown\n ? (params: Params & { label: string }) => DeFiKitEntry\n : (params: { label: string }) => DeFiKitEntry\n"],"mappings":";;;;;;;;AAwBA,MAAa,QAAQ,EAAE,OAAO,MAAM,WAAkC;CACpE;CACA,QAAQ;EAAE,MAAM;EAAQ,MAAM,CAAC,GAAG,KAAK;EAAE,KAAK,CAAC,GAAG,IAAI;EAAE;CACzD;;;;;;AAOD,MAAa,YAAY,EACvB,OACA,QACA,KAAK,EAAE,EACP,SAAS,EAAE,EACX,gBACmC;CACnC,MAAM,UAAU,OAAO,IAAI,eAAe;AAE1C,yBAAwB,QAAQ;CAEhC,MAAM,QAAuB;EAC3B;EACA,QAAQ;GACN,MAAM;GACN,QAAQ,CAAC,GAAG,OAAO;GACnB,IAAI,GAAG,IAAI,UAAU;GACrB,GAAI,QAAQ,SAAS,KAAK,EAAE,QAAQ,SAAS;GAC7C,GAAI,aAAa,QAAQ,EAAE,WAAW,aAAa,UAAU,EAAE;GAChE;EACF;CAID,MAAM,SAAS,GAAG,SAAS,cACzB,OAAO,cAAc,YAAY,UAAU,SAAS,OAChD,EAAE,GACF,CAAC,UAAU,MAAM,CACtB;CAED,MAAM,CAAC,SAAS;CAChB,MAAM,aAAa,OAAO,MAAM,UAAU,UAAU,MAAM;AAE1D,KAAI,cAAc,KAChB,OAAM,IAAI,MACR,sBAAsB,MAAM,iBAAiB,MAAM,SAAS,WAAW,oFACxE;AAGH,KAAI,OAAO,SAAS,EAClB,QAAO,eAAe,OAAO,iBAAiB;EAC5C,OAAO;EACP,YAAY;EACb,CAAC;AAGJ,QAAO;;;;;;;AAQT,MAAa,UAAU,EACrB,OACA,mBACwC;CACxC;CACA,aAAa,CAAC,GAAG,YAAY;CAC9B;;;;;;;;;;AAWD,MAAa,UAAmB,IAAI,MAAM,EAAE,EAAa,EACvD,MAAM,GAAG,aACP,IAAI,MACF,EAAE,EACF,EACE,MACG,GAAG,UACH,EAAE,OAAO,GAAG,cAAiC;CAC5C;CACA,SAAS;EAAE;EAAU;EAAM;EAAQ;CACpC,GACJ,CACF,EACJ,CAAC;AAEF,MAAM,gBAAgB,cACpB,OAAO,cAAc,WAAW,YAAY,UAAU;AAExD,MAAM,aAAa,cACjB,OAAO,cAAc,WAAW,YAAY,UAAU;;;;;;;AAQxD,MAAM,kBAAkB,EAAE,OAAO,IAAI,eAA6B;CAChE,OAAO,iBAAiB,OAAO,GAAG;CAClC,IAAI,GAAG,IAAI,UAAU;CACrB,SAAS,CAAC,GAAG,QAAQ;CACtB;;;;;AAMD,MAAM,2BAA2B,YAAkC;CACjE,MAAM,yBAAS,IAAI,KAAc;AAEjC,MAAK,MAAM,EAAE,WAAW,SAAS;AAC/B,MAAI,OAAO,IAAI,MAAM,CACnB,OAAM,IAAI,MACR,yEAAyE,MAAM,gGAChF;AAGH,SAAO,IAAI,MAAM;;;AAIrB,MAAM,oBACJ,OACA,eACY;CACZ,IAAI,cAAc;AAElB,MAAK,MAAM,aAAa,YAAY;AAClC,MAAI,OAAO,cAAc,YAAY,UAAU,SAAS,KACtD;AAGF,MAAI,eAAe,QAAQ,gBAAgB,UAAU,MACnD,OAAM,IAAI,MACR,mEAAmE,YAAY,SAAS,UAAU,MAAM,GACzG;AAGH,gBAAc,UAAU;;AAG1B,KAAI,eAAe,KACjB,OAAM,IAAI,MACR,gFACD;AAGH,QAAO"}
|
package/dist/allow/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as EVERYTHING, c as Scoping, i as ConditionFunction, l as TargetPermission, n as ChainPrefix, o as FunctionPermission, r as chainIdFor, s as Options, t as CHAIN_IDS, u as buildAllowKit } from "../index-
|
|
1
|
+
import { a as EVERYTHING, c as Scoping, i as ConditionFunction, l as TargetPermission, n as ChainPrefix, o as FunctionPermission, r as chainIdFor, s as Options, t as CHAIN_IDS, u as buildAllowKit } from "../index-D49x1ozw.mjs";
|
|
2
2
|
export { CHAIN_IDS, ChainPrefix, ConditionFunction, EVERYTHING, FunctionPermission, Options, Scoping, TargetPermission, buildAllowKit, chainIdFor };
|
package/dist/allow/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { n as chainIdFor, t as CHAIN_IDS } from "../networks-
|
|
2
|
-
import { n as EVERYTHING, t as buildAllowKit } from "../allow-
|
|
1
|
+
import { n as chainIdFor, t as CHAIN_IDS } from "../networks-UKtJ8QDG.mjs";
|
|
2
|
+
import { n as EVERYTHING, t as buildAllowKit } from "../allow-DX3iyizB.mjs";
|
|
3
3
|
export { CHAIN_IDS, EVERYTHING, buildAllowKit, chainIdFor };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as walkContracts, i as readAbi } from "./networks-
|
|
1
|
+
import { a as walkContracts, i as readAbi } from "./networks-UKtJ8QDG.mjs";
|
|
2
2
|
import { Operator, ParameterType, c, coercePermission } from "zodiac-roles-sdk";
|
|
3
3
|
import { Interface, isError } from "ethers";
|
|
4
4
|
//#region src/allow/types.ts
|
|
@@ -129,4 +129,4 @@ const applyOptions = (permission, options) => {
|
|
|
129
129
|
//#endregion
|
|
130
130
|
export { EVERYTHING as n, buildAllowKit as t };
|
|
131
131
|
|
|
132
|
-
//# sourceMappingURL=allow-
|
|
132
|
+
//# sourceMappingURL=allow-DX3iyizB.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"allow-CrDzDtxx.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('@zodiaceco/allow-kit/EVERYTHING')\nexport type EVERYTHING = typeof EVERYTHING\n","import { Interface, FunctionFragment, isError, type InterfaceAbi } from 'ethers'\nimport { c, coercePermission, ParameterType, Operator } from 'zodiac-roles-sdk'\nimport type {\n Condition,\n FunctionPermission,\n TargetPermission,\n} from 'zodiac-roles-sdk'\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(\n node.address,\n abi as InterfaceAbi,\n [node.chain, ...node.segments].join('.')\n )\n )\n }\n return kit\n}\n\nfunction attachAt(root: Record<string, any>, segments: string[], value: any) {\n let cursor = root\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i]!\n if (!(seg in cursor)) cursor[seg] = {}\n cursor = cursor[seg]\n }\n cursor[segments[segments.length - 1]!] = value\n}\n\nfunction missingAbiProxy(node: ContractNode) {\n const explain = () => {\n throw new Error(\n `ABI missing for ${node.chain}.${node.segments.join('.')} ` +\n `(${node.address}). Run \\`zodiac pull-contracts\\` to fetch it, or ` +\n `paste the ABI JSON manually at <abisDir>/${node.chain}/${node.segments.join('/')}.json`\n )\n }\n return new Proxy(\n {},\n {\n get: explain,\n has: explain,\n }\n )\n}\n\nfunction makeAllowContract(\n address: `0x${string}`,\n abi: InterfaceAbi,\n path: string\n): Record<string | symbol, any> {\n const iface = Interface.from(abi)\n const lowerAddr = address.toLowerCase() as `0x${string}`\n\n // Permissionable functions grouped by bare name, so an ambiguous one can be\n // told apart from an unknown one. `Interface.getFunction` reports both as\n // INVALID_ARGUMENT, and the two want opposite answers: an unknown name is\n // simply not a member, an ambiguous one is a member the caller has not\n // finished naming.\n const overloads = new Map<string, FunctionFragment[]>()\n iface.forEachFunction((fn) => {\n if (fn.stateMutability === 'view' || fn.stateMutability === 'pure') return\n overloads.set(fn.name, [...(overloads.get(fn.name) ?? []), fn])\n })\n\n const ambiguous = (prop: string) => (overloads.get(prop)?.length ?? 0) > 1\n\n const explainAmbiguity = (prop: string): never => {\n const signatures = overloads.get(prop)!.map((fn) => fn.format('sighash'))\n throw new Error(\n `\\`allow.${path}.${prop}\\` is ambiguous: ${address} has ` +\n `${signatures.length} functions named \"${prop}\". Name the overload ` +\n `you mean by its full signature — ` +\n signatures\n .map((sig) => `allow.${path}[${JSON.stringify(sig)}]`)\n .join(' or ') +\n `.`\n )\n }\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 (ambiguous(prop)) explainAmbiguity(prop)\n if (!has(prop)) return undefined\n const fn = iface.getFunction(prop)!\n return makeAllowFunction(fn, lowerAddr)\n },\n // An ambiguous bare name is not a member — only the full signatures are.\n // `in` answers that question; reaching for the property explains it.\n has: (_target, prop) => {\n if (prop === EVERYTHING) return true\n return typeof prop === 'string' && !ambiguous(prop) && 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
|
+
{"version":3,"file":"allow-DX3iyizB.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('@zodiaceco/allow-kit/EVERYTHING')\nexport type EVERYTHING = typeof EVERYTHING\n","import { Interface, FunctionFragment, isError, type InterfaceAbi } from 'ethers'\nimport { c, coercePermission, ParameterType, Operator } from 'zodiac-roles-sdk'\nimport type {\n Condition,\n FunctionPermission,\n TargetPermission,\n} from 'zodiac-roles-sdk'\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(\n node.address,\n abi as InterfaceAbi,\n [node.chain, ...node.segments].join('.')\n )\n )\n }\n return kit\n}\n\nfunction attachAt(root: Record<string, any>, segments: string[], value: any) {\n let cursor = root\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i]!\n if (!(seg in cursor)) cursor[seg] = {}\n cursor = cursor[seg]\n }\n cursor[segments[segments.length - 1]!] = value\n}\n\nfunction missingAbiProxy(node: ContractNode) {\n const explain = () => {\n throw new Error(\n `ABI missing for ${node.chain}.${node.segments.join('.')} ` +\n `(${node.address}). Run \\`zodiac pull-contracts\\` to fetch it, or ` +\n `paste the ABI JSON manually at <abisDir>/${node.chain}/${node.segments.join('/')}.json`\n )\n }\n return new Proxy(\n {},\n {\n get: explain,\n has: explain,\n }\n )\n}\n\nfunction makeAllowContract(\n address: `0x${string}`,\n abi: InterfaceAbi,\n path: string\n): Record<string | symbol, any> {\n const iface = Interface.from(abi)\n const lowerAddr = address.toLowerCase() as `0x${string}`\n\n // Permissionable functions grouped by bare name, so an ambiguous one can be\n // told apart from an unknown one. `Interface.getFunction` reports both as\n // INVALID_ARGUMENT, and the two want opposite answers: an unknown name is\n // simply not a member, an ambiguous one is a member the caller has not\n // finished naming.\n const overloads = new Map<string, FunctionFragment[]>()\n iface.forEachFunction((fn) => {\n if (fn.stateMutability === 'view' || fn.stateMutability === 'pure') return\n overloads.set(fn.name, [...(overloads.get(fn.name) ?? []), fn])\n })\n\n const ambiguous = (prop: string) => (overloads.get(prop)?.length ?? 0) > 1\n\n const explainAmbiguity = (prop: string): never => {\n const signatures = overloads.get(prop)!.map((fn) => fn.format('sighash'))\n throw new Error(\n `\\`allow.${path}.${prop}\\` is ambiguous: ${address} has ` +\n `${signatures.length} functions named \"${prop}\". Name the overload ` +\n `you mean by its full signature — ` +\n signatures\n .map((sig) => `allow.${path}[${JSON.stringify(sig)}]`)\n .join(' or ') +\n `.`\n )\n }\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 (ambiguous(prop)) explainAmbiguity(prop)\n if (!has(prop)) return undefined\n const fn = iface.getFunction(prop)!\n return makeAllowFunction(fn, lowerAddr)\n },\n // An ambiguous bare name is not a member — only the full signatures are.\n // `in` answers that question; reaching for the property explains it.\n has: (_target, prop) => {\n if (prop === EVERYTHING) return true\n return typeof prop === 'string' && !ambiguous(prop) && 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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"}
|
package/dist/cli/config.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as resolveAbisDir, i as loadConfig, n as defineConfig, r as ensureConfigStub, t as DEFAULT_ABIS_DIR } from "../config-
|
|
1
|
+
import { a as resolveAbisDir, i as loadConfig, n as defineConfig, r as ensureConfigStub, t as DEFAULT_ABIS_DIR } from "../config-BaChz2bq.mjs";
|
|
2
2
|
export { DEFAULT_ABIS_DIR, defineConfig, ensureConfigStub, loadConfig, resolveAbisDir };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/cli/commands/push.d.ts
|
|
2
|
+
type PushEntrypointOptions = {
|
|
3
|
+
/** The module whose named exports are the nodes to push, relative to the
|
|
4
|
+
* working directory. @default constellation/index.ts in the project root */
|
|
5
|
+
entrypoint?: string; /** Open every pushed constellation's review page. @default true */
|
|
6
|
+
openInBrowser?: boolean; /** The config file whose API key to push with, as for `zodiac pull`. */
|
|
7
|
+
config?: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Pushes every node an entrypoint exports — each export name becomes the
|
|
11
|
+
* node's ref — and prints where each constellation can be reviewed.
|
|
12
|
+
*
|
|
13
|
+
* A constellation Zodiac refuses is printed as the list of what it refused,
|
|
14
|
+
* and the process exits with code 1. Projects that set up globals the
|
|
15
|
+
* entrypoint relies on run their setup first and call this afterwards.
|
|
16
|
+
*/
|
|
17
|
+
declare const pushEntrypoint: ({
|
|
18
|
+
entrypoint,
|
|
19
|
+
openInBrowser,
|
|
20
|
+
config: configPath
|
|
21
|
+
}?: PushEntrypointOptions) => Promise<void>;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { pushEntrypoint };
|
|
24
|
+
//# sourceMappingURL=push.d.mts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { n as push, t as ConstellationRejectedError } from "../push-DdnHsLpO.mjs";
|
|
2
|
+
import { i as loadConfig } from "../config-BaChz2bq.mjs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import open from "open";
|
|
6
|
+
//#region src/cli/commands/push.ts
|
|
7
|
+
const DEFAULT_ENTRYPOINT = "constellation/index.ts";
|
|
8
|
+
/**
|
|
9
|
+
* Pushes every node an entrypoint exports — each export name becomes the
|
|
10
|
+
* node's ref — and prints where each constellation can be reviewed.
|
|
11
|
+
*
|
|
12
|
+
* A constellation Zodiac refuses is printed as the list of what it refused,
|
|
13
|
+
* and the process exits with code 1. Projects that set up globals the
|
|
14
|
+
* entrypoint relies on run their setup first and call this afterwards.
|
|
15
|
+
*/
|
|
16
|
+
const pushEntrypoint = async ({ entrypoint, openInBrowser = true, config: configPath } = {}) => {
|
|
17
|
+
const { apiKey, rootDir } = await loadConfig(configPath);
|
|
18
|
+
const entrypointPath = entrypoint == null ? resolve(rootDir, DEFAULT_ENTRYPOINT) : resolve(process.cwd(), entrypoint);
|
|
19
|
+
const { default: defaultExport, ...nodes } = await import(pathToFileURL(entrypointPath).href);
|
|
20
|
+
if (defaultExport !== void 0) console.warn(`warning: ${entrypointPath} has a default export which will be ignored. Use named exports — each export becomes a ref in the pushed spec.`);
|
|
21
|
+
try {
|
|
22
|
+
const results = await push(nodes, { apiKey });
|
|
23
|
+
for (const { url } of results) {
|
|
24
|
+
console.log(`Pushed constellation to: ${url}`);
|
|
25
|
+
if (openInBrowser) await open(url);
|
|
26
|
+
}
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (!(error instanceof ConstellationRejectedError)) throw error;
|
|
29
|
+
console.error(error.message);
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
export { pushEntrypoint };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=push.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.mjs","names":[],"sources":["../../src/cli/commands/push.ts"],"sourcesContent":["import { resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport open from 'open'\nimport { ConstellationRejectedError, push } from '../../push'\nimport { loadConfig } from '../config'\n\nconst DEFAULT_ENTRYPOINT = 'constellation/index.ts'\n\ntype PushEntrypointOptions = {\n /** The module whose named exports are the nodes to push, relative to the\n * working directory. @default constellation/index.ts in the project root */\n entrypoint?: string\n /** Open every pushed constellation's review page. @default true */\n openInBrowser?: boolean\n /** The config file whose API key to push with, as for `zodiac pull`. */\n config?: string\n}\n\n/**\n * Pushes every node an entrypoint exports — each export name becomes the\n * node's ref — and prints where each constellation can be reviewed.\n *\n * A constellation Zodiac refuses is printed as the list of what it refused,\n * and the process exits with code 1. Projects that set up globals the\n * entrypoint relies on run their setup first and call this afterwards.\n */\nexport const pushEntrypoint = async ({\n entrypoint,\n openInBrowser = true,\n config: configPath,\n}: PushEntrypointOptions = {}) => {\n const { apiKey, rootDir } = await loadConfig(configPath)\n const entrypointPath =\n entrypoint == null\n ? resolve(rootDir, DEFAULT_ENTRYPOINT)\n : resolve(process.cwd(), entrypoint)\n const { default: defaultExport, ...nodes } = await import(\n pathToFileURL(entrypointPath).href\n )\n\n if (defaultExport !== undefined) {\n console.warn(\n `warning: ${entrypointPath} has a default export which will be ignored. ` +\n `Use named exports — each export becomes a ref in the pushed spec.`\n )\n }\n\n try {\n const results = await push(nodes, { apiKey })\n\n for (const { url } of results) {\n console.log(`Pushed constellation to: ${url}`)\n\n if (openInBrowser) {\n await open(url)\n }\n }\n } catch (error) {\n if (!(error instanceof ConstellationRejectedError)) {\n throw error\n }\n\n console.error(error.message)\n process.exitCode = 1\n }\n}\n"],"mappings":";;;;;;AAMA,MAAM,qBAAqB;;;;;;;;;AAoB3B,MAAa,iBAAiB,OAAO,EACnC,YACA,gBAAgB,MAChB,QAAQ,eACiB,EAAE,KAAK;CAChC,MAAM,EAAE,QAAQ,YAAY,MAAM,WAAW,WAAW;CACxD,MAAM,iBACJ,cAAc,OACV,QAAQ,SAAS,mBAAmB,GACpC,QAAQ,QAAQ,KAAK,EAAE,WAAW;CACxC,MAAM,EAAE,SAAS,eAAe,GAAG,UAAU,MAAM,OACjD,cAAc,eAAe,CAAC;AAGhC,KAAI,kBAAkB,KAAA,EACpB,SAAQ,KACN,YAAY,eAAe,gHAE5B;AAGH,KAAI;EACF,MAAM,UAAU,MAAM,KAAK,OAAO,EAAE,QAAQ,CAAC;AAE7C,OAAK,MAAM,EAAE,SAAS,SAAS;AAC7B,WAAQ,IAAI,4BAA4B,MAAM;AAE9C,OAAI,cACF,OAAM,KAAK,IAAI;;UAGZ,OAAO;AACd,MAAI,EAAE,iBAAiB,4BACrB,OAAM;AAGR,UAAQ,MAAM,MAAM,QAAQ;AAC5B,UAAQ,WAAW"}
|
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { a as
|
|
2
|
+
import { t as resolveZodiacDir } from "./paths-LsD6ULk0.mjs";
|
|
3
|
+
import { r as ApiClient } from "./push-DdnHsLpO.mjs";
|
|
4
|
+
import { a as walkContracts, i as readAbi, n as chainIdFor, o as writeAbi, r as abiFilePath } from "./networks-UKtJ8QDG.mjs";
|
|
5
|
+
import { a as resolveAbisDir, i as loadConfig, o as findProjectRoot, r as ensureConfigStub } from "./config-BaChz2bq.mjs";
|
|
6
|
+
import { pushEntrypoint } from "./cli/push.mjs";
|
|
5
7
|
import fs, { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
8
|
import path, { basename, dirname, join, resolve } from "node:path";
|
|
7
9
|
import { getAddress } from "ethers";
|
|
@@ -703,14 +705,14 @@ const loadConfigOrInit = (configPath) => loadConfig(configPath, {
|
|
|
703
705
|
});
|
|
704
706
|
const run = async (argv = process.argv) => {
|
|
705
707
|
const program = new Command();
|
|
706
|
-
program.name("zodiac").description("Zodiac SDK CLI – pull org data and contract ABIs").version(ownVersion()).option("-c, --config <path>", "path to the config file", "zodiac.config.ts");
|
|
708
|
+
program.name("zodiac").description("Zodiac SDK CLI – pull org data and contract ABIs, push constellations").version(ownVersion()).option("-c, --config <path>", "path to the config file", "zodiac.config.ts");
|
|
707
709
|
let updateNotice = Promise.resolve(null);
|
|
708
710
|
program.hook("preAction", () => {
|
|
709
711
|
updateNotice = checkForUpdate();
|
|
710
712
|
});
|
|
711
713
|
program.hook("postAction", async () => {
|
|
712
714
|
const notice = await updateNotice;
|
|
713
|
-
if (notice != null) console.warn(`\n${notice}`);
|
|
715
|
+
if (notice != null && !process.exitCode) console.warn(`\n${notice}`);
|
|
714
716
|
});
|
|
715
717
|
program.command("pull").description("Fetch Zodiac org data and contract ABIs, generating typed SDK functions. Authorizes this directory automatically on first run.").action(async (_opts, cmd) => {
|
|
716
718
|
const config = await loadConfigOrInit(cmd.optsWithGlobals().config);
|
|
@@ -722,6 +724,13 @@ const run = async (argv = process.argv) => {
|
|
|
722
724
|
program.command("pull-contracts").description("Fetch contract ABIs, generate typed permissions kit").action(async (_opts, cmd) => {
|
|
723
725
|
await pullContracts(await loadConfigOrInit(cmd.optsWithGlobals().config));
|
|
724
726
|
});
|
|
727
|
+
program.command("push").description("Push the nodes an entrypoint exports to Zodiac and open them for review. Projects that set up globals for their entrypoint call `pushEntrypoint` from `@zodiaceco/sdk/cli/push` after that setup instead.").argument("[entrypoint]", "module exporting the nodes (default: constellation/index.ts in the project root)").option("--no-open", "do not open the review pages in a browser").action(async (entrypoint, opts, cmd) => {
|
|
728
|
+
await pushEntrypoint({
|
|
729
|
+
entrypoint,
|
|
730
|
+
openInBrowser: opts.open,
|
|
731
|
+
config: cmd.optsWithGlobals().config
|
|
732
|
+
});
|
|
733
|
+
});
|
|
725
734
|
program.command("init").description("Re-authorize this directory / mint a new API key (run automatically by `pull` on first use).").option("--app-url <url>", "Override the Zodiac app URL (defaults to ZODIAC_APP_URL or app.zodiac.eco)").action(async (opts) => {
|
|
726
735
|
await init({ appUrl: opts.appUrl });
|
|
727
736
|
});
|
|
@@ -730,7 +739,7 @@ const run = async (argv = process.argv) => {
|
|
|
730
739
|
//#endregion
|
|
731
740
|
//#region src/cli/index.ts
|
|
732
741
|
run().then(() => {
|
|
733
|
-
process.exit(0);
|
|
742
|
+
process.exit(process.exitCode ?? 0);
|
|
734
743
|
}, (error) => {
|
|
735
744
|
if (error) console.error(error);
|
|
736
745
|
process.exit(1);
|