Guide

Enforce the grant

Fidacy authorizes; your executor enforces. A verdict on its own changes nothing, a firewall is only real if the code that moves money refuses to move it without a valid grant. That refusal is yours to install, at your PSP boundary, and it is about thirty lines.

The loop

  • ·Your agent calls request_payment (MCP) or /v1/assess (engine). An in-mandate action returns an ALLOW with a short-lived Ed25519 grant; anything outside the mandate returns a DENY with no grant.
  • ·Your executor verifies the grant against Fidacy's public key and settles exactly once — or refuses. No valid grant, no money.
The executor is your code at yoursettlement boundary. That is the point: Fidacy is non-custodial and never touches funds, so the enforcement lives where the money actually moves.

The executor (copy this)

Pure node:crypto, no dependency to install. Swap the mock rail.charge for your PSP call (Stripe, a wire API, your treasury) and the single-use Set for a durable, atomic claim in your database.

import { verify as edVerify, createPublicKey } from "node:crypto";

// The Fidacy public key. From the local firewall: call the verify_mandate tool
// (fidacyPublicKey). From the hosted engine: fetch /.well-known/jwks.json. Pin it.
const FIDACY_PUBLIC_KEY_PEM = process.env.FIDACY_PUBLIC_KEY_PEM;

// Durable single-use store. Use your DB in production (a UNIQUE decisionId column
// with an atomic claim). In-memory here for the example.
const redeemed = new Set();

// The non-bypass gate. Call this at your PSP boundary BEFORE moving any funds.
// It verifies the Ed25519 signature, expiry, single-use, AND that the grant is
// bound to the EXACT payment about to execute. A hallucinated, swapped, replayed,
// or forged payment has no valid grant and never gets past here.
function verifyGrant(grant, expected) {
  if (typeof grant !== "string" || grant.split(".").length !== 2) return { ok: false, reason: "malformed_grant" };
  const [body, sig] = grant.split(".");
  const pub = createPublicKey(FIDACY_PUBLIC_KEY_PEM);

  // Ed25519 over the exact body string; signature is base64url.
  const sigOk = edVerify(null, Buffer.from(body, "utf8"), pub, Buffer.from(sig, "base64url"));
  if (!sigOk) return { ok: false, reason: "invalid_signature" };

  const p = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
  if (Date.now() > p.exp) return { ok: false, reason: "grant_expired" };
  if (p.payee !== expected.payee) return { ok: false, reason: "payee_mismatch" };
  if (p.amount !== expected.amount) return { ok: false, reason: "amount_mismatch" };
  if (p.currency !== expected.currency) return { ok: false, reason: "currency_mismatch" };
  if ((p.invoiceRef ?? null) !== (expected.invoiceRef ?? null)) return { ok: false, reason: "invoice_mismatch" };
  return { ok: true, decisionId: p.decisionId };
}

// Your settlement. Refuses unless a valid, unused grant matches THIS payment.
async function settle(payment, grant, rail) {
  const check = verifyGrant(grant, payment);
  if (!check.ok) return { status: "REFUSED", reason: check.reason };
  if (redeemed.has(check.decisionId)) return { status: "REFUSED", reason: "grant_replayed" };
  redeemed.add(check.decisionId); // claim BEFORE settling (atomic in your DB)
  const { railRef } = await rail.charge(payment); // <-- Stripe, wire, treasury…
  return { status: "EXECUTED", railRef };
}

Prove it does not bypass

Every way a payment can go wrong — a hallucinated payee, a swapped amount, a replayed or forged grant, an outright DENY — produces zero settlements. A single legitimate grant produces exactly one.

// A payment your agent proposed, and the grant Fidacy returned from request_payment.
const payment = { payee: "acme-supplies.inc", amount: 100, currency: "USD" };
const rail = { charge: async () => ({ railRef: "psp_" + Math.random().toString(36).slice(2) }) };
// Flip one byte of the signature — a forged grant.
const tamper = (g) => { const [b, s] = g.split("."); return b + "." + (s.slice(0, -2) + (s.slice(-2) === "AA" ? "BB" : "AA")); };

await settle(payment, allowGrant, rail);          // EXECUTED  (one settlement)
await settle(payment, allowGrant, rail);          // REFUSED   grant_replayed
await settle(payment, undefined, rail);           // REFUSED   malformed_grant   (a DENY carries no grant)
await settle({ ...payment, amount: 900 }, allowGrant, rail);  // REFUSED  amount_mismatch
await settle(payment, tamper(allowGrant), rail);  // REFUSED   invalid_signature

This is the same attack matrix Fidacy's own non-bypass acceptance suite runs in CI. Run it against your executor before you ship, and a regression that would let money slip through fails the build.

What's in a grant

The grant is <body>.<sig>: body is the base64url of a canonical JSON payload, sig is the Ed25519 signature over that exact body string, base64url-encoded.

{
  "decisionId": "…",   // single-use key: redeem once, ever
  "subject":    "agent:…",
  "payee":      "acme-supplies.inc",
  "amount":     100,
  "currency":   "USD",
  "exp":        1751560000000,   // epoch ms; refuse after this
  "invoiceRef": "INV-2026-014"   // optional; one payment per invoice
}

Bind every check to the payment actually about to execute. A grant for invoice X and payee A cannot settle payment Y to payee B, even with a perfect signature — the field match is what makes injection and swap attacks dead on arrival.