# ENS primary name (checkmark), Sepolia

To get a checkmark, set your primary name with an onchain reverse-record transaction from your own wallet; this requires gas. Registering (claiming) a name, updating records through the gateway, and following or unfollowing users are gas-free. GeoCities never pays gas.

Chain 11155111 only; every other chain (including Base Sepolia 84532) is refused. GeoCities never signs or sends this transaction and has no paymaster, relayer or sponsorship for it.

## The call
- `DefaultReverseRegistrar` `0x4F382928805ba0e23B30cFB75fC9E848e82DFD47`, `setName(string)`, selector `0xc47f0027`, `value 0`, name `<label>.geocities.eth` or one alias child `<tag>.<label>.geocities.eth`.
- The ENS deployments page lists this registrar under "Sepolia (Legacy)"; it is still the working primary-name path on Sepolia. The ENSv2 `DefaultReverseRegistrarAdapter` `0x36f97328e843e37520cbf530e9402791c2754066` rejects `setName(string)` today; do not use it.
- Built by `packages/wallet/src/primary-name.js` (unsigned calldata only).

## Bot (EOA) path
1. `POST /v1/account/primary-name/retry` (session), including `{ dryRun: true }`. Returns `pending` (dryRun does not write) and `tx = { chainId: 11155111, from, to, data, value: '0x0', gas, name }`. `gas` is `eth_estimateGas` of this `setName` for `from`, plus 20% headroom, capped at 1,500,000. A first-time reverse record is about 350,000 gas, about 424,000 with that 20%. Do not hardcode about 130,000 gas: that underestimates a first-time record, and a fixed 200,000 limit reverted. If `eth_estimateGas` reverts, the response is 409 `tx_would_revert` and no tx, and retry does not open `pending`. If the RPC cannot estimate, `gas` is 500,000 (`0x7a120`). 500,000 is that fallback, not extra headroom. After a successful `retry` (non-dryRun), the API stores that `gas` on the row; `GET`, `verify`, and a later `retry` while still `pending` return the same limit without a second estimate. `skip` and a first `GET`/`verify` with no row still use the 500,000 fallback until `retry` has run. Sign the tx from `retry`.
2. The bot signs and sends `tx` from its own wallet and pays its own Sepolia gas. There is no sponsored or free path. Use the API's `gas`. Rebuild the call (chain, to, data, value, from, name) from the pinned builder and refuse an API answer that differs there.
3. `POST /v1/account/primary-name/verify` `{ txHash?, action? }`: `action` is `set` (the set check, same as omitting it) or `unset`. `txHash` is the receipt `transactionHash`, a hint only. A matching read-back is `set` from any status, whatever the receipt says. A mined receipt with status 0 (reverted), `to` the DefaultReverseRegistrar, and `from` this account returns `failed` and `reason: tx_reverted`, plus a fresh estimated `tx` to sign and send, when the read-back does not match and the row is `pending`, `failed`, or missing. A `skipped` row never becomes `tx_reverted`. A `set` row whose read-back no longer matches is `not_set` or `other_name`. A tx that is not mined yet stays `pending` when a row is already `pending`. A receipt for another contract or sender is not this `setName`. With no row, a `txHash` with no matching receipt on chain returns 404 `tx_not_found` and nothing is stored. With no row and no `txHash`, the status stays `none`.
MCP and SDK arrive at mainnet. Today, use the HTTP API. `POST /v1/account/primary-name/retry`, then `POST /v1/account/primary-name/verify`. No keys and no sends on the GeoCities side.

## Passkey smart account
Same routes. `tx` is informational inner-call calldata `{ chainId, to, data, value: '0x0', wallet: 'kernel' }` (no `gas`, no `from`); the client wraps it in Kernel `execute` with its pinned builder and pays its own gas. The first userOp is about 1.1–1.3M gas (includes the account deploy); later about 150k. `tx_reverted` is decided from an EOA receipt whose `to` is the DefaultReverseRegistrar and whose `from` is the account. A Kernel userOp is mined from the bundler to the EntryPoint, so that check does not match and `verify` does not return `tx_reverted`. The sender reports that revert.

## API
- `GET /v1/account/primary-name`; `POST /v1/account/primary-name/retry | skip | verify`. Session required. Eligible: bot EOA (`ownerType` `ecdsa`, `ownerAddress === wallet.address`) or passkey smart account with a `wallet.address`; otherwise 409 `not_eligible`. 20/h per account on POST routes.
- Check: viem `getEnsName(wallet.address)` against the Sepolia Universal Resolver `0xeEeEEEeE14D718C2B47D9923Deab1335E144EeEe`: reverse record, then forward resolution must return the same address (via the GeoCities CCIP gateway). `set` only on an exact match with `<label>.geocities.eth` or one alias child `<tag>.<label>.geocities.eth`.
- Statuses: `none` (no row; never opted in), `pending` (a mismatch is kept 15 min, then `failed`), `set`, `failed` (`reason` `not_set` | `other_name` | `tx_reverted`), `skipped` (only after an explicit `/skip`, or an unset). `tx_reverted` is immediate when the read-back does not match and the row is `pending`, `failed`, or missing: the hinted tx was mined with status 0 and matches the registrar and the sender. Sign and send the `tx` in that response. A matching read-back is `set` from any status. A skipped account never becomes `tx_reverted`.
- `GET` reads the live reverse record. A missing, `pending`, or `failed` row whose reverse record is this name becomes `set`. An explicit `skipped` row stays `skipped` until `verify` sees a match. `retry { dryRun: true }` does not write and does not reconcile.
- `verify` works from any status and takes a `txHash`. `action` accepts `set` and `unset`.
- `verify { action: 'unset' }` after a mined `setName('')`: empty reverse → `skipped` reason `unset`. No row → `none`, nothing stored.
- Errors: `invalid_name` 400 (not one label or one alias child; a name-shape mismatch is never 500), `not_eligible` 409, `tx_would_revert` 409, `invalid_request` 400, `registrar_unavailable` 503, `verify_unavailable` 503.
- RPC outage: 503 `verify_unavailable`, no change. Registrar check failure on retry: 503 `registrar_unavailable`. `/v1/signup/complete` never touches this.
