close

agents & wallets · oauth 2.0 / oidc

Give agents permission,
not credentials.

Agent authority infrastructure — with no authorization server.

A static API key is all-or-nothing: whoever holds it can do everything, until you rotate it and break everyone. A deed is bounded authority — scoped to named actions, short-lived, revocable on-chain — and an agent can hand a sub-agent a narrower slice, never a wider one.

People sign in the same way — wallet or passkey, arriving as a pairwise pseudonym, never an email — and everything lands as a standard OIDC token your stack already verifies.

the whole idea

Authority only narrows.
Never widens.

A grant is a tree. Every hand-off can shrink what a sub-agent may do — scope, uses, time — and can never grow it. Revoke once, on-chain, and the whole subtree dies.

a delegation chain — enforced, not promised
you
 └─ grant  search, fetch          · 20 uses · 60 min
    └─ research agent
       └─ delegate  search        · 5 uses · 10 min — narrower ⇒ verifies
          └─ worker
             ├─ search  → ALLOWED
             ├─ fetch   → DENIED   not delegated to this hop
             └─ write   → DENIED   never granted anywhere

# one on-chain call revokes the cohort — every deed below it dies at once
one authority — acquired, narrowed, revoked
agent → GET https://api.example/tools                     # no credential
401  WWW-Authenticate: Grantor-Deed realm="https://api.example",
             discovery="/.well-known/grantor-deed"
agent   discovery → challenge → mints a bounded deed    # no signup, no human
agent → GET https://api.example/tools   X-Grantor-Deed: eyJ…
200                              # verified in-process, against the chain
 
agent   delegate  search · 5 uses · 10 min → sub-agent   # narrower ⇒ verifies
sub   → search "release notes"            → ALLOWED
sub   → delete_everything                  → DENIED   CapabilityDenied — no effective grant
 
you     grantor-mcp revoke --child sub               # one on-chain call
sub   → search "release notes"            → DENIED   EpochRevoked — the subtree is dead
 
# that's grantor.

Watch a denial happen in your own terminal, zero setup: npx -y @grantor/mcp demo · the 60-second guide → · or the five-act browser demo →

00 — the whole thing

Verify a deed.
That's the integration.

No SDK ceremony, no callback dance, no service to call. You hold a deed the caller presented; you check it against the chain; you get claims back.

// npm i @grantor/verify
import { DeedVerifier, Registry } from "@grantor/verify";

// Registry.canonical() resolves the on-chain registry for you — no address to pin.
const verifier = new DeedVerifier(RPC, Registry.canonical(), CHAIN_ID, TENANT, AUDIENCE, ORIGIN, 120, 30, false, now);

// the challenge is yours — your nonce, your session store, single-use
const claims = await verifier.verifyAt(deed, challenge, now);

// membership, revocation and billing were all checked against the
// chain inside that one call.

// then mint your session JWT — yours, or one call:
// const jwt = sessionJwt(claims.sub, AUD, TENANT, YOUR_SIGNING_KEY, ...);
// your OIDC stack carries on unchanged either way.
# pip install grantor-verify
from grantor_verify import DeedVerifier, registry

verifier = DeedVerifier(RPC, registry.canonical(), CHAIN_ID, TENANT, AUDIENCE, ORIGIN, 120, 30, False, now)

# the challenge is yours — your nonce, your session store, single-use
claims = await verifier.verify_at(deed, challenge, now)

# then mint your session JWT — yours, or one call:
# jwt = session_jwt(claims, YOUR_SIGNING_KEY, ...)
# your OIDC stack carries on unchanged either way.
// go get chaingrantor.com/grantor-verify-go
v, err := verify.NewDeedVerifier(rpc, verify.RegistryCanonical(), chainId, tenant, audience, origin, 120, 30, false, now)

// the challenge is yours — your nonce, your session store, single-use
claims, err := v.VerifyAt(deed, challenge, now)

// then mint your session JWT — yours, or one call:
// jwt, err := verify.SessionJwt(claims, YOUR_SIGNING_KEY, ...)
// your OIDC stack carries on unchanged either way.
// Cargo.toml: grantor-verify = { features = ["sovereign-chain"] }
use grantor_verify::sovereign_gate::verify_deed;

// the challenge is yours — your nonce, your session store, single-use
let claims = verify_deed(
    &deed, &policy, &challenge, now, &gate,
).await?;

// then mint your session JWT — yours, or one call:
// let jwt = session_jwt(&claims, YOUR_SIGNING_KEY, ...)?;
// your OIDC stack carries on unchanged either way.
// an agent mints its own deed — no token endpoint, no issuer
const deed = await agent.mintDeed(
  RPC, Registry.canonical(), TENANT,
  audience,
  origin,     // where YOU are
  challenge,  // yours
  now + 60,
  // vouch — before proving
  vouchSig, vouchEpoch, vouchExp,
  false, now,
);

// attach it to the request; you verify it the same way every time
fetch(url, { headers: { "X-Grantor-Deed": deed } });

// the key must be registered on-chain for this tenant.

Same call in every SDK — TypeScript, Python, Go and Rust today, more as bandwidth allows. grantor-verify is a library, not an endpoint.

Build without spending a cent: docker run grantor-devnet stands up the whole thing on your machine — registry, funded tenant, vouch — and hands your SDK a config to point at. Develop locally →

01 — agent-native

Your agents adopt it
themselves.

No sales call, no human approving a ticket. An agent mid-task reads a machine spec and wires itself in — keys registered, scoped and revoked on-chain. The agent's proof is the credential; there's no endpoint to call.

  1. 1
    Fetch a challenge from your API
    your own nonce, single-use
  2. 2
    Prove membership, in zero knowledge
    against the on-chain tree
  3. 3
    Attach the deed to the request
    X-Grantor-Deed header
  4. 4
    Call the API
    verified in-process, no round trip
sovereign — no issuer, no token endpoint
# sovereign: there is no issuer, and no token endpoint
GET https://api.your-company.example/challenge
# ← { "challenge": "b1c4…" }   the RP's own nonce

# the agent proves membership of the on-chain tree, in zero knowledge,
# bound to that challenge — the proof IS the credential
GET https://api.your-company.example/resource
  X-Grantor-Deed: eyJtb2RlIjoiYWdlbnQtemsi…

# ← 200. verified in-process, against the chain. nothing else ran.

Gate an MCP server. No authorization server.

MCP authorization is optional — but when a server implements it, the spec's route is standing behind an OAuth 2.1 authorization server. A deed-gated server installs a library instead: DeedGuard verifies the credential in-process, against the same on-chain registry that gates billing. Two doors in.

And for the other direction — what your own sub-agents are allowed to do — the @grantor/mcp permission broker replaces handing them your credentials. One command wraps any stdio MCP server, unmodified, in an enforced grant — named tools, a use budget, an expiry:

npx -y @grantor/mcp wrap --tools search,fetch --max-uses 20 --ttl-secs 3600 -- npx some-mcp-server

Sub-agents narrow the grant onward, never widen it; escalations are denied; revocation is one on-chain call. Zero-setup sandbox, live against the production registry: npx -y @grantor/mcp serve

user-sig

Public door

Any wallet may mint — permissionless by design. The deed proves the tenant's bill is paid; authorization past that is your own layer.

agent-zk

Fleet door

Only a commitment the tenant admin registered can produce a proof. REQUIRE_MODE=agent-zk refuses anything else at the exchange.

server.mjs — mount the guard
const g = grantorExpress({
  verifier,
  app,
  challengeEndpoint: "/auth/challenge",
  chainId: CHAIN_ID,
  modes: ["user-sig", "agent-zk"],
  vouchSignature: VOUCH_SIGNATURE,
  vouchEpoch: VOUCH_EPOCH,
  vouchExp: VOUCH_EXP,
});
app.get("/auth/challenge", g.challenge);

Read the MCP guide — the quickstart runs the whole proof on a local chain: just mcp-e2e.

For agents you build or control — off-the-shelf MCP hosts (Claude Desktop et al.) speak spec OAuth.

for machines If you're an AI agent, start here → /llms.txt

02 — proof

Don't take it on faith.
Read the credential.

Every mode below ships today. Underneath is the whole exchange — what the caller runs, what crosses the wire, and what you run.

Every combination ships today.
who's authenticatingsovereign
human wallet-derived, app-scoped key
user-sig
human passkey, browser-origin-bound
user-passkey
smart wallet EIP-1271 contract signature (Safe & co)
user-1271
human zero-knowledge proof of your user allowlist
user-zk
agent zero-knowledge membership proof
agent-zk
the caller mints a deed
// an agent, or a wallet
const deed = await agent.mintDeed(
  RPC, Registry.canonical(), TENANT,
  audience,
  origin,     // where YOU are
  challenge,  // yours
  now + 60,
  // vouch — before proving
  vouchSig, vouchEpoch, vouchExp,
  false, now,
);

// no issuer was called
the deed — this is the whole credential
{
  "v": 1,
  "mode": "user-sig",
  "tenant": 7,
  "aud": "https://app.example",
  "challenge": "rp-challenge-1",
  "exp": 123456,
  "sub": "794b8e61a658…",
  "pubkey": "02881ee55896…",
  "signature": "d33b8f4077…"
}

mode is user-sig or agent-zk. sub is recomputed from the key on verify, never trusted from the wire. No issuer signed any of it.

you verify it
// 1. a challenge you remember
const challenge = randomUUID();

// 2. check what comes back
const claims = await v.verifyAt(
  deed, challenge, now,
);
// throws ChallengeMismatch,
// Expired, BadProof, StaleRoot,
// TenantInactive, Chain

// 3. your session, your rules
setCookie(mySign(claims.sub));

A revoked agent is rejected. An unpaid tenant is rejected. A replayed challenge is rejected. Enforced by a public contract and your own verify call.

Signing up is the same story: createTenant on that public contract, then USDC to it. No form, no account, no server of ours in either path. Our own dashboard is no exception — it signs in with a deed (admin-sig) through grantor-verify, on its own walled-off entry point: an ordinary relying party cannot accept an admin deed, by design.

Try it live

beyond identity

A deed carries more
than who you are.

Not three more products — the same deed, carrying more. One credential can confer bounded authority, prove private membership, or stand in for a software license — and your app checks all of it with the same local verify call it already makes.

ships today

  • deed verification & signed credentials — TypeScript, Python, Go, Rust
  • capability delegation & attenuation, verified through the chain
  • MCP broker (wrap / serve) & DeedGuard
  • agent fleet membership (agent-zk) · anonymous user gating (user-zk)
  • on-chain revocation & billing · standard OIDC output

preview — not yet audited

Structure-hiding multi-hop delegation (zk-chain): one proof folds the whole chain, hiding even the hop count and the intermediaries. Novel cryptography awaiting an independent circuit audit and a multi-party setup ceremony. Explore it; don't yet rely on it for production authority.

03 — pricing

Pay the registry.
Not a vendor.

There's nothing to buy but capacity — apps, signing keys, agents, team members — metered on-chain, the same way for every tenant. Your tenant is a row in a public contract.

Free
$0capped · time-boxed

A real taste — the whole product, on-chain.

  • 1 app · 1 signing key · 2 agents
  • 1 team member
  • No feature gates on Free, either
  • Expires a fixed window after signup
  • Full docs & llms.txt
Start free Available at launch
Scale
$45/mo

For growing products & agent fleets.

  • 25 apps · 10 signing keys · 250 agents
  • 25 team members
  • High volume · usage analytics
  • Grace period, never a cliff
  • Priority support
Get started Available at launch
Enterprise
Customlicense · support

Commercial terms and a human to call.

  • Commercial license & security review
  • Custom caps and terms
  • SLAs & dedicated support
  • Deployment help, in your infra
Talk to us

“Why not just an OAuth token?”

An OAuth token says this client has permission. A deed can say this agent holds a narrowed slice of that permission, received from another agent — delegation and attenuation live in the credential, no child ever exceeds its parent, and the whole chain checks in one local call. And no authorization server had to mint it.

“Why pay you if you run nothing?”

You're buying standing in the registry, not a service — verification itself is gated by it, and a tier is capacity. The public record anyone can check is the part worth paying for.

“What happens if you disappear?”

Nothing. No process of ours runs in your auth path; the trust anchor is a public contract, readable by anyone. No auth server, no auth company to withdraw. It lives on Base today, and the verifier is chain-agnostic — another chain is one more entry in the canonical map.

“Is this production ready?”

Invariant + fuzz coverage on the contract, end-to-end suites on the stack, no runtime dependency on us to fail. Don't trust it — run the suites first.

You can take your balance back.

Topping up credits your tenant, it doesn't pay us — funds sit in the contract, and withdrawBalance returns anything undrawn to an address you choose. Only periods you actually consumed are ours. There is no minimum, no lock-in and nobody to email, because the withdrawal is a function call and the balance was never in our custody. The registry has no owner power to touch it: an invariant test asserts its USDC holdings always equal the sum of tenant balances. Fund once and forget it: periods draw themselves from your prepaid balance — no transactions, no gas, nothing to remember until your next top-up.

Billed on-chain in USDC per active period — rates are published in the registry contract, readable before you pay a cent. Free tenants are capped and expire a fixed window after creation, anchored to signup so switching tiers doesn't reset it. New to USDC? Fund with a card — card → USDC on Base in ~15 minutes; KYC with the exchange, never with us. Or pay in any liquid token — ETH, WETH, USDT, DAI, cbBTC — live on Base today: swapped to USDC on-chain, inside your own transaction.