Skip to Content
DevelopersConstellations

Constellations

A constellation is your organization as code. You declare every Zodiac object in TypeScript: the Safes, the Roles Modifiers, the members, the permissions, and the wiring between them. The SDK compares your declaration with the chain and applies the difference. You do not deploy contracts, encode calls, or wire modules yourself — the constellation is the wrapper around all of it.

This page explains the model. The Roles quickstart walks through it hands-on.

Why a constellation

  • One source of truth. The repository describes the whole setup. A pull request shows a permission change as a readable diff, before anything is onchain.
  • Reviewable apply. push computes the difference against the live chain state and opens it for review in the Zodiac App. Nothing changes until the Safe signs.
  • No contract plumbing. Deployments, enableModule wiring, and the configuration calls of the permission model are derived from your files.

The project

The template  gives you this layout:

constellation/ index.ts # entry point; only exported nodes get deployed allowances/ # shared allowance definitions roles/<role>/ index.ts # the role members.ts # who holds it permissions.ts # what it allows zodiac.config.ts # contract registry per chain abis/ # manual ABIs for unverified contracts and proxies .zodiac/ # generated code — do not edit .env # the API key — do not commit

The keys in zodiac.config.ts become your typed permission paths: a contract registered as eth: { weth: "0xC02a…" } appears as allow.eth.weth.* after pull-contracts. The CLI reads zodiac.config.ts from the project root; pass -c <path> for a different file.

Each non-view ABI function becomes a member named after it. When a contract overloads a name, no single member could mean either overload, so address the one you want by its full signature:

// sUSDS has both deposit(uint256,address) and deposit(uint256,address,uint16) allow.eth.susds['deposit(uint256,address)']( c.withinAllowance('savings'), c.avatar, )

The bracketed form is also how you write a permission for any function whose name is not a valid JavaScript identifier.

Nodes

A constellation is a set of nodes on one chain:

const eth = constellation({ workspace: 'Production', label: 'Ops', chain: 1 })

In a template project, constellation, allow, and c are globals, and Permissions and Members are global types. Do not import them.

NodeWhat it declares
eth.safe["Treasury"]A Safe: owners, threshold, enabled modules.
eth.roles["Ops Roles"]A Roles Modifier: its wiring (owner, avatar, target) and its roles, members, permissions, and allowances.
eth.user["alice"]A person in your workspace, usable wherever an address is expected.

Three rules carry the model:

  1. Reference or create. eth.safe["Treasury"] without arguments references what exists in your workspace. With arguments, the node declares state: nonce deploys a new instance, address binds an existing one — never both.
  2. Nodes compose. A roles node points its target at a Safe node — or at another modifier node, which is how the Delay composition is declared. Forward references resolve by label at push time.
  3. Export what you reference. push resolves referenced nodes by export name. Export every node that another node references, including a Safe that already exists. A missing export stops the push with the error: Node "…" is referenced but not included in the push() call.

Share the instance across files

The members.ts files need the constellation instance for eth.user["…"] accessors, but an import from index.ts creates an import cycle. Create the instance in its own module and import it from both sides:

// constellation/context.ts export const eth = constellation({ workspace: 'Production', label: 'Ops', chain: 1, })
// constellation/roles/ops/members.ts import { eth } from '../../context' export default [eth.user['alice']] satisfies Members

Constellations manage Safes, Roles Modifiers, and users today. The delay node appears in the Delay quickstart as a draft, ahead of the SDK release.

The apply loop

bun pull → edit the files → bun push → review in the Zodiac App → sign with the Safe
  • pull fetches your workspace state and contract ABIs, and regenerates .zodiac/.
  • push sends the declared state to the Zodiac API, which computes the calls against the current onchain state. You review the change set in the app and sign with the Safe.
  • The diff is per role: a role you mention is replaced, a role you omit stays untouched, and null clears a role key.

For a dry run in CI, send the same payload to POST /api/v1/workspace/<id>/constellation/resolve. The endpoint computes the change set and stores nothing.

Where the docs plug in

In your constellationThe page that explains it
permissions.ts entriesThe permission model and Conditions
A DeFi Kit preset in permissions.tsDeFi Kit presets
allowances/ definitionsAllowances
members.ts entriesMembers and role keys
The target of a roles nodeDelay composition
A role for a bot or an agentPermission an AI agent

Next steps

Last updated on