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 (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_signatureThis 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.