Why validate every transaction?
Validation is Mandate’s core contract. Every transaction your agent makes must pass through thevalidate() call before signing. This single API call runs your transaction against the policy engine’s 14 sequential checks: circuit breaker status, schedule windows, address allowlists, blocked actions, per-transaction limits, daily and monthly quotas, risk screening, reputation scoring, reason scanning, and approval thresholds. If any check fails, the transaction is blocked with a specific blockReason code before your wallet is ever called.
This is what separates Mandate from session keys. Session keys check whether a signature is valid. Mandate checks whether the transaction should happen at all, evaluating the intent, the recipient, the amount, and the stated reason. An agent that always validates before signing cannot be manipulated into executing a prompt-injected transfer, because the policy engine catches the anomaly before the private key is involved.
How do you call validate()?
Thevalidate() method on MandateClient is the primary, recommended way to check a transaction. Pass a PreflightPayload with the action, amount, recipient, token, and reason. The policy engine evaluates the payload and returns one of three outcomes: allowed, blocked, or approval required.
What fields does PreflightPayload accept?
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | The type of transaction: transfer, approve, swap, or a custom action string |
reason | string | Yes | Why the agent wants to transact. Max 1,000 characters. Scanned for prompt injection. |
amount | string | No | Amount in USD as a string (e.g., '50' means $50). This differs from wallet.transfer(), which takes raw token units. |
to | string | No | Destination address (checked against allowlist if configured) |
token | string | No | Token symbol: USDC, ETH, WETH, etc. |
chain | string | No | Chain name or identifier, e.g. base-sepolia |
While
amount, to, token, and chain are technically optional, you should always include them when available. The more context the policy engine has, the more accurately it evaluates the transaction. Omitting fields means those policy checks are skipped.The reason field
Every validation call requires a reason string (max 1,000 characters). This is Mandate’s core differentiator from session keys.
The reason field serves three purposes:
- Audit trail. Every transaction is logged with its stated purpose, creating a searchable history.
- Prompt injection detection. Mandate scans the reason for 18+ hardcoded attack patterns and runs an LLM judge (zero retention) to detect social engineering attempts.
- Policy learning. The reason feeds into Mandate’s self-improving policy engine, which generates Insights based on transaction patterns.
What are the three possible outcomes?
Everyvalidate() call results in exactly one of three outcomes. Your agent code must handle all three.
1. Allowed
The policy engine approved the transaction.result.allowed is true, and no errors are thrown. Proceed with signing and broadcasting.
2. Blocked
The transaction violates a policy rule. The SDK throws aPolicyBlockedError (HTTP 422) with a blockReason code and a human-readable declineMessage. Do not retry the same transaction. The policy must be updated in the dashboard before it can pass.
per_tx_limit_exceeded, daily_limit_exceeded, address_not_in_allowlist, action_blocked, reason_blocked, and schedule_outside_window. See Block Reasons for the full list of 12 codes.
3. Approval required
The transaction is within policy limits but exceeds the approval threshold, or the action type requires manual sign-off. The SDK throws anApprovalRequiredError (HTTP 202) with an intentId. The wallet owner receives a notification and can approve or reject in the Approvals dashboard.
Handling approval workflows
When a transaction requires human approval, the SDK throws anApprovalRequiredError. Catch it and poll for the decision:
Validation response
A successfulvalidate() call returns a PreflightResult:
| Outcome | allowed | requiresApproval | What happens |
|---|---|---|---|
| Approved | true | false | Proceed with the transaction |
| Needs approval | true | true | SDK throws ApprovalRequiredError. Poll with waitForApproval(). |
| Blocked | false | false | SDK throws PolicyBlockedError with blockReason |
When
requiresApproval is true, the SDK throws an ApprovalRequiredError automatically.
You do not need to check this field manually. See Handle Approvals.How does validate() differ from raw validation?
Mandate supports two validation flows. Thevalidate() method (also called preflight) is the primary, recommended approach for all new integrations.
| Aspect | validate() (recommended) | rawValidate() (deprecated) |
|---|---|---|
| Input | Action, amount, recipient, reason | Full EVM tx params + intentHash |
| Gas estimation | Not needed | Required before calling |
| Use case | All agents (custodial and non-custodial) | Legacy self-custodial flows |
| Complexity | Low: 5-6 fields | High: 10+ fields + hash computation |
| Post-call steps | Sign and broadcast | Sign, broadcast, and postEvent() |
Deprecated.
rawValidate() is kept for backward compatibility. Use validate() for all new code. If you need the raw flow for a self-custodial wallet, see MandateClient.rawValidate().What are the best practices for production agents?
Always include a descriptive reason
Thereason field is not optional filler. It powers three critical systems: the audit trail, prompt injection detection, and policy insights. Write a specific, honest description. Include invoice numbers, vendor names, or task context when available.
Handle all error types
Your agent must handlePolicyBlockedError, ApprovalRequiredError, CircuitBreakerError, and RiskBlockedError. Missing any one of these creates an unhandled rejection that could crash your agent or, worse, silently skip validation.
The SDK throws typed errors you can catch with instanceof:
| Error Class | HTTP Status | When it fires |
|---|---|---|
PolicyBlockedError | 422 | Transaction violates a policy rule (limits, allowlist, schedule, etc.) |
CircuitBreakerError | 403 | Agent is emergency-stopped by owner |
ApprovalRequiredError | 202 | Amount exceeds approval threshold, or action/selector requires approval |
RiskBlockedError | 422 | Address flagged as critical risk by Aegis scanner |
MandateError | any | Base class for all Mandate errors |
Never skip validation
Every transaction path in your agent must include avalidate() call. This includes retries, fallback logic, and edge cases. If your agent has a code path that signs a transaction without validating first, that path is a security hole.
Validate before gas estimation
Callvalidate() before estimating gas or preparing transaction parameters. If the policy engine blocks the transaction, you save the gas estimation RPC call. If it requires approval, you avoid preparing a transaction that may never execute.
Use the SDK, not raw HTTP
The SDK handles error parsing, typed exceptions, retry logic, and response validation. Raw HTTP calls require you to parse error codes, match status codes to error classes, and handle edge cases manually. UseMandateClient or MandateWallet unless you are working in a language without an SDK.
How do you audit validated transactions?
Everyvalidate() call creates an intent record in Mandate’s audit log, regardless of the outcome. The wallet owner can view all intents (allowed, blocked, and pending approval) in the Audit Log dashboard page. Each entry includes the action, amount, recipient, reason, policy evaluation trace, and final decision.
For programmatic access, use client.getStatus(intentId) to retrieve the current state of any intent. The response includes the transaction hash, block number, gas used, and decoded action summary for confirmed transactions.
Next Steps
Handle Approvals
Wait for human decisions with polling, timeouts, and callback patterns.
Handle Errors
Recovery patterns for all 5 error classes and 12 block reason codes.
MandateClient Reference
Full API reference for validate(), rawValidate(), postEvent(), and polling methods.
Block Reasons
Complete table of block reason codes with descriptions and resolution steps.