5. Roles & permissions
A role is the unit of delegation: a set of members (who can act) and a set
of permissions (what they can call). Each role lives in its own folder under
constellation/roles/.
constellation/roles/backend_operator/
├── index.ts # combines members + permissions
├── members.ts # who can act as this role
└── permissions.ts # what the role can callindex.ts just re-exports the two:
// roles/backend_operator/index.ts
export { default as members } from './members'
export { default as permissions } from './permissions'Members
Members are anything that resolves to an address — raw addresses, user accessors, or node references:
// roles/backend_operator/members.ts
export default [
'0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
eth.user['alice'],
eth.safe['Trading Bot'],
] satisfies MembersPermissions
Permissions are expressed with the allow kit (auto-generated from the
contracts in your zodiac.config.ts) or defi-kit for common DeFi presets.
Each entry scopes the role to a specific contract, function, and — optionally —
parameter constraints:
// roles/backend_operator/permissions.ts
import { allow as allowAction } from 'defi-kit/eth'
export default [
// exact functions on a known contract
allow.eth.weth.deposit({ send: true }),
allow.eth.weth.withdraw(),
// a DeFi preset
allowAction.aave_v3.deposit({
market: 'Core',
targets: ['WETH'],
}),
] satisfies PermissionsAnything you don’t allow reverts at execution time — enforced by the Roles
module onchain, not by operational trust.
Spending allowances
For value-bounded permissions, define a reusable allowance under
constellation/allowances/ and reference it with c.withinAllowance. An
allowance is a refillable budget — here, a rolling daily cap:
// constellation/allowances/index.ts
import { encodeKey } from 'zodiac-roles-sdk'
const USDC_DECIMALS = 6n
const DAILY = 10_000n * 10n ** USDC_DECIMALS
export const usdc_payouts = {
key: encodeKey('usdc_payouts'),
refill: DAILY,
maxRefill: DAILY,
period: 60n * 60n * 24n, // one day, in seconds
balance: DAILY,
timestamp: 0n,
}// roles/backend_operator/permissions.ts
import { usdc_payouts } from '../../allowances'
export default [
allow.eth.usdc.transfer(
undefined, // any recipient
c.withinAllowance(usdc_payouts.key),
),
] satisfies PermissionsThen register the allowance on the Roles node back in constellation/index.ts:
export const backendRoles = eth.roles['Backend Operator']({
nonce: 0n,
target: backendSafe,
owner: backendSafe,
avatar: backendSafe,
roles: { backend_operator },
allowances: { usdc_payouts },
})The role is fully described. Time to ship it → Review & deploy.