# Develop locally

Point your app at a **local devnet** while you build: a real `anvil` chain, a
real registry contract, a real funded tenant — all disposable, all free. No
production USDC, no production gas, no production tenant capacity spent while
you iterate.

## The one command

```bash
just devnet
```

Equivalent, if you're not using `just`:

```bash
docker run --rm -p 127.0.0.1:8545:8545 -v "$PWD/.grantor:/out" grantor-devnet
```

This builds (once) and runs a container that starts `anvil` on
`127.0.0.1:8545`, deploys a fresh registry + `MockUSDC`, creates and funds
tenant `1` to `Active`, and writes a dev-config to `.grantor/grantor-devnet.json`:

```json
{
  "network": "grantor-local-devnet",
  "rpcUrl": "http://127.0.0.1:8545",
  "chainId": 31337,
  "registry": "0x9770e9b01684269f089b752eaa9d9936d77f197b",
  "mockUsdc": "0x8593e0dcf11ab46bf0dd02e025c1b8146f56030e",
  "devTenant": 1,
  "adminKey": "0x3f4a…9f0fa",
  "allowInsecureOrigin": true,
  "expiresAt": 1786077198
}
```

Leave the container running in a terminal while you develop; `Ctrl-C` (or
`docker rm -f`) to stop it.

## Point a `DeedVerifier` (or an agent) at it

Read the four fields you need straight out of the config: `rpcUrl`,
`registry`, `chainId`, `devTenant`. TypeScript, matching the shape in
[Getting started](getting-started.md#the-quickstart):

```ts
import { DeedVerifier } from "../../sdk/verify/ts/pkg/grantor_verify_wasm.js";
import { Registry } from "../../sdk/verify/ts/src/registry.js";

const cfg = JSON.parse(fs.readFileSync(".grantor/grantor-devnet.json", "utf8"));

const verifier = new DeedVerifier(
  cfg.rpcUrl,
  Registry.devnet(cfg.registry),  // a custom address is only ever legal on a devnet chain id
  cfg.chainId,
  cfg.devTenant,
  "http://localhost:3000",   // audience: what deeds must be scoped to
  "http://localhost:3000",   // origin: what THIS deployment is
  300,                       // max deed TTL, seconds
  30,                        // chain-read cache TTL, seconds
  cfg.allowInsecureOrigin,   // true — required for a localhost/http origin, see below
  Math.floor(Date.now() / 1000),
);
```

Python, Go and Rust take the same ten arguments, in the same order — see
[Sovereign tier § Every capability, every language](../sovereign-tier.md#every-capability-every-language).
`Registry.devnet(address)` is hard-gated to chain ids `31337`/`1337` — the
first chain read self-checks `eth_chainId` and refuses on any other chain,
so this construction is structurally unusable outside a local devnet. A
custom registry address on a real chain requires an [enterprise
license](enterprise-registry.md) (`Registry.dedicated(address, license)`) —
the standard SDK (`Registry.canonical()`, until mainnet launch populates the
map) cannot express one at all.

`cfg.allowInsecureOrigin` is `true` because a local devnet's origin is
`http://localhost:…`. Grantor's [fail-closed origin
policy](../sovereign-tier.md#fail-closed-origin-policy) refuses a non-HTTPS,
non-routable origin by default, so localhost development needs the opt-in. Pass
`true` here; leave it `false` (the default) on a real deployment.

## Use `adminKey` to set up test fixtures

`adminKey` is tenant `1`'s admin — the same key that deployed the registry —
so it can sign anything a real tenant admin could, against your throwaway
chain:

**Create another tenant** (tenant `1` already exists and is `Active`; make
more if your test needs multiple):

```bash
PRIVATE_KEY=$ADMIN_KEY CONTRACT_ADDR=$REGISTRY USDC=$MOCK_USDC TIER=1 \
  ISSUER_KEY_ID=0x0000000000000000000000000000000000000000000000000000000000000002 \
  forge script contracts/script/SetupTenant.s.sol --rpc-url $RPC_URL --broadcast
```

**Register a test agent** (`agent-zk`) against tenant `1`'s membership tree:

```bash
cast send $REGISTRY "registerZkAgent(uint256,uint256)" 1 $COMMITMENT \
  --private-key $ADMIN_KEY --rpc-url $RPC_URL
```

**Sign an origin vouch** for your dev origin, so `authenticate()` /
`ZkAgent.mintDeed`'s [origin provenance](../sovereign-tier.md#origin-provenance)
check passes locally:

```bash
MSG=$(node --input-type=module -e "
import { originVouchBinding } from './sdk/ts/pkg/grantor_sdk_wasm.js';
process.stdout.write(originVouchBinding(1, 'http://localhost:3000',
  'http://localhost:3000', 0, $(( $(date -u +%s) + 86400 )), true));")
cast wallet sign --private-key $ADMIN_KEY "$MSG"   # strip the 0x → your VOUCH_SIGNATURE
```

The trailing `true` is `originVouchBinding`'s own `allowInsecureOrigin`
argument — same reason as above, needed because the vouched origin is
`http://localhost:…`.

## Good to know

- **It resets after 24 hours.** The container runs `GrantorRegistryDevnet` —
  `GrantorRegistry` with one difference: `status()` reports every tenant
  `Inactive` once `block.timestamp` passes `devnetExpiry` (`expiresAt` in the
  config). Restart `just devnet` for a fresh 24 hours. Chain state is in-memory
  `anvil`, so a restart also wipes whatever tenants/agents/vouches you set up.
  (`LIFETIME_SECS` overrides the default.)
- **The registry address changes on every run.** Each start deploys from a
  freshly generated key, so `registry` and `adminKey` differ every time.
  **Re-read the emitted config after a restart** — don't hardcode addresses
  from a previous run.
- **`network: "grantor-local-devnet"` + `chainId: 31337`** are in the config
  so your own code can guard against pointing anything but a dev build at this
  chain — check `network` (or `chainId`) before wiring up something that could
  reach a real environment.
- **Reachable only from this machine.** The `docker run` maps
  `-p 127.0.0.1:8545:8545`, so the published port is bound to host loopback.
  (`anvil` itself binds `0.0.0.0` *inside* the container — it must, since Docker
  forwards host traffic to the container's bridge IP, not its loopback.) To
  reach it from elsewhere, change the host-side mapping explicitly, e.g.
  `-p 0.0.0.0:8545:8545`.

## ⚠️ `adminKey` is a throwaway devnet key

`adminKey` is generated fresh, on your machine, every single run, and funded
only from `anvil`'s well-known dev account. It is **never** a production
key and holds nothing of value. Do not reuse it for anything real, do not
fund it on a real chain, and do not put it anywhere near a production
tenant's admin set.

## See also

- [Getting started](getting-started.md) — the same `DeedVerifier`/`grantorExpress` wiring, pointed at a real chain.
- [Sovereign tier § Fail-closed origin policy](../sovereign-tier.md#fail-closed-origin-policy) — why `allowInsecureOrigin` exists and when to set it.
- [Sovereign tier § Origin provenance](../sovereign-tier.md#origin-provenance) — what an origin vouch is for.
- [Agent tokens](agent-tokens.md) — enrolling and authenticating an `agent-zk` agent, the full recipe.
- [Errors](errors.md) — every error code a relying party branches on.
