Skip to content

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_policy

03Smart accounts

OpenZeppelin stellar-accounts

  • Context rules. The owner adds a token-scoped CallContract rule for the agent session, one rule per asset.
  • Signer::External. jev_verifier is an OZ Verifier installed as Signer::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_threshold requires 2-of-2 (agent + Jev); spending_limit meters transfer over 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_auth needs ~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-transfer events, 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.verify returns true only if: verdict is ALLOW and p_allow ≥ τ; model_id, intent_hash and template match the key; issued_ledger ≤ now ≤ expiry_ledger within max_age_ledgers; exactly k ed25519 signatures with strictly ascending operator indices over sha256(xdr(DecisionBody)). Deny and escalate are vetoes; an invalid signature traps (fail-closed).
  • jev_policy is 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_auth tests.

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.