← Index
Source: docs/features/jayverse-auditor.md (auto-generated by scripts/generate-docs-html.mjs — edit the .md, not this file)

Jayverse — Authority Auditor

Purpose: feed it a wallet/dapp config (or pick a preset) and get back a filled authority matrix — who can sign / recover / export keys / change policy, alone or only in cooperation — as a shareable report where every cell is backed by an evidence link or marked "not verified." Pure read/analyze: no keys, no custody, no transactions.

Source: ../tasks/09-02-jayverse.md §8 Authority Auditor and jay's comment there ("Show me the user scenario and what web app shows and the flow. You can imagine some basic feature."). Sits under the Jayverse hub — README.md. Repo: jayverse-auditor (small Next.js app + rules engine, rabbit cloud). Depends on nothing on-chain, so it can ship first; richest once the Wallet service (#6) exists to dogfood against. This is a design draft for review, not built.


Why it matters

In web3, who can actually move funds or control an account is usually undocumented and diffuse. A modern wallet/dapp stacks a vendor, a smart account, an upgradeable proxy, guardians, session keys, and an owner key — each quietly adds an actor who can sign, recover, export keys, upgrade, or pause. A large share of hacks and rugs trace to one actor who could act alone that nobody had mapped. The Auditor makes that explicit — a per-action grid of who can do it, alone or only together, every cell backed by evidence — so a team fixes single points of failure before they are exploited, not after. It is the ecosystem's defensive lens, and its audience is builders (developers, founders, reviewers), not end-users.


Phases (build order)

Phase Focus What we implement
1 (MVP) Dogfood matrix hand-authored authority matrix for our own wallet config; matrix schema (actions × actors × evidence + severity + verified-tag); report renderer + shareable read-only page; ship as the first public report.
2 Rules engine evaluate(config) → Matrix pure function; per-provider parsers (Privy first, then Dynamic / Web3Auth / Turnkey); config intake tiers 1–2 (guided form/paste + exported JSON).
3 On-chain + API viem readers (owner/getOwners, EIP-1967 admin slot, paused() + PAUSER_ROLE) → verified cells; opt-in provider-API tier (tier 3); broader AccessControl role discovery.

1. What we build (basic feature)

Three layers, built in this order — each is useful on its own:

  1. Hardcoded matrix for our OWN wallet config (dogfood = launch content). Before any engine exists, hand-author the authority matrix for the Jayverse Wallet service's real config (provider, session-key policy, recovery setup) and ship it as the first public report. This proves the format, gives the app real launch content, and forces us to look our own custody reality in the eye.
  2. A rules engine mapping config → matrix cells. A pure function evaluate(config) → Matrix. Each cell for an (action × actor) pair resolves to one of alone / cooperation / cannot (plus unknown), with a rule id and an evidence reference. Feed it a provider config (Privy / Dynamic / Web3Auth / Turnkey shapes) and it fills the same matrix we hand-authored in step 1 — the dogfood report becomes engine output, not prose.
  3. Contract-side checks (on-chain reads). For a smart-account / upgradeable-contract config, read the real permission state via viem: owner() / getOwners(), proxy admin slot (EIP-1967), and pause authority (paused() + who holds PAUSER_ROLE). These populate the contract half of the matrix with verified cells, not documentation claims.

Basic and buildable. Out of scope for v1: writing anything, simulating transactions (that's the Wallet service's simulate()), multi-chain crawling, and auto-discovery of every role in an arbitrary contract — v1 checks a known, small set of authority surfaces.


2. User scenario

Dana, a small-dapp founder (a developer/operator, not an end-user — the Auditor's audience is builders), runs her app on an embedded-wallet provider and isn't sure who could actually move her users' funds if a vendor were compromised. (End-user "Mina" appears in the other Jayverse docs; the Auditor's persona is a builder, so she has her own name.)

  1. She opens the Authority Auditor and either pastes her config (provider name + the relevant settings, or a contract address + chain) or picks a preset — e.g. "Privy embedded wallet, default recovery" or "Safe 2-of-3 with a Timelock proxy admin."
  2. She submits. The app runs the rules engine over the config and, if an address was given, reads on-chain permissions.
  3. Seconds later she sees a filled authority matrix: rows are actions (Sign a tx, Recover the account, Export the private key, Change the policy/upgrade, Pause), columns are actors (User, Provider, Our backend / owner key, Guardians, Nobody). Each cell says alone, cooperation, or cannot, colored by severity.
  4. She spots the row that matters: Export private key → Provider: alone flagged high, with an evidence link to the provider's key-export doc — meaning the vendor is a single point of custody failure. A cell she assumed was safe, Upgrade → Owner key: alone, is marked "not verified — no test exercised this" rather than pretending certainty.
  5. She clicks Share and gets a public report URL she can send to a co-founder or paste in a security review. The report is read-only and carries the same evidence links and badges.

No key was touched, nothing was signed, nothing was custodied — the tool only read config and public chain state.


3. What the web app shows (screen by screen)

Screen A — Input

Screen B — Authority matrix (the core)

Screen C — Shareable report page

How the config gets in — three tiers

The provider-config half of the input can arrive three ways, trading secrecy for accuracy:

Tier How Secrets? Accuracy
1. Manual form / paste (default) the operator types the settings none inferred (doc-only)
2. Exported config JSON paste the non-secret config from the provider dashboard none accurate, credential-free
3. Provider API (opt-in) a read-scoped API key; the Auditor calls the provider yes — a credential auto + verified

Default is tiers 1–2 — they preserve the "we never ask for keys or secrets" promise (tier 2 is the sweet spot: accurate and credential-free). Tier 3 is a clearly-labeled opt-in that upgrades cells to verified; if used, the key is used server-side once, never stored, and read-scoped where the provider supports it. (The on-chain half never needs an API — public RPC reads, no key.)


4. The flow

                 ┌────────────────────────────────────────────┐
   config in ───▶│  Normalizer  → canonical Config object      │
 (form / preset  └───────────────┬────────────────────────────┘
  / address)                     │
                                 ▼
                     ┌───────────────────────┐        ┌──────────────────────────┐
                     │   Rules engine        │        │  On-chain readers (viem)  │
                     │  evaluate(config)     │◀──────▶│  owner()/getOwners()      │
                     │  provider parsers     │  reads │  EIP-1967 admin slot      │
                     │  → cells + rule ids   │        │  paused() + PAUSER_ROLE   │
                     └───────────┬───────────┘        └──────────────────────────┘
                                 │  Matrix (cells × evidence × severity)
                                 ▼
                     ┌───────────────────────┐
                     │  Report renderer      │ → matrix table + badges + share URL
                     └───────────────────────┘

From matrix to action — decisions, levers, timing

The matrix is a decision list. For every dangerous cell the question is: "is it acceptable that this actor can do this alone? If not, make it require cooperation or a delay." Each finding resolves to accept, fix, or avoid — but who can act depends on who holds the authority:

Beyond per-cell fixes the report drives: prioritize the HIGH single-points-of-failure first; vendor due-diligence before adopting; communicate custody posture (share the report); and catch regressions by re-running after any config change or upgrade (a change can silently add an authority).


5. Cooperate with existing services


6. Implementation sketch

Matrix schema (actions × actors × evidence):

type Verdict  = "alone" | "cooperation" | "cannot" | "unknown";
type Severity = "critical" | "high" | "medium" | "info";
type Verified = "test" | "on-chain" | "doc-only" | "not-verified";

interface Cell {
  action: Action;        // sign | recover | export-key | change-policy | pause | upgrade
  actor:  Actor;         // user | provider | backend-owner | guardians | nobody
  verdict: Verdict;
  severity: Severity;
  verified: Verified;
  ruleId: string;        // which rule produced this cell
  evidence?: string;     // URL: provider doc, explorer link, or test id
  note?: string;
}
interface Matrix { config: string; cells: Cell[]; generatedAt: string; }

What's new vs reused

Risk note — wrong cells are worse than no cells. A confidently-wrong matrix gives false safety, which is more dangerous than an empty one. Therefore: every cell must carry an evidence link or an explicit "not verified" mark; the engine never emits a bare verdict. Doc-only claims are visibly weaker than test/on-chain-verified ones, and unknown is a first-class verdict, not a gap we paper over.

Open questions


The Authority Auditor is pure read / analyze, so it consumes no Chainlink itself. Chainlink appears here the other way round: an oracle / price feed is an actor whose authority the matrix must capture

So for the Auditor, "if the feed is wrong" isn't a runtime guard — it's a row worth auditing. (Umbrella map: README.md.)

Config-not-code trust facts — a Phase 3 target (LayerZero DVN)

The Auditor's whole thesis — the most security-relevant fact is absent from code review because it lives in configuration, not code — has a clean external test case: LayerZero's Decentralized Verifier Network. An application picks its verifier set; skip the choice and it accepts a default (usually "LayerZero Labs' own DVN, 1-of-1"), and nothing in the repo names who verifies its cross-chain messages.

Concrete Phase 3 target: read an OApp's verifier config (lz:oapp:config:get) and render it as an authority row — not just who verifies, but how many of whom, after how many block confirmations (required DVNs · optional DVNs + threshold · confirmations). "The default" sounds neutral; "trusting one company's DVN, 1-of-1" does not — naming it is the whole value, the same job this tool already does for sign / recover / upgrade.

It generalizes the product beyond wallets: a pluggable verifier set, an oracle feed address, a 7715 session-key scope, a mandate bound — all the same shape, a load-bearing decision recorded outside code. The Auditor's reach is every "who am I trusting right now, written where a change would break" fact, on-chain config included. (Rail-side view: jayverse-token-bridge.md; agent-side: jayverse-rabbit.md.)