wallets & agents · oauth 2.0 / oidc

Stop shipping
static API keys.

OAuth with no authorization server.

Agents authenticate with a key they already hold — scoped to one audience, short-lived, revocable on-chain. People sign in with a wallet they already have — no email, no password, nothing personal to store or leak. Both arrive as a standard OIDC token your stack already verifies.

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 } from "@grantor/verify";

const verifier = new DeedVerifier(RPC, REGISTRY, TENANT, AUDIENCE, ORIGIN, 120, 30, false);

// 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

verifier = DeedVerifier(RPC, REGISTRY, TENANT, AUDIENCE, ORIGIN, 120, 30, False)

# 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, registry, tenant, audience, origin, 120, 30, false)

// 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, TENANT,
  audience,
  origin,     // where YOU are
  challenge,  // yours
  now + 60,
  // vouch — before proving
  vouchSig, vouchEpoch, vouchExp,
  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 each SDK — TypeScript, Python, Go and Rust today, more as bandwidth allows. grantor-verify is a library — there is no endpoint, and nothing of ours is running.

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 →

which one are you?

Four ways in.
Pick yours.

01 — why

Credentials you never
have to hold.

Three bad options, one bad assumption

Static API keys, an auth SaaS, or building it yourself — all three assume the caller is a person. Yours isn't.

Never hold personal data again

Your app receives a pairwise pseudonym — never an email, a password, or the wallet address. Nothing to breach, nothing to reset. Sovereign sign-in scopes it per app, so it is unlinkable across your other apps too.

Nobody runs anything

The credential is a deed: a self-proving instrument backed by a public register of record, not by a server you have to trust. Your app adds grantor-verify, checks the deed against the registry, and mints its own JWT — no crypto in your request path, no SDK lock-in.

02 — proof

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

Every cell below ships today. Underneath is the entire exchange — what the caller runs, what crosses the wire, and what you run. Three moving parts, none of them ours.

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, TENANT,
  audience,
  origin,     // where YOU are
  challenge,  // yours
  now + 60,
  // vouch — before proving
  vouchSig, vouchEpoch, vouchExp,
  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 — with none of our infrastructure running, because there isn't any.

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

03 — 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 are registered, scoped and revoked on-chain — verifiable by anyone, controlled by no one. There's no endpoint to call at all: the agent's proof is the credential.

  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.

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

beyond identity

A deed carries more
than who you are.

The same self-certifying credential can carry bounded authority, prove private membership, or stand in for a software license — each checked by the same local verify call, against the same public registry, with nothing of ours in the path.

preview The deepest rung — structure-hiding multi-hop delegation that folds an entire chain into a single proof, hiding even the hop count and the intermediaries — ships as a preview: novel cryptography that has not yet been independently audited. Explore it; don't yet rely on it for production authority. Everything else here is live today.

04 — pricing

Pay the registry.
Not a vendor.

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

Free
$0capped · time-boxed

A real taste, on-chain. No card.

  • 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
$99/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 pay you if you run nothing?”

You're not buying a service — there isn't one running. You're buying standing in the registry: verification itself is gated by it, and a tier is simply capacity. Software is the easy part; standing on a public record anyone can check is the part worth paying for.

“What happens to me if you disappear?”

Nothing. In sovereign mode no process of ours runs in your auth path, and the trust anchor is a public contract readable from any RPC, by anyone. No auth server. No auth company. That's an architecture, not a slogan — there is no service to withdraw.

“Is this actually production ready?”

The contract carries invariant and fuzz coverage, the verifier and the full stack ship end-to-end suites, and the sovereign path has no runtime dependency on us to fail. Don't take it on trust — run the suites before you commit a line of your own code.

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.

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; the window is anchored to signup, so switching tiers doesn't reset it. Long enough to ship something real, short enough to stay a trial rather than a permanent free tier.

get started

Register a tenant.
Free is on-chain too.

Add the verifier library — that's the integration. The free tier is a real, capped tenant registered on-chain — no card, no hosted sign-up. Scale up whenever you're ready; same registry, same tenant.

agents → /llms.txt · enterprise terms: get in touch