Sponsored Content

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.