
Arx
Arx is a programmable transaction firewall and policy engine for on-chain AI agents.
Demo
About
An AI agent that can move money needs authority. The usual way to give it authority is a private key or an API credential, which grants everything that credential can do, for as long as it exists. That is a poor fit for a system whose behaviour is steered by text it did not write. A poisoned invoice tells a payment agent the supplier's banking details have changed. The agent believes it — it is not malfunctioning, it was convinced — and signs. Better reasoning does not fix this, because the model is the part that can be talked into things. The problem is not intelligence. The problem is authority. Arx sits between the agent and the signer and answers one question: is this exact transaction inside the authority this agent was actually granted? It normalizes the transaction, resolves the agent's capability, evaluates policy, inspects the transaction bytes, scores risk from explainable signals, and produces one of four outcomes in Ledger's own vocabulary — ALLOW, DENY, ESCALATE, or ABORT. Only an approval cryptographically bound to those exact bytes can reach the signer, and the Ledger device remains the final boundary. What makes it more than an allowlist is the set of checks a naive policy layer gets wrong: - The agent's own numbers are claims, not facts. An intent declares amountUsd; Arx prices the actual transaction bytes through a Chainlink feed and refuses the mismatch. An agent declaring $1 while moving 1000 ETH is caught. - The recipient is often inside the calldata. In an ERC-20 transfer the payee is an argument, not the `to` field, so an allowlisted token contract is otherwise an open payment channel. - A standing allowance is authority. An approve moves nothing now and lets the spender move everything later, so the USD ceiling applies to it. - Dependency failure is never permission. Every external adapter distinguishes "checked, clean" from "could not check". Unknown resolves to DENY or ESCALATE, never ALLOW. Silence is never approval. Arx also solves the credential half of the problem. Using the Ledger Key Ring, a capability broker seals upstream secrets and hands the agent a short-lived token bound to a hash of the policy in force — never the API key. The agent cannot leak what it does not hold, and tokens die the moment an operator narrows the policy. Fourteen adversarial scenarios run as assertions, not printouts: prompt injection, declared-value lies, calldata smuggling, address poisoning, a double-sign race, replay, revocation mid-flight, and audit tampering. The suite exits non-zero if any behaves differently from the documentation. A separate demo drives a real AI agent that is genuinely prompt-injected, alongside an unprotected control case that signs the attacker's transaction — because the contrast is the argument.
How it's made
TypeScript on Bun, Fastify, Zod, SQLite via bun:sqlite, viem for EVM primitives. The authorization kernel makes no network calls and runs no model on the decision path, so there is no prompt in it to inject into. Every decision is a pure function of the capability, the intent, the transaction bytes and an injected clock, which is what makes decisions reproducible from the audit log. Ledger is the core. The Device Management Kit (1.9.0), the Ethereum signer kit (1.18.0), the first-party Speculos transport (1.2.1) and context-module (2.5.0) are all pinned exactly, because the @ledgerhq packages publish a develop build daily and a caret range drifts mid-event. Alongside the SDK path I implemented the app-ethereum APDU protocol directly from its published specification and the app's own C source, so the signing path is transparent and auditable rather than opaque: BIP-32 path packing, 255-byte chunking, the EIP-2718 type-byte framing, and the typed-transaction parity convention. Signatures are not accepted, they are verified — the signer address is recovered from the exact signed digest and compared to what the device reported. Because Docker was unavailable on the development machine, I built an in-process implementation of the app-ethereum protocol that drives the same codec and is keyed from the published Speculos test mnemonic. It derives 0xDad77910DbDFdE764fC21FCD4E74D71bBACA6D8D — the address Ledger's own fixtures expect for that seed — which cross-checks the request encoding, BIP-32 packing and response parsing against an external value. That mode produces real secp256k1 signatures that verify by address recovery, so the full pipeline is demonstrable on any machine with no hardware, while reporting emulated: true and refusing to start in production. The Ledger Key Ring CLI (wallet-cli ring) backs the capability broker. Its key property is that after ring init, encrypt and decrypt need network but not the device — which is what makes the "hosts with no USB port" story work: provision once on a laptop, then unseal on a VPS with no hardware attached. Integrations plug into named seams — PriceOracle, NameResolver, RiskSignalProvider, SignerAdapter, HumanVerifier, ExecutionVenue — and every one returns an Availability<T> that keeps "checked, clean" distinct from "could not check". ENSv2 on Sepolia resolves allowlist entries during authorization and binds the resolved address, so a name re-pointed between approval and signing aborts rather than redirecting funds. Chainlink aggregators are read directly rather than through a convenience API, because latestRoundData returns updatedAt and that lets Arx enforce its own staleness bound. The parts I would highlight as hacky or hard-won: hashing goes through an RFC 8785-style canonicalizer rather than JSON.stringify, because otherwise a digest depends on key insertion order and a reordered payload could be shaped to match an approval bound to different bytes. Claiming an approval for signing is a SQL compare-and-swap performed before any bytes reach the device, which is what makes double-signing impossible under concurrency — verified with eight parallel requests. The audit log is hash-chained so editing any historical decision invalidates every hash after it, and GET /audit/verify recomputes the chain and names the first break. An adversarial audit of this repository found six real defects during the build — including a $100M standing allowance passing a $500 ceiling, and one agent being able to sign another agent's approval. All six are fixed, each with a test, and they are listed in the README rather than quietly patched.