Version 0.1.0, updated 2026-09-15 after review. Download the source (tgz) A public repository follows; until then this tarball is the release. Tests: 12 passing with node --test.

fifty-guard

Open-source controls for AI agents that handle money on Solana. fifty-guard checks a proposed transaction against a policy file and returns a verdict: allowed or rejected, with reasons. Nothing here ever signs a transaction.

Honest positioning, v0.1: this is a transaction policy linter, not an enforcement boundary. A caller can ignore the verdict, edit the policy, or sign a different transaction. The verdict becomes a control only when the signing path requires an approval artifact bound to the exact transaction hash and policy hash; that is roadmap item one. Until then, treat it as a preflight check and a public record, not a guarantee.

FIFTY (the memecoin project this was built alongside) is the live test case: an AI agent given a small treasury. From 2026-09-15 on, every proposed dev-wallet move is run through this check and the audit line is published; moves before that date were not, and the tool did not exist yet.

What this is not

signature or touches a private key. It only reads and reports.

price, marketing, or any token's fundamentals.

Install

npm install fifty-guard

Or clone this directory and run npm install (zero runtime dependencies beyond @solana/web3.js).

CLI usage

fifty-guard check --tx <base64|path> --policy policy.json [--rpc URL] [--audit audit.jsonl]
fifty-guard verify --audit audit.jsonl
fifty-guard policy-hash --policy policy.json

the policy, prints a human-readable verdict, and exits 0 when allowed, 2 when rejected.

simulateTransaction to catch anything that would fail on-chain.

one hash-chained JSON line (see below).

policy that's published somewhere can later be proven unchanged.

Library usage

import { evaluate } from 'fifty-guard/src/evaluate.js';

const result = await evaluate(txBase64, policy, { ledgerPath: './ledger.json' });
// result.allowed, result.rule, result.reasons, result.summary, ...

Policy fields (policy.example.json)

FieldMeaning
maxSolPerTxMaximum SOL a single transaction may move out of the signer's account. A transaction over this is rejected.
maxSolPerDayMaximum SOL total across all transactions approved in a UTC day, tracked in the ledger file. A transaction that would push the day's total over this is rejected, even if it is under maxSolPerTx on its own.
allowedProgramsProgram IDs the transaction is allowed to invoke. Any instruction targeting a program not on this list rejects the whole transaction.
allowedDestinationsAccount addresses the transaction is allowed to send funds or touch as a non-signer account. The signer's own address and the fee payer are always implicitly allowed.
requireHumanApprovalWhen true (the default if omitted), every verdict carries needsHuman: true. This is a reporting flag only, since fifty-guard never signs regardless of this setting.
signerThe public key of the wallet whose outgoing balance is being controlled. Used to identify which side of a transfer is "out".

Audit chain

--audit audit.jsonl appends one JSON object per line. Each line includes prevHash (the previous line's hash, or null for the first line) and its own hash (a sha256 of everything else in that line). This means:

was edited or deleted after the fact.

so verify reports the first index where the chain no longer matches.

tweet) be checked against the actual policy file in use, without exposing the policy's private fields if you choose to hash it before adding secrets.

This is a local, offline chain (no blockchain, no external service). It proves internal consistency of the log, not that the log itself wasn't replaced wholesale; publish the file or its running hash somewhere append-only (e.g. commit it) if you need that guarantee too.

Limitations

1. **SPL token transfers are listed as programs and destinations, but token amounts are not valued in SOL in this version.** A transaction that moves SPL tokens will show the token program and the destination token account under policy checks (so allowedPrograms / allowedDestinations still apply), but maxSolPerTx / maxSolPerDay only count native SOL moved by SystemProgram.transfer. A large token transfer through an allowed program to an allowed destination will not be capped by SOL value. 2. **CPIs (cross-program invocations) inside an allowed program are not inspected.** fifty-guard only reads the top-level instruction list from the transaction message. If an allowed program internally calls another program via a CPI, that inner call is invisible to this check. 3. The SOL-out estimate is an instruction sum, not a balance diff. simulateTransaction's account snapshot only returns a post-run balance with no pre-run balance in the same response, so --rpc improves correctness by catching failing transactions, not by refining the amount.

Roadmap

price oracle, so token moves are capped the same way SOL moves are.

an allowed outer program can't smuggle an unlisted inner transfer.

fetch balance) instead of the instruction-sum estimate, when --rpc is set.

Disclosure

This is a safety tool, not a guarantee. It reduces the ways a signing key can be misused by an automated agent; it does not replace reviewing what you sign, and it cannot protect funds once a transaction has already been signed and sent outside of it.

License

MIT

Try it and tell me what breaks

Three unrelated developers testing this is the week-three goal. If you run it, send what blocked you through the Operator Inbox or ops@fiftycoin.lol. Feedback is published only with permission.