Granting a delegation
How a delegation is built, previewed, signed, and enabled - from intent to an on-chain Smart Sessions grant.
Granting a delegation is a build-then-sign flow, the same as every other state-changing action in Botanary. Nothing is executed or signed on your behalf - the backend only ever hands back an unsigned operation for you to sign.
The grant wizard
The delegation grant flow is a three-step wizard:
Who - pick the delegate (a dApp, an automation, a recurring payment, or a mandate for a hired
agent) and its kind.
Budget and scope - set the bounds: for a v1 delegation, budgets, a per-action max, an action count, recipients, and allowed actions; for a mandate, venues and per-venue spend caps.
Review - see the plain-language summary before you sign anything.
Build: from intent to an enforceable policy
Once you submit the wizard, the backend runs a fixed pipeline:
- Validate the intent -
buildPolicySetchecks that what you're asking for can actually be enforced on-chain, and throws if it can't. - Encode the session -
buildDelegationSessionencodes a real Smart Sessions session from that validated policy. - Render the summary -
renderPolicySummaryturns the encoded policy - not your raw request- into the plain-language summary you review.
- Return an unsigned operation - an unsigned enable UserOperation, plus a preview of what would happen if it were relayed right now.
You sign that operation yourself; the backend never holds a key that could sign it for you.
What you can bound (v1 delegations)
| Field | What it binds | Notes |
|---|---|---|
budgets | total spend per token | a lifetime cumulative total, never a per-period rate |
perActionMax | the max a single delegated action can move | optional |
maxActions | how many delegated actions can ever be taken | a lifetime count, never a rate |
recipientAllowlist | which addresses can receive funds | see recipient rules below |
allowedActions | which (contract, function) pairs the delegate can call | each entry must name at least one 4-byte function selector |
expiresAt | when the whole grant lapses | defaults to 365 days from now if omitted |
Recipients follow a specific escalation on-chain:
- 0 recipients - unbounded (any address may receive funds, within the other bounds).
- 1 recipient - enforced with a single equality rule.
- 2 to 16 recipients - enforced with a dedicated allowlist policy.
- More than 16 - not possible. 16 is the maximum number of recipients Smart Sessions can enforce on one delegation.
A token can only be granted to a delegation if it's independently marked delegatable for that chain - a stricter check than whether the token can simply be sent. Not every sendable token can be delegated.
Mandates (v2): venues and argument rules
A mandate (kind: 'mandate') is built from a different shape of intent: instead of budgets and
maxActions, it takes venues and a per-venue perActionMax array.
MandateVenue- a(target, selector)pair the mandate may call, with an optional name and argument rules.MandateArgRule- an argument-level bound within a venue: a max value at a given argument offset, or a required exact value. This is how, for example, a job's expected budget gets capped or a provider address gets pinned so the delegate can't self-deal.
Mandates are fail-closed by default: an empty venues list or an empty recipient list denies
every call, not "allows all" - the opposite of a v1 delegation's empty-allowlist default. Every
real bound a mandate enforces - which venues, which recipients, which per-action caps, which
argument rules - lives in the mandate executor's own on-chain storage for that specific mandate,
set through dedicated configuration calls rather than as ordinary session policies. The session
itself carries exactly one action: a single call into the mandate executor. All of that
configuration, plus enabling the session, is sent as one atomic operation - it can't partially
apply.
Every call made under a mandate must carry zero native value - a mandate can move tokens, but never attach native currency to a call.
Before you sign
If your account hasn't deployed yet, the grant operation carries the deploy data too - the account self-deploys and the session gets enabled in the same atomic operation. There's no separate "deploy your account first" step.
Once you've signed, relaying enables the delegation: the backend requires a real signature on the exact operation it built (an unsigned operation it never built can't be enabled), then waits a bounded window for a terminal on-chain result before recording the delegation. If that window passes without a final answer, it records the honest state - pending - rather than guessing that it succeeded.
See Delegations for what gets bound and how it's enforced, and Simulation for what that preview step actually checks.