A mandate is a signed, on-chain delegation: the controller authorises a subordinate DID to perform a specific subset of actions, subject to spend caps, temporal bounds, and lineage constraints. Mandates compose without amplification — a delegated DID cannot grant more authority than it received.

When to use this

  • Authorise an agent to spend up to N MINT/day on a specific token.
  • Delegate signing authority to a hot wallet while the cold key stays offline.
  • Grant short-lived approval for a one-off counterparty.

Prerequisites

  • Controller DID (the granter) is funded.
  • Subordinate DID (the grantee) exists.

Recipe

1

Define the mandate scope

Mandate actions reference transaction types from the chain. Common scopes:
const scope = {
  actions: ['Transfer', 'DexSwapExactIn'],
  constraints: {
    spend_cap_per_day: '10000000',     // 10 MINT/day
    spend_cap_total: '300000000',      // 300 MINT lifetime
    allowed_recipients: null,           // null = any; or a DID allowlist
    denied_recipients: [],
  },
  valid_from_height: null,             // null = now
  valid_until_height: 1_184_201,       // ~7 days at 6s blocks
};
The full action set lives in sigil-core/src/transaction.rs.
2

Issue the mandate

await signAndSend({
  IssueMandate: {
    mandate_id: 'mandate:agent-treasury-1',
    grantor_did: process.env.SIGIL_SENDER_DID,
    grantee_did: 'did:oas:sigil:agent:subordinate...',
    scope,
  },
});
The mandate id is unique per grantor. Once accepted, the grantee may submit transactions that match scope.actions and reference the mandate id.
3

Exercise the mandate

The grantee constructs a transaction normally, but adds a mandate_id field referencing the granted authority. The executor verifies the grantee’s signature plus the mandate’s scope before applying state.
// From the grantee's side:
await signAndSendAs(granteeKey, {
  Transfer: {
    recipient_did: 'did:oas:sigil:agent:vendor...',
    amount: 1_000_000,
    memo: 'vendor payment',
  },
}, { mandate_id: 'mandate:agent-treasury-1' });
Each exercise updates the mandate’s running spend counters; the chain rejects the transaction if any cap would be breached.
4

Revoke (grantor only)

Revocation is immediate.
await signAndSend({
  RevokeMandate: {
    mandate_id: 'mandate:agent-treasury-1',
  },
});
In-flight transactions referencing the revoked mandate fail with mandate revoked.

Inspecting a mandate

curl -s "$SIGIL_RPC_URL" -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"sigil_getMandate",
       "params":{"mandate_id":"mandate:agent-treasury-1"}}'
The response includes the scope, current usage counters, and remaining capacity.

Common errors

SymptomCauseFix
mandate scope exceededAction outside scope.actionsAdd the action when issuing, or split into a new mandate
spend cap exceededspend_cap_per_day or _total hitWait for daily reset, or issue a higher cap
grantor lineage shorter than granteeTried to grant authority a parent lineage does not haveIssue from a DID with the required lineage
mandate expiredvalid_until_height passedIssue a new mandate

Mandate composition rules

  • A grantee may further delegate only the subset they hold, never more.
  • Spend caps from parent mandates are enforced transitively.
  • Lineage constraints are intersected — a grandchild mandate cannot exceed the grandparent’s bounds.

See also