Skip to Content
DevelopersRolesAllowances

Allowances

An allowance is a quota that permissions consume. Use allowances for spending limits and rate limits. An allowance can refill on a schedule.

In the Zodiac App , you set spending limits directly on a policy, with intervals such as daily, weekly, and monthly. The app manages allowances for Roles Modifiers that a Zodiac account owns. This page explains the record behind those limits.

The allowance record

The contract stores each allowance under a bytes32 key:

struct Allowance { uint128 refill; // amount added per period uint128 maxRefill; // balance ceiling for refills uint64 period; // refill interval in seconds; 0 = one-time allowance uint128 balance; // unused amount, available now uint64 timestamp; // start of the last refilled interval }

The owner sets an allowance with:

setAllowance(bytes32 key, uint128 balance, uint128 maxRefill, uint128 refill, uint64 period, uint64 timestamp)

Two inputs have defaults: maxRefill = 0 becomes “no ceiling”, and timestamp = 0 becomes the current block time.

How refills work

The balance does not grow per block. It grows in steps, one step per full period:

  1. When a member uses the allowance, the contract counts the full periods since timestamp.
  2. It adds refill for each full period, up to maxRefill.
  3. It moves timestamp forward to the start of the current period.
  4. It subtracts the consumed amount from balance.

The consumption is final only when the whole transaction succeeds. A reverted transaction consumes nothing.

Worked example: 10,000 USDC per day

USDC has 6 decimals. The values for “10,000 USDC each day, no roll-over” are:

FieldValueReason
balance10_000_000_000The member can spend today.
refill10_000_000_000Each day adds 10,000 USDC.
maxRefill10_000_000_000The balance never exceeds one day of budget. Unused budget does not accumulate.
period86_400One day, in seconds.
timestamp0Start now.

Set maxRefill to a multiple of refill to let unused budget accumulate up to that ceiling. Set period = 0 for a one-time budget that never refills.

The three allowance operators

OperatorWhat it meters
WithinAllowanceAn integer parameter, for example a transfer amount.
EtherWithinAllowanceThe Ether value of the call.
CallWithinAllowanceThe number of calls. Each call consumes 1.

Several permissions can share one allowance key. They then draw from the same budget. This also works across targets: one “stablecoin budget” can meter USDC and DAI transfers together — but note that the raw integer amounts must be comparable.

Define allowances with the SDK

Declare the allowance and reference it from a permission. In a template project, c is a global, so nothing needs an import:

const USDC_DECIMALS = 6n const DAILY = 10_000n * 10n ** USDC_DECIMALS export const usdc_payouts = { key: 'usdc_payouts', refill: DAILY, maxRefill: DAILY, period: 60n * 60n * 24n, balance: DAILY, timestamp: 0n, }

The key is a plain label, the way a role key is, and is encoded to the contract’s bytes32 when the constellation is deployed. It has to fit in 32 bytes. In the record form of allowances, the record key is the identity — it decides what a partial update replaces and what null deletes — so key restates it and can be omitted.

The transfer() action meters itself with the allowance you hand it:

transfer({ label: 'Grant payouts', tokens: [USDC], to: [payee], allowance: usdc_payouts, })

A hand-written permission goes to the chain exactly as authored, so a condition that references an allowance needs the encoded key:

import { encodeKey } from '@zodiaceco/sdk' allow.eth.usdc.transfer( undefined, c.withinAllowance(encodeKey(usdc_payouts.key)), )

encodeKey returns an already-encoded key unchanged, so wrapping is always safe — and declaring key: encodeKey('usdc_payouts') stays valid if you prefer the key usable in conditions without wrapping.

Register the allowance on the Roles Modifier node of your constellation:

export const backendRoles = eth.roles['Backend Operator']({ // ... allowances: { usdc_payouts }, })

An allowance limits amounts, not destinations. Combine WithinAllowance with a condition on the recipient, or the member can send the full budget to any address.

Allowances and DeFi Kit presets

A DeFi Kit preset writes its own conditions, and most presets leave the amount parameters open. You cannot attach an allowance to a preset afterwards: a second permission for the same function widens access instead of tightening it — see One function, several conditions.

To meter an amount, author that function permission yourself with the allow kit, with WithinAllowance on the amount parameter. Do not put a preset that allows the same function openly into the same role: the open condition wins.

Last updated on