Getting started#
Your app verifies a deed — a self-certifying credential the caller mints itself — with a library call and an RPC read. No auth server runs anywhere. TypeScript below; the same calls in Python, Go and Rust.
The quickstart#
// Imports are by path — @grantor/verify is not yet on npm. This is exactly
// how the runnable example examples/mcp-server/server.mjs does it today.
import express from "express";
import { DeedVerifier } from "./sdk/verify/ts/pkg/grantor_verify_wasm.js";
import { grantorExpress } from "./sdk/verify/ts/src/express.js";
import { Registry } from "./sdk/verify/ts/src/registry.js";
const app = express();
const verifier = new DeedVerifier(
process.env.RPC_URL, // any RPC endpoint — the registry check is an eth_call, no gas, no API key
Registry.canonical(), // the SDK pins the canonical GrantorRegistry for you — no address here
Number(process.env.CHAIN_ID), // which chain the canonical map (and every chain read) resolves against
Number(process.env.TENANT_ID), // your tenant (step 0)
"https://api.example.com", // audience: what deeds must be scoped to
"https://api.example.com", // origin: what THIS deployment is — never taken from a request
300, // max deed TTL, seconds
30, // chain-read cache TTL, seconds
false, // allowInsecureOrigin: true only for a localhost/dev origin
Math.floor(Date.now() / 1000), // construction-time clock
);
const g = grantorExpress({
verifier, app,
challengeEndpoint: "/auth/challenge",
chainId: Number(process.env.CHAIN_ID),
modes: ["user-sig"], // add "agent-zk" to admit an enrolled agent fleet
vouchSignature: process.env.VOUCH_SIGNATURE, // step 0 — bare hex, no 0x
vouchEpoch: Number(process.env.VOUCH_EPOCH ?? 0),
vouchExp: Number(process.env.VOUCH_EXP),
});
app.get("/auth/challenge", g.challenge);
app.get("/api/me", g.protect, (req, res) => res.json({ you: req.deed.sub }));
app.listen(8930);
That is the whole server side: mounting auto-publishes /.well-known/grantor-deed, g.challenge issues single-use challenges, and g.protect verifies the deed (crypto + on-chain billing check) and puts the claims on req.deed. Sessions stay yours — mint whatever you already mint, or use the SDK's one-call session JWT.
Registry.canonical()is empty until mainnet launch. The compiled-in chain map has no entries yet, so a real-chain construction errors, naming the chain — expected today, not a bug. Develop againstRegistry.devnet(...)(below) until then. A custom registry address on a real chain requires an enterprise license; the standard SDK cannot express one otherwise.
Developing? Don't spend anything.
just devnet(Develop locally) spins up a free, disposable local chain with the registry, a funded tenant, and an origin vouch already deployed, and writes anrpcUrl/registry/devTenant/adminKeyconfig the snippet above reads — so you can skip Step 0 entirely until you move to a real chain.
Step 0: a tenant and a vouch#
Two one-time artifacts, neither of which is an account.
A tenant on the registry. createTenant plus funding on GrantorRegistry — the register page walks any wallet through it. On a local devnet:
PRIVATE_KEY=$DEPLOYER CONTRACT_ADDR=$REG USDC=$USDC TIER=1 \
forge script contracts/script/SetupTenant.s.sol --rpc-url $RPC --broadcast
An origin vouch — the tenant admin signs your deployment's origin so holders can refuse imposters before signing anything:
MSG=$(node --input-type=module -e "
import { originVouchBinding } from './sdk/ts/pkg/grantor_sdk_wasm.js';
process.stdout.write(originVouchBinding(1, 'https://api.example.com',
'https://api.example.com', 0, $(( $(date -u +%s) + 86400 * 30 ))));")
cast wallet sign --private-key $ADMIN_KEY "$MSG" # strip the 0x → VOUCH_SIGNATURE
Point a caller at it#
- Humans with a wallet → Wallet login (
user-sig) - Humans with a passkey → Passkey login
- Autonomous agents → Agent tokens (
agent-zk)
The caller fetches /auth/challenge, mints a deed against it, and sends X-Grantor-Deed + X-Grantor-Challenge. Your /api/me answers with a pseudonymous sub. See it run end to end: just mcp-e2e.
Next#
- Concepts — the mental model behind all of this.
- Verify a deed — errors, session JWTs, scaling the challenge store.
- Errors — every code a relying party branches on, and which are 401 vs 503.
- Sovereign tier — the full reference: discovery, origin binding, every check the verifier makes.