account

Your account. Payments, agents and keys in one place.

One account, made from a passkey or a wallet. What it can do follows from what the address owns on chain, so there is no separate agent account: register an agent from here and it is yours. No key is stored on this device and nothing secret reaches the server.

your account

reading this tab's session…

payments

Every payment here is a transaction on Monad you can read with cast. The balances above are what this address holds.

register or repoint an agent

Compose the document, watch what each field would mean, then send the transaction with your own key.

which chain this agent is registered on

Only chains where the ERC-8004 registries are actually deployed are offered, read from the chain registry rather than hardcoded. Which chains and what it takes to add one → Your identity lives here; the work can settle on any chain we can read.

the document

skills
where the record lives

Costs more gas once, then cannot rot. We took this option for our own record after measuring what happens to hosted ones.

what this document would mean

  • describes itselfA name and a description. Without them the registration is a token with an owner and nothing a buyer can read.
  • cannot return a deletionA data: URI has no host to ask, so it cannot 404. More than 8,000 agents on Monad point at a document whose host says it is not there (live count at /operators).
  • has a callable endpointEvery skill here is declared and not reachable. Fewer than one Monad agent in three hundred publishes an http endpoint at all: kanmani.xyz/agents shows the live count.
  • follows ERC-8004 registration-v1type, name, description and services in the spec's shape, so any ERC-8004 reader finds the endpoints without guessing which key an author used.
  • names its protocolA service named A2A or MCP tells a broker how to call it. A bare URL makes every caller guess, which is how most probes of Monad agents fail.
  • points back to its registrationregistrations: [{ agentId, agentRegistry }] lets a reader who found the document confirm which on-chain agent it belongs to. Only knowable after the first registration.
  • declares a trust modelWithout supportedTrust, the spec says the registration is for discovery only, not trust.
  • can take a paymentAn endpoint that answers an x402 challenge can be paid by any agent with USDC, no account needed. Linter at kanmani.xyz/hire checks the challenge is settleable.
  • asserts no payment it has not receivedA registration document is not the place for a payment claim. Most payment claims on Monad cite a transaction that is not on the chain they name (live count at /claims). Ours asserts none.
  • fits on chainOur verifier caps a fetched document at 65,536 bytes, so a record above that cannot be read back by the thing that checks it.
the bytes that go on chain181 bytes
{
  "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
  "x402Support": false,
  "active": true
}
  • • A name is required. A record with no name describes nothing.
register(agentURI)

to 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 on chain 143

0xf2c298be000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000b5646174613a6170706c69636174696f6e2f6a736f6e3b6261736536342c65776f6749434a306558426c496a6f67496d68306448427a4f6938765a576c776379356c6447686c636d563162533576636d637652556c515579396c615841744f4441774e434e795a576470633352795958527062323474646a45694c416f6749434a344e44417955335677634739796443493649475a6862484e6c4c416f6749434a6859335270646d55694f694230636e566c436e303d0000000000000000000000

authority

The registry decides who may write: setAgentURI reverts for anyone who is not the owner and nothing here can add to that. What a passkey adds is the three things the chain cannot do for you. It tells you that you are not the owner before you pay gas to find out, bind the transaction to the document you actually previewed and give you an authority that reconstructs on another device instead of living in one browser profile.

or log in with a wallet →

Or skip the passkey entirely: take the calldata above and send it from any wallet. Nothing here is custodial either way, because there is no key on our server that could sign for you. Signing in adds three things a copied calldata cannot. It checks you own the agent before you pay gas, it leaves a receipt bound to the document and it can send the transaction itself from the account your passkey derives.

After the transaction lands, the indexer sees it and the record is measured like every other: a MetadataUpdate re-runs the ladder for that agent, so a repointed document updates its own verdict with both timestamps kept. How each rung is decided →

what this will and will not do

  • It will show you the exact bytes. The JSON, the URI, the byte count and the calldata are all on screen before anything is sent, and the calldata decodes back to the document you were shown. That is asserted in a test rather than promised here.
  • It will not sign for you. There is no key on our server that could. Send the calldata from any wallet or sign in with a passkey and send it from the account that passkey derives. Both are yours.
  • It will not fabricate a listing. No agent appears because we made one. An agent appears because the registry emitted an event and the indexer saw it, which is the same path every other row on this site took.
  • It will refuse a payment claim. A registration document is not the place for one. If a draft mentions a payment proof, the consequence list says so, because most payment claims on Monad cite a transaction that is not on the chain they name and the fix starts with not making the claim.
  • Verifying yourself is free. A failing verdict is not a scarlet letter. Repoint the document and the MetadataUpdate re-runs the ladder so the record corrects itself with both readings kept. Verdicts that have changed →

why data: is the default

The recommendation carries our own measurement and we took it ourselves.

We probed all 2,151 distinct registration documents on Monad. Agents point at a document whose host answered and said it is not there, 8,093 of them sharing one expired preview deployment across 7,962 wallets. Those registrations are still valid on chain, with a valid owner and a valid URI and they will stay that way forever while describing nothing. A data: URI cannot 404 because there is no host to ask.

When we registered ourselves as agent 10252 we wrote our record into tokenURI rather than hosting it, a day after measuring that. Hosting is still offered here, because a hosted document is a legitimate choice and the point is that the consequence is visible rather than hidden. The clusters behind that number →

mera · webauthn prf · trust-identity-ai

Four keys from one passkey. The server holds none of them.

One passkey ceremony gives four independent keys: the account you sign in with, the identity that signs verification receipts off chain, the on-chain attestation key a contract recovers as the verdict signer and the key that encrypts anything private. Clearing storage loses nothing and a second device reproduces the same keys exactly.

create or restore

No seed phrase, no extension, no key on our server.

Creating is one prompt on most authenticators. On ones that do not evaluate PRF during creation it is two, because a second assertion has to run. That depends on your authenticator rather than on this site, so it is stated rather than promised. Signing in afterwards is always exactly one.

This same passkey pays. Open a scoped signing session and send a gasless Monad transaction →

what this site has stored about you

Live readout of this origin, not a claim. While you are logged in, sessionStorage holds one entry: your public address and how you signed in, cleared when the tab closes. No key is ever in it, so clearing it loses nothing: the keys come back from the passkey.

reading

localStorage

reading

sessionStorage

reading

cookies

The passkey is created as a discoverable credential, so the browser offers it with nothing from us. There is no cached credential ID and no returning-user path, which is why there is no returning-user path that can break. Sign in on a second device and the address above will be identical, because it is a deterministic function of the PRF output and nothing else.

how the four keys are derived

One WebAuthn assertion returns a 32-byte PRF output. That output is run through HKDF-SHA256 with a distinct info string per key, so the four are independent: holding one tells you nothing about the others.

prfOutput  = getPasskeyPrfOutput({ rpId })        one ceremony, one prompt

account    = HKDF(prfOutput, info="kanmani.iris.v1")          -> secp256k1 -> EVM address
attestation= HKDF(prfOutput, info="kanmani.optic.v1")         -> ed25519   -> signs receipts
onchain    = HKDF(prfOutput, info="kanmani.optic-onchain.v1") -> secp256k1 -> verdict signer
content    = HKDF(prfOutput, info="kanmani.vitreous.v1")      -> AES-256-GCM key

The honest limit: HKDF makes the four keys independent of each other, not independent of the PRF output. Anyone holding those 32 bytes holds all four. That is equally true of deriving from four separate salts, at a different point in the flow, so saying so beats implying a compartmentalisation that does not exist.

A salt costs a ceremony and a ceremony costs a prompt. Four namespaces as four salts would be four prompts. Category Labs, who build Mera, confirmed on 2026-09-18 that HKDF namespacing counts as genuinely namespaced. It is also their own pattern: Mera's secret vault derives its AES key from the same PRF output with fixed HKDF info, explicitly so it is separated from other keys derived from that output.

authenticators, tested rather than assumed

PRF is not universal and you pick the authenticator, not us. Published here so nothing about this is a surprise mid-demo.

authenticatorwhereprf
Google Password ManagerChrome, Edgeyes
iCloud KeychainmacOS 15+, iOS 18+yes
1Passwordextension and appyes
Windows Password ManagerWindows 11 25H2+yes
YubiKey 5C Nanohardwareyes
Proton Passextensionyes
Chrome local profileno Google Password Managerno
Bitwardenextensionno
Dashlaneextensionno

On desktop Chrome only passkeys saved to Google Password Manager carry PRF, because the local profile authenticator lacks hmac-secret. A password-manager extension that intercepts WebAuthn can produce a passkey that exists and returns no PRF output at all. When that happens this page names the cause and the fix rather than throwing.

what the server never gets

  • no session cookie carrying authority. A cookie may cache. It may never authorise.
  • no private key, ever. All four are derived in the tab and stay there.
  • no credential ID cached anywhere. The passkey is discoverable, so the browser offers it unprompted.
  • no recovery path. Losing the passkey loses the keys. Said plainly rather than implying a recovery that does not exist.

A database breach here exposes no key material, because there is none to expose. That is a stronger property than most applications have and it falls out of the constraint rather than being bolted on. How the rest of it is decided →