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.
wallets & agents · oauth 2.0 / oidc
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.
human or agent checks the chain, then mints a deed — your app verifies there too, no issuer, ever
00 — the whole thing
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?
My agents need to call APIs.
Today that's one static key per agent. Instead: a short-lived token scoped to one audience, minted from a key registered on-chain — revoke one agent without touching the rest.
agent auth →My users are wallets, not emails.
Sign-in is a wallet signature. Your app receives a pairwise pseudonym — no email, no password, not even the wallet address. Nothing personal to store, reset or leak.
the proof →I'm shipping an MCP server, or a tool API for agents.
They present a deed — a credential that proves itself. You verify it in-process against a public registry, issuing no keys and storing no secrets. No authorization server to stand up.
gate an mcp server →Nothing third-party in my auth path.
Nobody runs anything — the credential verifies against the public chain. No service of ours ever sits between your users and your API.
how it deploys →01 — why
Static API keys, an auth SaaS, or building it yourself — all three assume the caller is a person. Yours isn't.
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.
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
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.
| who's authenticating | sovereign |
|---|---|
| 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 |
// 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
{
"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.
// 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.
03 — agent-native
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.
# 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.
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.
Any wallet may mint — permissionless by design. The deed proves the tenant's bill is paid; authorization past that is your own layer.
Only a commitment the tenant admin registered can produce a proof.
REQUIRE_MODE=agent-zk refuses anything else at the exchange.
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.
beyond identity
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.
I need to grant scoped authority, not just prove identity.
A deed can confer bounded, delegable, revocable authority. Scope it to specific actions, then hand a narrowed slice to a sub-agent — which can only ever attenuate it, never widen it. Verify the whole chain locally; revoke a cohort instantly with one on-chain call. The delegator can even stay hidden — an enrolled member delegated this, not which one — and separate chains can be made unlinkable from each other.
delegation & capabilities →Members-only — but private by construction.
A human proves membership of your allowlist in zero knowledge. Your app learns “an enrolled member logged in” plus a stable pseudonym — never which member. Enroll a commitment on-chain, revoke it on-chain; there's no directory of who your members are, anywhere.
anonymous gating →License my software without a license server.
A license is a deed. Your software verifies it with a local call — no license server, no phone-home — revokes on-chain, and rides paid tiers on delegated capability grants. You learn “a valid licensee is running this,” not who.
software licensing →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
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.
A real taste, on-chain. No card.
For devs shipping to production.
For growing products & agent fleets.
Commercial terms and a human to call.
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.
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.
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.
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
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