# Software licensing with Grantor

A **license is a deed.** The licensed software verifies it **locally**
against the public on-chain registry — no license server, no phone-home, no
activation backend to run or keep online. Revocation, tiers and billing all
ride primitives that already ship: a license is an enrolled [`user-zk`
member](user-gating.md), a paid tier is a delegated [capability
grant](capabilities.md), and the license fee **is** the tenant's on-chain
billing. If you haven't already, read [Concepts](concepts.md) for the deed
mental model this builds on.

The runnable reference this guide describes lives at `examples/licensing/`
and `crates/grantor-verify/tests/licensing_e2e.rs` (`just licensing-e2e`).

## Check a license, at a glance

The licensed software's side — verify, then gate the paid tier:

```js
// the licensed software: verify the presented license deed locally against
// the public registry — no license server anywhere in this call.
const { claims, grants } = await verifier.verifyWithCapabilityAt(deedJson, challenge, now);

// pseudonym-only: the one line the software is allowed to log about who is
// running it. No wallet address, no Semaphore identity, no commitment.
console.log(`valid license for tenant ${claims.tenant} — basic unlocked (sub=${claims.sub})`);
```

```js
import { CapabilityGuard } from "@grantor/verify";

// gate a pro-only feature on the vendor's delegated tier grant. A basic-only
// license (no grant attached) is DENIED here with CapabilityDenied — the
// license itself is still perfectly valid, it just carries no pro grant.
CapabilityGuard.authorize(grants, {
  res: "app://features/pro", act: "use", args: { tier: "pro" }, now,
});
```

`grants` is `[]` for a `basic`-only license, so the same
`verifyWithCapabilityAt` call is safe to run on every check — a feature that
needs no tier just never calls `CapabilityGuard.authorize` at all. See
[Verify a deed](verify-tokens.md) and [Capabilities](capabilities.md) for
what each call does underneath.

## A license is a `user-zk` enrollment

Issuing a license is enrolling a licensee's public commitment in the
vendor's own on-chain user tree — the exact mechanism [User
gating](user-gating.md) documents for anonymous, allowlisted human login,
reframed here as "issuing a license":

1. The licensee derives an identity from a wallet signature
   (`ZkUser.fromWalletSignature`) and hands the vendor the public
   `commitment()` — the signature itself never leaves their machine.
2. The vendor calls `registerZkUser(tenantId, commitment)` — this **is**
   issuing the license.
3. Each time the licensed software runs, it syncs the vendor's user tree
   from chain, proves membership, and mints a `user-zk` deed bound to a
   challenge the software itself issued (`ZkUser.mintUserDeed`).

The software verifies that deed with the exact same `verify_deed` /
`verifyWithCapabilityAt` call every other deed mode goes through — there is
no license-specific verification path.

## The pro-tier grant

A paid tier is a capability the vendor delegates to that specific license,
not a second credential. The vendor's registered agent key is the
delegation root (the same `registerAgentKey` requirement [every capability
chain](capabilities.md#delegating--delegate) has); it signs a `Delegation`
naming the tier and handing it to the licensee's own `user-zk` `sub`:

```json
{
  "res": "app://features/*",
  "act": ["use"],
  "cav": { "nb": { "tier": "pro" } }
}
```

The delegation's last `to` equals the license's own proven `sub` — the
capability chain's binding rule — so the tier is cryptographically bound to
*this* license, not a transferable second token. The licensee's software
attaches it with `mintCapabilityDeed` and presents one deed carrying both
the license and the tier; the vendor's software never issues or checks a
separate "tier credential."

## Revocation — two independent channels

Licensing has two separate revocation levers, each proven live against its
own chain deployment:

- **Revoking the license itself:** the vendor calls
  `revokeZkUser(tenantId, commitment)`. The next time the licensed software
  checks, the deed fails closed — `StaleRoot` — because `verify_deed` checks
  root recency on every request, not just at mint time. There is no grace
  period.
- **Revoking a tier, in bulk, instantly:** the vendor calls `bumpEpoch` on
  the tier grant's delegation cohort. Every deed carrying a link signed
  against the old epoch value is refused — `EpochRevoked` — while the
  license's own `user-zk` membership is completely untouched: a licensee who
  loses the `pro` tier this way keeps their `basic` license working.

These are two distinct mechanisms, not one revocation flow with two
outcomes: revoking a license does not touch any tier cohort, and bumping a
tier's epoch does not touch membership. Pick whichever matches what you're
actually taking away — the whole license, or just a paid tier.

## Anonymity

The vendor learns *"a valid licensee is running this"* plus a stable,
app-scoped pseudonym (`claims.sub`) — never a wallet address, never the
licensee's underlying Semaphore identity or commitment. The same licensee
gets the same pseudonym on every check against the same software, so it is
a safe primary key for per-license state, but it reveals nothing about
which enrolled member it belongs to. This is the differentiated pitch over
a conventional license-key or account-based scheme: the vendor cannot build
a profile of *who* is running licensed copies, only *how many* valid ones
are active.

## Billing is the license fee

There is no separate payment processor and no license-issuing backend to
keep funded. The vendor's tenant billing (`topUp`/`drawPeriod` on
`GrantorRegistry`) **is** the license revenue: a tenant that is `Active` or
in its `Grace` window issues and verifies licenses normally; a tenant that
lapses goes `Inactive`, and every license — even one for a genuinely
enrolled, never-revoked member — starts failing `TenantInactive` on its
next check. Reactivating the tenant (drawing a period) makes every
outstanding license start verifying again immediately, with nothing to
reissue.

## Also possible: per-deployment licensing with `agent-zk`

Everything above uses `user-zk` — an anonymous *per-user* license, one
enrollment per human. The same model works with [`agent-zk`](agent-tokens.md)
instead, for a *per-deployment* or *per-server* license (one enrollment per
install rather than per person) — same revocation, same tier-via-delegation
composition, same billing gate. This variant is a straightforward
substitution of the membership tree, not a different mechanism, but it is
**not** what the reference proves live; only the `user-zk` shape above has
a running end-to-end test.

⚠️ **Not to be confused with an [enterprise dedicated-registry
license](enterprise-registry.md)** — an unrelated concept that reuses the
word "license" for a different thing: this page is about YOUR product's
end-user software licenses, built as a composition of Grantor primitives on
the shared canonical registry; the enterprise page is about Grantor's own
operator-signed grant that lets an SDK point at a *custom* registry address
at all.

## See also

- [User gating](user-gating.md) — `user-zk`, the enrollment/revocation
  mechanism a license is built on.
- [Capabilities](capabilities.md) — the delegated-grant mechanism a tier is
  built on, including `bumpEpoch` and the full delegation-chain rules.
- [Agent tokens](agent-tokens.md) — `agent-zk`, the per-deployment variant.
- [Verify a deed](verify-tokens.md) — the relying-party side in general.
- [Errors](errors.md) — every error code a relying party branches on
  (`StaleRoot`, `EpochRevoked`, `TenantInactive`, `CapabilityDenied`).
- [Enterprise registries](enterprise-registry.md) — a different, unrelated
  "license": Grantor's own operator-signed grant for a custom registry
  deployment.
