STELLAR JEV POLICIES · TECHNICAL DEEP DIVE
How the decision layer works
Deterministic security plus smart AI decisions. Every agent transaction needs two signatures in one transaction: the agent’s key and a Jev operator. Deterministic on-chain rules cap what even a fully compromised AI can do. Everything below is sourced from the open-source repository. Status: proof of concept on Stellar testnet, not yet audited.
01Explainer
How a policy decision works
A 90-second walkthrough of one agent payment: the agent and Jev co-sign, jev_verifier checks the decision against τ = 0.80, and jev_policy enforces caps and allowlists, then a payment to an unknown recipient is blocked.
02Soroban auth
Soroban smart contracts and __check_auth
The smart account is a Soroban custom account. When the agent submits a transaction, the host calls the account’s __check_auth with the signature payload for that exact invocation. The Jev decision is signed over that auth_digest, so it is bound to one transaction: changing the amount or recipient after approval invalidates it. Replay protection, expiry and atomic rollback come from Stellar itself, and no model runs on-chain; contracts only check signatures, hashes and bounds.
user (passkey) ──add_context_rule──▶ OZ Smart Account (stellar-accounts, soroban-sdk 28)
rule: CallContract(token) "jev-agent-session"
signers: agent ed25519 + Signer::External(jev_verifier, JevKey)
policies: simple_threshold(2) · spending_limit · jev_policy
agent ──tx──▶ __check_auth ──▶ jev_verifier.verify (allow, p_allow ≥ τ, model/intent pinned, fresh, k-of-n sigs)
└──▶ policies (all must pass): 2-of-2 threshold · spending_limit window · jev_policy03Smart accounts
OpenZeppelin stellar-accounts
- Context rules. The owner adds a token-scoped
CallContractrule for the agent session, one rule per asset. - Signer::External.
jev_verifieris an OZ Verifier installed asSigner::External(jev_verifier, encode_key(JevKey)). OZ caps external key data at 256 bytes, so the key uses a fixed binary layout (79 + 32·n bytes, n ≤ 4). - Stock policies.
simple_thresholdrequires 2-of-2 (agent + Jev);spending_limitmeterstransferover a window. OZ ANDs every policy. - OZ stellar-contracts is pinned at
8e25220(soroban-sdk 28.0.0), which includes the v0.7.3 accounts security fix; testnet contracts were redeployed from this build on 2026-10-20.
04Passkeys
Passkeys (WebAuthn / secp256r1) as owners
The web console uses a real browser passkey as the account owner through the OZ WebAuthn verifier, with smart-account-kit’s key format (65-byte P-256 public key ‖ credential ID) and low-S signatures over {authenticator_data, client_data, signature}. Only the owner can change a context rule, so the passkey is what commits the session: intent hash, limits, allowlists and τ. A Node end-to-end test deploys a passkey-owned account, installs the session with a passkey-signed authorization and makes a Jev co-signed transfer on testnet.
05Payments
x402 on Stellar, facilitators and fee sponsorship
The agent calls an x402 v2 resource server, builds the exact payload (a SEP-41 transfer from its smart account with a Jev co-signed auth entry), and a facilitator verifies, sponsors the fee and settles. A payment has settled on testnet through a self-hosted @x402/stellar facilitator; it was 0.1 XLM, Jev allow 0.95.
- Fee ceiling. Smart-account
__check_authneeds ~310k–5.73M stroops of resource fee, above the 50,000-stroop default. Pre-paying TTL rent once brings warm payments to ~319k; we run a 500,000-stroop ceiling. - Event check. Reference and OpenZeppelin Relayer facilitator paths reject non-
transferevents, including OZ policy audit events. Our facilitator allowlists audit events from listed policy contracts. These issues are public upstream, and we contribute measurements and fix testing. - MPP integration is on the roadmap; the same co-signed auth entry applies.
06Jev
Jev decisions
The operator sends TypeSafe AI’s Jev one typed choice question (allow / deny / escalate) about whether the decoded transaction fits the user’s intent, and reads the calibrated probability p_allow. The model is pinned (jev-1.13.0, never jev-latest) and the operator co-signs only if the verdict is allow and p_allow ≥ τ, with τ = 0.80 chosen from the measured ROC curve.
pub struct JevKey { // committed by the owner when the rule is added
pub operators: Vec<BytesN<32>>, // ed25519 operator keys (max 4)
pub k: u32, // required operator signatures
pub model_id: BytesN<32>, // sha256("jev-1.13.0"), never "latest"
pub intent_hash: BytesN<32>, // sha256(canonical intent JSON)
pub tau_allow_ppm: u32, // calibrated threshold, parts per million
pub template_id: u32,
pub max_age_ledgers: u32, // decision lifetime (~5 s per ledger)
}
pub struct DecisionBody { // what operators sign
pub auth_digest: BytesN<32>, // the exact OZ auth digest of this transaction
pub verdict: u32, pub p_allow_ppm: u32,
pub model_id: BytesN<32>, pub intent_hash: BytesN<32>,
pub template_id: u32, pub issued_ledger: u32, pub expiry_ledger: u32,
}07Contracts
jev_verifier, jev_policy and “AI can only restrict”
jev_verifier.verifyreturns true only if: verdict is ALLOW and p_allow ≥ τ; model_id, intent_hash and template match the key;issued_ledger ≤ now ≤ expiry_ledgerwithinmax_age_ledgers; exactly k ed25519 signatures with strictly ascending operator indices oversha256(xdr(DecisionBody)). Deny and escalate are vetoes; an invalid signature traps (fail-closed).jev_policyis an OZ Policy that, on every call, re-checks that the pinned spending_limit is still on the rule, that the Jev signer authenticated, a (contract, function) allowlist, and per-token rules: a per-call cap and recipient allowlist for SEP-41 transfer/approve.- A Jev allow is necessary, never sufficient. Jev cannot add signers, raise limits or edit allowlists. On testnet, a simulated compromised operator approving everything is still rejected (
#4207 RecipientNotAllowed,#4206 AmountExceedsTokenRule). - 35 contract tests: 21 for jev_verifier and 14 end-to-end
__check_authtests.
08Benchmark
Benchmark methodology
- Deterministic labeled sets: a 200-case tuning set and a 200-case holdout. Each has 75 should-sign and 125 should-refuse cases (over cap, unknown recipient/contract, 30 prompt injections, function misuse, wrong asset) across 4 intents, run against real Jev
jev-1.13.0. - Four prompt framings compared; τ chosen by maximizing Youden’s J. Selected: framing v4 at τ 0.80.
- Result: 0/125 attacks co-signed on both sets (95% Clopper-Pearson upper bound 2.9%), 0/30 injections signed, holdout AUC 0.989, latency p50 72 ms (75 ms on the tuning set), ~940 input tokens (≈ $0.00004) per decision.
- Legitimate payments escalated to the owner: 10.7% (tuning) and 12.0% (holdout).
- Dataset: both sets are public on Hugging Face (jev-policy-calibration).
- Caveat: the holdout reuses text templates, so it is not adversarially novel. A larger red-team set is planned.