Grantline Docs
Enforcement

Action commitments, signatures and nonces

How Grantline commits an exact Action Plan to its authority domain and prevents it from being executed twice.


The Action Plan digest is Grantline's canonical commitment to the proposed action. It is derived from the complete ordered Action Plan and its EIP-712 domain, then used wherever the current contracts need to identify that exact plan.

Grantline uses EIP-712 to sign the complete Action Plan rather than an unstructured message. The digest is computed by ActionSignature and verified by MandateEvaluator before the executor can consume a nonce or call the Vault.

The canonical action commitment

The current digest includes:

  • the mandateId and agent in the plan;
  • the proposal nonce and deadline; and
  • the complete ordered action array, including each action's type, version, and exact parameter bytes.

The EIP-712 domain adds Grantline, version 1, the active chain ID, and the deployed MandateEvaluator address. Changing any signed plan value, the action order, the evaluator, or the chain changes the digest.

Mandate rules are not copied into the signed bytes. The signature binds the agent to the exact Action Plan and Mandate ID it uses; the evaluator reads the current Mandate rules, active lineage, validity window, and Preflight state when it checks the plan. A signature therefore does not freeze earlier permissions.

actionDigest is derived at the points that need it. It is not a field on ActionPlan, and the current EvaluationResult does not return it directly.

What the signature covers

The signed plan includes the Mandate ID, agent address, proposal nonce, deadline, and complete ordered action array. Each action contributes its type, version, and exact parameter bytes to the digest. Changing an action, its order, its parameters, its deadline, or its Mandate ID produces a different commitment.

The evaluator accepts a signature only when it recovers to the agent recorded in the Mandate. Recovery requires a 65-byte signature with v equal to 27 or 28, and rejects an s value above the secp256k1 half-order. The low-s check prevents signature malleability from creating alternate valid encodings of the same proposal.

Where the digest is used

The current contract paths use the derived commitment consistently when they need to identify a complete plan:

  • EscalationManager.submit computes the digest, stores the complete plan and signature behind it, and reserves the plan's nonce.
  • EscalationManager.approve and deny address the stored escalation by its digest.
  • VaultExecutor.execute computes the digest for a successful normal execution and emits it in ActionPlanExecuted.
  • VaultExecutor.executeEscalated reloads the stored plan, evaluates current state again, recomputes the digest, and rejects the execution if it differs from the reserved value.

The escalation and execution events therefore identify an exact signed plan, while the registry and evaluator still provide the current authority state that was checked around it.

Proposal nonces

A nonce identifies an execution slot for a Mandate and agent. It does not require nonces to execute in numerical order, so a later valid proposal can proceed while an earlier proposal is unused.

For a normal ALLOW path, VaultExecutor asks MandateRegistry to consume the (Mandate, agent, nonce) slot after evaluation and before external Vault calls. The registry checks the active lineage, the agent binding, the current Vault authority, and whether the slot was already used or reserved.

The executor rejects a normal plan when that slot is reserved for an escalation. A successful normal execution marks the slot used, and a later proposal using it fails. If any later call reverts, the nonce update reverts with the transaction, so a failed downstream execution does not permanently consume the slot.

Escalation reservations

When EscalationManager.submit accepts an ESCALATE result, it computes the exact Action Plan digest and reserves the plan's nonce against that digest. The reservation prevents an ordinary execution from using the same slot and prevents another escalation from replacing the stored plan.

The reservation is shared through MandateRegistry, not kept only in the manager. The current executor's configured escalation manager is the only caller allowed to create a reservation, and an escalated execution must consume the same digest that was reserved. Pending or denied reservations remain binding, while successful approved execution deletes the reservation and marks the nonce used.

Nonce cancellation

The Mandate agent or the Vault controller can permanently invalidate an unused, unreserved nonce through Grantline.cancelNonce(). This is a targeted recovery mechanism: it does not pause or revoke the broader authority.

Cancellation marks the nonce as used. A cancelled nonce cannot be consumed by a normal execution or reserved for an escalation. The Registry repeats the authority check before cancelling, so a parent agent cannot cancel a child's nonces.

Cancellation remains available during pause, outside validity windows, and after revocation. It is a recovery-only restriction on a specific execution slot.

Nonce-aware evaluation

The evaluator returns DENY with distinct failure codes for nonce problems:

  • NONCE_USED: the nonce was already consumed or cancelled.
  • NONCE_RESERVED: the nonce is reserved for an escalation digest.

Public evaluation reports both as DENY. The internal evaluator mode (used during approved escalation execution) accepts only the exact reservation digest that matches the reserved nonce.

Deadlines and rollback

A deadline of 0 means no expiry. Any other deadline is an absolute Unix timestamp checked against the chain's block.timestamp; the plan is accepted while the current timestamp is at or before the deadline. Callers should leave a safety margin for inclusion delay rather than depend on an exact second.

Nonce consumption, Vault calls, event emission, and escalation status changes happen in one EVM transaction. If a downstream call or a later validation step reverts, those state changes and events do not persist. A receipt with status 0 proves that the transaction failed, not that a nonce was permanently consumed.

See Escalation for the approval lifecycle and Transaction lifecycle for how these checks appear in normal and escalated execution.

Last updated on

On this page