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.
restructured original content into implexa's six required components, added explicit decision points for all preflight and record outcomes including currency mismatch and concurrent race conditions, documented policy schema and ledger format in inputs, clarified exit codes and JSON output contracts, flagged edge cases (missing policy, zero amounts, idempotency, file locking), and kept original author and intent intact.
Signet Guardian is a policy firewall for any skill that handles money. it enforces a master switch (payments on/off), per-transaction limits, monthly spend caps, and optional confirmation thresholds. use this skill before initiating any payment and after recording a successful payment. the skill does not block payments at runtime by itself; other payment-capable skills must call signet-preflight (before) and signet-record (after) by contract. this gives you one place to enforce policy across all payment flows in your agent.
Policy configuration (source of truth, in order of precedence):
signet.policy in the main control config (if the extension is installed).{baseDir}/references/policy.json (where {baseDir} is set by OPENCLAW_SKILL_DIR or OPENCLAW_BASE_DIR).Policy schema:
| Field | Type | Meaning |
|---|---|---|
paymentsEnabled |
boolean | Master switch. if false, all payments denied. |
maxPerTransaction |
number | Max amount per single transaction (e.g. 20). |
maxPerMonth |
number | Max total spend in current calendar month (e.g. 500). |
currency |
string | ISO code (e.g. GBP, USD). request currency must match or preflight denies. |
requireConfirmationAbove |
number | If amount exceeds this, return CONFIRM_REQUIRED (e.g. 5). |
blockedMerchants |
array (optional) | List of substrings; payee matching any is denied. |
allowedMerchants |
array (optional) | If non-empty, only payees matching one are allowed. |
version |
number (optional) | For future policy migrations. |
Ledger file: {baseDir}/references/ledger.jsonl (strict JSONL, one object per line, newline-separated). no extra setup needed; created on first write.
External dependencies: none (file-based). all policy and ledger data lives in {baseDir}/references/.
Edge cases to know:
{baseDir}/references/.ledger.lock to enforce monthly cap safely.Input: amount (number > 0), currency (ISO code), payee (string), purpose (string), optional idempotency-key (string for dedup), optional caller-skill (string for audit).
Command:
signet-preflight --amount 15 --currency GBP --payee "shop.example.com" --purpose "Subscription" [--idempotency-key "sub-123"] [--caller-skill "my-payment-skill"]
Output (JSON):
{ "result": "ALLOW", "reason": "Within policy" } , safe to proceed.{ "result": "CONFIRM_REQUIRED", "reason": "Above confirmation threshold of £5" } , ask user to confirm.{ "result": "DENY", "reason": "Payments disabled by policy" } , do not proceed; tell user.Exit code: 0 for ALLOW or CONFIRM_REQUIRED, 1 for DENY.
Output location: stdout (JSON). denials are also logged to ledger with status: "denied".
See decision points section below. if ALLOW, proceed to payment. if CONFIRM_REQUIRED, ask user and only proceed if confirmed. if DENY, stop and inform user of the reason.
the calling skill executes the actual payment (e.g. via Stripe, PayPal, bank API, etc.). signet-guardian does not do this; it only validates policy.
Input to payment handler: same amount, currency, payee, purpose as passed to preflight.
only call this if the payment succeeded. use the same amount, currency, payee, purpose, and idempotency-key (if used in preflight).
Command:
signet-record --amount 15 --currency GBP --payee "shop.example.com" --purpose "Subscription" [--idempotency-key "sub-123"] [--caller-skill "my-payment-skill"]
Validation scope: signet-record re-checks currency and monthly cap (under file lock). it does not re-check paymentsEnabled or merchant rules (those are preflight's job). if the same idempotency-key was already recorded, this is a no-op (idempotent).
Output location: appended to {baseDir}/references/ledger.jsonl (strict JSONL format).
Exit code: 0 on success, 1 if currency mismatch or monthly cap exceeded (cap check happens under lock).
call signet-report to show user their transaction history or current month spend.
Command:
signet-report --period today
signet-report --period month
Output: human-readable summary plus optional JSON (caller skill determines format).
run signet-policy to view, edit, or set up policy.
Commands:
signet-policy --show # Print current policy (config, then file).
signet-policy --edit # Open policy.json in $EDITOR.
signet-policy --wizard # Interactive setup (no JSON editing).
signet-policy --migrate-file-to-config # Copy file policy into OpenClaw config (one-time).
If preflight returns ALLOW:
If preflight returns CONFIRM_REQUIRED:
If preflight returns DENY:
If currency in request does not match policy currency:
If amount is zero or negative:
If policy file is missing or invalid JSON:
If signet-record is called with same idempotency-key twice:
If signet-record detects currency mismatch at record time:
If signet-record detects monthly cap would be exceeded (under lock):
If concurrent calls race on the monthly cap:
{baseDir}/references/.ledger.lock) ensures only one record call updates the ledger at a time.signet-preflight output (stdout):
{
"result": "ALLOW" | "CONFIRM_REQUIRED" | "DENY",
"reason": "string describing the decision"
}
exit code 0 for ALLOW or CONFIRM_REQUIRED, 1 for DENY.
signet-record output (stdout):
{
"status": "recorded" | "no-op (idempotent)" | "rejected",
"reason": "string"
}
exit code 0 on success or idempotent no-op, 1 on rejection.
Ledger format (file: {baseDir}/references/ledger.jsonl):
strict JSONL, one object per line, each containing:
{
"ts": "2025-01-15T14:32:00Z",
"status": "completed" | "denied",
"amount": 15,
"currency": "GBP",
"payee": "shop.example.com",
"purpose": "Subscription",
"callerSkill": "my-payment-skill",
"idempotencyKey": "sub-123",
"reason": "Within policy" | "Payments disabled" | etc.
}
all fields present except callerSkill and idempotencyKey (only if provided).
signet-report output: human-readable summary (caller skill determines format). may include total spend this month, transaction list, last N transactions, etc.
signet-policy output: current policy in JSON (for --show) or confirmation message (for --edit, --wizard, --migrate).
overall: the user knows signet-guardian is working when: