// ON-CHAIN SPEND AUTHORITY FOR AI AGENTS

Would you give an AI agent your wallet?

>
Prompts are a suggestionMandates are physicsBounded on-chainThe model obeyed. The chain didn't.

// 01 · THE INSIGHT

Move the limit off the prompt. Onto the chain.

THE PROMPT // a suggestion

"Never spend more than $5."

  • ✗ editable by injection
  • ✗ trust-based
  • ✗ lives in the context window

THE MANDATE // a contract

cap = 5 USDC, MandateRegistry.settle()

  • the LLM can't reach it
  • reverts CapExceeded
  • lives on Solana

// 02 · TWO LAYERS, BOTH REAL

Defense the model can't argue with.

LAYER A · OFF-CHAIN

Constrained Signer

Holds the key the brain never sees. Refuses to sign any x402 payment that breaks the mandate. No signature is ever produced.

won't even sign

LAYER B · ON-CHAIN

MandateRegistry.settle()

The contract re-checks every cap and reverts CapExceeded before USDC moves. Holds even if the signing key is stolen.

the chain says no

Even if the signing key is stolen, the contract still reverts. That's the difference between a guardrail and a guarantee.

// 03 · TRY TO BREAK IT

Inject a malicious instruction.
Watch it fail.

The agent below is wide open to prompt injection. Tell it to drain the wallet. The mandate stops it anyway. First off-chain, then on-chain.

AGENT MANDATE · cap 5.00 USDC / tx ERC-8004 · FUJI
THE AGENT
awaiting instruction…
MANDATE CONTRACT
MANDATE 5.00
LAYER A · signer
·
LAYER B · on-chain
·
>

// 04 · ADD IT IN 10 SECONDS

Wrap your agent. Done.

One wrapper around the x402 client. Your agent's payments are now physically bounded by an on-chain mandate. No model changes.

import { withCapline } from "capline"

// the agent's brain calls this. it holds NO key.
const xPayment = await withCapline({ signer, mandateId })
  .pay(paymentRequirements)
// → returns a signed x402 payment ONLY if it fits the mandate
// → throws MandateExceeded otherwise. it cannot overspend.
drop-in x402 signer no model changes enforced on-chain on Solana

Stop trusting.
Start enforcing.