Payment guard middleware for AI agents. Use whenever any skill is about to initiate a payment. Runs a preflight check against the user's policy (payments enabled, per-transaction limit, monthly cap). Returns ALLOW, DENY, or CONFIRM_REQUIRED. Other payment-capable skills must call signet-preflight before proceeding and signet-record after a successful payment.
---
name: signet-guardian
description: "Payment guard middleware for AI agents. Use whenever any skill is about to initiate a payment. Runs a preflight check against the user's policy (payments enabled, per-transaction limit, monthly cap). Returns ALLOW, DENY, or CONFIRM_REQUIRED. Other payment-capable skills must call signet-preflight before proceeding and signet-record after a successful payment."
metadata: {"openclaw":{"emoji":"π‘οΈ","homepage":"https://getsignet.xyz"}}
---
# Signet Guardian β Payment Guard Middleware
## Overview
Signet Guardian is a **policy firewall** for money actions. It does not intercept payments at runtime by itself; **payment-capable skills must route through it by contract**:
1. Before any payment: call **signet-preflight** (amount, currency, payee, purpose).
2. If result is **ALLOW** or **CONFIRM_REQUIRED** (and user has confirmed): the skill may proceed.
3. If result is **DENY**: do **not** proceed; tell the user the reason.
4. After a successful payment: call **signet-record** to append to the ledger.
This gives one place to enforce: master switch (payments on/off), max per transaction (e.g. Β£20), max per month (e.g. Β£500), and optional confirmation above a threshold (e.g. Β£5).
**Concurrency:** Preflight is advisory (no lock). **Record enforces the monthly cap under a file lock** (`{baseDir}/references/.ledger.lock`): it re-checks the cap before appending and refuses to record if the month would be exceeded. So the monthly limit is enforced at record time; idempotency and cap are both safe under concurrent calls. Preflight can still be used to fail fast; the definitive check is in record.
**Currency:** No FX conversion. The request currency **must match** the policy currency; otherwise preflight returns DENY. Conversion source/rules are not defined.
## Policy (user configuration)
**Source of truth:** OpenClaw config first (`signet.policy` in the main config, e.g. editable in the Control UI if the extension is installed), then fallback to `{baseDir}/references/policy.json`. OpenClaw sets `{baseDir}` via `OPENCLAW_SKILL_DIR` or `OPENCLAW_BASE_DIR`.
| Field | Meaning |
|-------|--------|
| `paymentsEnabled` | Master switch. If `false`, all payments are denied. |
| `maxPerTransaction` | Max amount allowed for a single transaction (e.g. 20). |
| `maxPerMonth` | Max total spend in the current calendar month (e.g. 500). |
| `currency` | ISO currency code (e.g. GBP, USD). Request currency must match. |
| `requireConfirmationAbove` | Above this amount, return CONFIRM_REQUIRED so the user must explicitly confirm (e.g. 5). |
| `blockedMerchants` | Optional list of substrings; payee matching any is denied. |
| `allowedMerchants` | Optional; if non-empty, only payees matching one of these are allowed. |
| `version` | Optional number for future policy migrations. |
**Default behaviour:** If the policy file is missing or invalid, **preflight returns DENY** (default-deny).
## Commands
### `signet-preflight`
Run **before** initiating any payment. Validates: payments enabled, currency match, amount > 0 and β€ max per transaction, (current month spend + amount) β€ max per month, and optional merchant rules. Optionally requires explicit confirmation above a threshold. Amount must be greater than zero.
```bash
signet-preflight --amount 15 --currency GBP --payee "shop.example.com" --purpose "Subscription"
```
Optional:
- `--idempotency-key "unique-key"` β Used when recording later to avoid duplicate ledger entries.
- `--caller-skill "skill-name"` β Name of the skill invoking the guard (for audit).
**Output (JSON):**
- `{ "result": "ALLOW", "reason": "Within policy" }` β Proceed with the payment.
- `{ "result": "CONFIRM_REQUIRED", "reason": "..." }` β Ask the user for explicit confirmation; if they agree, proceed then call signet-record. (Confirmation is the callerβs responsibility.)
- `{ "result": "DENY", "reason": "..." }` β Do **not** proceed. Notify the user.
Every DENY is logged to the audit trail.
**Exit code:** 0 for ALLOW or CONFIRM_REQUIRED, 1 for DENY.
### `signet-record`
Call **after** a payment has successfully been made. Appends one line to the ledger (append-only). If an idempotency key was used in preflight, pass the same key here to avoid double-counting.
**Record validation scope:** `signet-record` re-checks only **currency** and **monthly cap** (under lock). It does **not** re-check `paymentsEnabled` or merchant allow/block lists. Policy enforcement (switch, merchants, per-tx limit) is done at **preflight** (and in an optional future authorize phase). Record is the post-success log; the cap check at record time prevents double-counting when concurrent preflights both allowed.
```bash
signet-record --amount 15 --currency GBP --payee "shop.example.com" --purpose "Subscription" --idempotency-key "sub-123"
```
Optional: `--caller-skill "skill-name"` for audit.
If the same `idempotency-key` was already recorded, the command is a no-op (idempotent).
### `signet-report`
Shows spending and transaction history for the user.
```bash
signet-report --period today
signet-report --period month
```
### `signet-policy`
Show, edit, or configure policy via wizard.
```bash
signet-policy --show # Print current policy (config, then file)
signet-policy --edit # Open policy.json in $EDITOR
signet-policy --wizard # Interactive step-by-step setup (no JSON)
signet-policy --migrate-file-to-config # One-time: copy file policy into OpenClaw config
```
## Audit (ledger and deny log)
Ledger file: `{baseDir}/references/ledger.jsonl`. Format is **strict JSONL**: one JSON object per line, **newline-separated** (no space between entries). Each line contains:
- **ts** β Timestamp UTC (ISO 8601).
- **callerSkill** β Optional; skill that invoked preflight/record.
- **idempotencyKey** β Optional; dedupe key for record.
- **status** β `completed` or `denied`.
- **reason** β Decision reason (especially for denials).
- Plus: amount, currency, payee, purpose.
All preflight denials are appended to the same ledger with `status: "denied"` and a reason.
## Critical Rules (for the agent)
1. **Never skip preflight** β Any payment from any skill must go through `signet-preflight` first. No exceptions.
2. **Respect DENY** β If preflight returns DENY, do not attempt the payment. Tell the user the reason.
3. **CONFIRM_REQUIRED** β If preflight returns CONFIRM_REQUIRED, ask the user explicitly (βAllow this payment of Β£X to Y?β). Only proceed if they confirm, then call `signet-record`.
4. **Always record success** β After a successful payment, call `signet-record` with the same amount, currency, payee, purpose, and idempotency key (if used).
5. **Idempotency** β For critical flows, use a stable `--idempotency-key` (e.g. order ID or request ID) so retries do not double-count in the monthly total.
6. **Default-deny** β If the policy file is missing or corrupt, the skill denies by default.
7. **Record is authoritative for cap only** β The monthly cap is enforced when recording (under lock). If `signet-record` fails with a cap error, the payment already happened; do not retry without user confirmation. For cap-safe flows before payment, a future **authorize** (reservation under lock) then **settle** (convert reservation to completed) pattern can reserve budget before the payment is made.
## First Run
On first use, the user must have a valid `{baseDir}/references/policy.json`. Run `signet-policy --show` to see current policy; if missing, create it (e.g. via `signet-policy --edit`) with at least:
- `paymentsEnabled`: true/false
- `maxPerTransaction`: number
- `maxPerMonth`: number
- `currency`: e.g. "GBP"
- `requireConfirmationAbove`: number (e.g. 5)
Ledger lives at `{baseDir}/references/ledger.jsonl`; no extra setup required.
don't have the plugin yet? install it then click "run inline in claude" again.