A mandate is a signed, bounded delegation of capability from one DID to another. Mandates are the agent-capability primitive on Sigil. They carry explicit spending caps, temporal limits, allowed transaction classes, and a revocation switch enforced by the executor. Mandates compose: a mandate holder can issue a sub-mandate with stricter policy. Revoking the parent revokes the entire subtree atomically.

Mandate shape

pub struct Mandate {
    pub id: [u8; 32],                 // blake3 of jcs(mandate)
    pub issuer: Did,
    pub holder: Did,
    pub parent_id: Option<[u8; 32]>,  // for sub-mandates
    pub policy: MandatePolicy,
    pub issued_at_height: u64,
    pub state: MandateState,
}

pub struct MandatePolicy {
    pub spending: Option<SpendingPolicy>,
    pub temporal: Option<TemporalPolicy>,
    pub allowed_kinds: Vec<TransactionKind>,
    pub lineage_depth_max: u32,       // 0 = holder cannot re-delegate
    pub conditions: Vec<ConditionExpr>,
}

Spending policy

KindDescription
TotalCapAbsolute total over the mandate’s lifetime.
DailyCapPer-24-hour ceiling, resets at midnight UTC.
EpochCapPer-epoch ceiling, resets every 1,000 blocks.
PerCallCapMaximum amount per single transaction.
Multiple caps can be combined; the executor enforces the intersection.

Temporal policy

KindDescription
ValidFromEarliest height the mandate is usable.
ValidUntilLatest height the mandate is usable.
ValidForDuration from issuance.
Temporal policy is enforced at admission; mandates outside their window reject before reaching the executor.

Allowed kinds

A list of transaction kinds the holder may sign while attaching the mandate. Common patterns:
  • DEX-only mandate: [DexSwapExactIn, DexSwapExactOut].
  • NFT-trade mandate: [TransferNft, BurnNft].
  • Treasury-disburse mandate: [DisburseFromTreasury].
  • Read-only mandate: empty list; useful only for capabilities like signed subscriptions.

Conditions

Conditions are deterministic expressions evaluated by the executor:
balance(issuer) >= 1000_MINT
height < 1024533
counterparty in {did:oas:sigil:agent:bob, did:oas:sigil:agent:carol}
The condition language is a subset of CEL (Common Expression Language) with no I/O, no looping, and a bounded evaluation depth.

Lifecycle

TransactionPurpose
IssueMandateIssue a new mandate.
DelegateMandateIssue a sub-mandate under an existing mandate.
RevokeMandateRevoke a mandate (and its subtree atomically).
ConsumeMandateAttach a mandate to a transaction the holder is signing.

Lineage

Sub-mandates inherit the parent’s policy intersected with their own. The holder of a parent mandate cannot grant a sub-mandate that exceeds the parent’s caps; the executor reduces the sub-policy to the intersection at issuance. lineage_depth_max bounds how deep sub-mandates can nest. A value of 0 means the holder cannot re-delegate at all.

Revocation

Revocation is atomic: a single RevokeMandate transaction marks the mandate and every descendant as revoked. Subsequent transactions attempting to use any of them reject with Unauthorised. The issuer may revoke at any time. Holders cannot prevent revocation; the design is permissioned toward the issuer.

Use cases

Use casePattern
Agent trading on behalf of a humanDEX-only mandate with daily cap.
Treasury operations under an org’s MHRDisburse-only mandate with per-call cap and condition counterparty in allowlist.
Service account with quarterly budgetDailyCap + ValidFor + RevokeOnAbuse.
Sub-agent delegationSub-mandate with lineage_depth_max = 0 and tighter caps.

RPC

MethodReturns
sigil_getMandateA single mandate record by id.
sigil_listMandatesByIssuerAll mandates issued by a DID.
sigil_listMandatesByHolderAll mandates held by a DID.
sigil_getMandateUsageCumulative spend and remaining caps.

Implementation

  • Types: node/sigil-core/src/mandate.rs.
  • Executor: node/sigil-node/src/executor_mandate.rs.
  • Durable state: node/sigil-node/src/mandate_durable.rs.

See also