Survival LLM
A free AI assistant and survival library (1.5 million offline articles) that runs on your own computer, with no internet. Shows the source of every answer. Mac and Windows.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證HoodFi Names is the go to naming service on Robinhood chain
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗hoodfi.eth is an ENS subname service on Robinhood Chain (chain id 4663, an Arbitrum-technology Ethereum L2, mainnet since July 2026). Names of every length mint publicly, in one transaction, from $3.
Names of 1–3 characters are premium inventory at $15 / $10 / $5, on public sale since 2026-10-29. Before that they could only be minted with short-name credits, earned by donating years to the parent name's expiry; those credits still mint them free. The parent name itself is renewed by the HoodFi 2-of-3 Safe, 6 years a month, until its expiry reaches 2127 — 100 years past launch.
Names like blake.hoodfi.eth are lifetime ERC-721s: no renewals, no fees, no
revocation, full owner control of records — resolvable in every ENS-aware wallet
and app through the ENS Universal Resolver.
Independent project. Not affiliated with, endorsed by, or connected to Robinhood Markets, Inc.
| Contract | Network | Address |
|---|---|---|
| HoodFi Safe (2-of-3) | Ethereum mainnet + Robinhood Chain | 0x837Fa2d033f1F7275452D780a5E54Eda903C1330 — owns hoodfi.eth, the registrar, the L2Registry root and the L1 resolver; renews hoodfi.eth monthly |
HoodfiDonations v2 | Ethereum mainnet | 0x588c597bA6a3685511617bCece8457ca7648c9c0 (verified; deploy block 25698544). Closed since ENSv2 — still read for shortCredits and nameExpires |
HoodfiL1Resolver | Ethereum mainnet | 0x37215Dd89D0Fd4ea0Dbce690bDe58490fB7f7cF2 (verified; live resolver for hoodfi.eth) |
L2Registry (hoodfi.eth) | Robinhood Chain | 0xf2bABA012244bdD7445129597350054E1B3aEe5C |
HoodfiRegistrar v2 | Robinhood Chain | 0x56be5565acc823f4195c2cf3b9046C083633209a |
HoodfiSites | Robinhood Chain | 0x90517237F52caC977398CA1391b3B006bA028c99 (the website builder's paywall) |
L2RegistryFactory | Robinhood Chain | 0x6bA501514244D42726b12Be9f19C13AA870692B1 |
| USDG (Paxos stablecoin) | Robinhood Chain | 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168 |
| CCIP gateway | Cloudflare Workers | https://ccip.hoodfi-mcp.com/v1/{sender}/{data}.json — the value in HoodfiL1Resolver.url() onchain; hoodfi-gateway.dmpay.workers.dev also answers |
| Credit voucher signer | Cloudflare Workers | https://hoodfi-gateway.dmpay.workers.dev/voucher/{address} |
| ERC-721 metadata | Cloudflare Workers | https://hoodfi-gateway.dmpay.workers.dev/nft/{tokenId} |
| Token art (NFT image) | Cloudflare Workers | https://hoodfi-gateway.dmpay.workers.dev/art/{label}.png (generated 1000×1000) |
| Per-name share page | Cloudflare Workers | https://www.hoodfi.name/{label} → /n/{label} (per-name OG tags) |
| Per-name share card | Cloudflare Workers | https://www.hoodfi.name/card/{label}.png (generated 1200×630) |
| Donation ledger (closed) | Cloudflare Workers | https://hoodfi-gateway.dmpay.workers.dev/donations |
| Reverse lookup (address → names) | Cloudflare Workers | https://ccip.hoodfi-mcp.com/owner/{address}/names → { address, primary, primarySource, names: [{ name, node, addr, resolves, avatar }] }. ENS getEnsName is null for any holder who hasn't set a primary on mainnet, so this is how an app labels an address. primary only ever names a name that resolves back to the address; a failed chain read is a 502, never an empty list. Cached 60s |
| MCP server (for agents) | Cloudflare Workers | https://hoodfi-mcp.com/mcp (own worker; www. and hoodfi-mcp.dmpay.workers.dev also answer) |
| Site publishing | Cloudflare Workers | POST /site/{label} → pin · POST /site/{label}/confirm → verify payment on chain |
| Website | hosted + IPFS | https://www.hoodfi.name · https://hoodfi.eth.limo |
| Website builder | hosted | https://build.hoodfi.name |
hoodfi.name/{label} is a real URL — a vercel.json rewrite maps it to the
gateway's share page, restricted to the [a-z0-9-]{1,32} charset the registrar
enforces. Vercel checks the filesystem before rewrites, so every real route wins;
the tradeoff is that an unknown single-segment path renders "available" rather
than 404ing. /n/{label} still resolves, so links shared before this keep working.
Three of those endpoints exist because the site is a static export, which can
serve only one HTML document per route. A crawler fetching /search/?q=gm sees
byte-identical markup for every name, so per-name cards need a route that renders
per request; and a wide eth_getLogs needs an archive-capable RPC, which the
browser can only be given by inlining the key into the bundle. Both jobs therefore
live on the worker, where the key is a secret. Every ledger row still carries its
transaction hash, so the feed stays checkable without trusting us.
The v1 contracts (0x12c03c69…11cE2 donations, 0x75d61F7d…4f51 registrar) are
retired. The L2Registry was not redeployed, so every name minted under v1 —
and the NFT baseURI and CCIP wiring — carried over untouched.
Proven live: test1000.hoodfi.eth was minted on Robinhood Chain and resolves on
Ethereum mainnet via viem.getEnsAddress() through the Universal Resolver, both
before and after the v2 registrar swap.
HoodfiRegistrar.status() on
Robinhood Chain — no waitlist, no allowlist./manage. The registrar has no say in it. Every change
in the form saves as one multicall, so it costs a single signature however
many records moved./search renders any name's records for anyone, no wallet
required, and hoodfi.name/{name} unfurls with a generated card. /manage
previews that same card live against your unsaved draft.Addresses cover Ethereum and every EVM chain from one addr record — a wallet
resolves against L1 whatever network it's on, and the address it gets is valid on
Base, Arbitrum, Polygon, Optimism and Robinhood Chain alike. Bitcoin, Solana and the XRP
Ledger are separate ENSIP-9 coinType records, stored in each chain's own binary encoding
rather than as the text you typed.
A name can also be a website. The EIP-1577 contenthash record is editable from
/manage — paste an IPFS CID or an IPNS key and the name serves that site, the same
mechanism that serves hoodfi.eth itself. /search shows the record and a Visit
button for anyone looking the name up. The codec (frontend/lib/contenthash.ts,
IPFS and IPNS only) is hand-rolled rather than pulled from a dependency and has its
own regression test: cd frontend && npm test. Contenthash needs no gateway change —
the CCIP path forwards whatever record call it is given, and HoodfiL1Resolver
already advertises 0xbc1c58d1.
Verified end to end on gm.hoodfi.eth: the record was set from /manage, and the
site is served at gm.hoodfi.eth.link and gm.hoodfi.eth.limo (the UI links
.link; both are run by eth.limo). So a subname
resolves through the public ENS gateways with no per-name setup — that was an open
question until the first record existed, and it is why /search links a visitor to
the name rather than to the CID's IPFS gateway.
| Length | Price | Availability |
|---|---|---|
| 1 character | $15 | public since 2026-10-29; free with a credit |
| 2 characters | $10 | public since 2026-10-29; free with a credit |
| 3 characters | $5 | public since 2026-10-29; free with a credit |
| 4+ characters | $3 | public |
Prices are exact in USDG. The ETH leg is a fixed amount per tier set by the owner (0.0055 / 0.0037 / 0.0018 / 0.0011 ETH), so it lands near the dollar figure, not on it.
A visitor can sign in with email, Google, X, Discord, Apple, GitHub or Farcaster and mint a name in that session. Reown AppKit mints a Reown embedded wallet behind the login, so someone who has never held crypto gets an address, funds it, mints, and signs record changes without installing anything. Self-custodial wallets are untouched and remain the default path — Robinhood Wallet and MetaMask are the two featured connectors.
Getting an embedded wallet to sign on chain 4663 took two fixes that are worth recording, because neither is documented and both fail silently:
chain: null on writes (frontend/lib/useWriteContractCompat.ts). wagmi asserts
the connector's chain before sending, and the embedded wallet cannot answer that
question the way an injected wallet does. The wallet is not the problem; the
assertion is. chain: null skips it, and only for the AUTH connector — every other
wallet keeps wagmi's normal path.customRpcUrls pointing at Reown's Blockchain API (frontend/app/providers.tsx).
W3mFrameProvider.getRpcUrl() hands the secure site whatever sits first in
rpcUrls.default.http, and Reown's own WC_HTTP_RPC_SUPPORTED_CHAINS does not list
4663 — so the embedded wallet fell back to our gateway and never completed a write.
The Blockchain API does in fact serve 4663; the constant is stale. This steers the
embedded wallet only: the app's own reads and writes keep the explicit transports in
lib/wagmi.ts, so the Blockchain API is never billed for page traffic.Proven live on Robinhood Chain and on mainnet. A signing prompt from an embedded wallet still reads "On Unknown" — cosmetic, upstream, and tracked at reown-com/appkit#5765 alongside a session that does not survive a page reload.
Signing out needs resetWalletSession (frontend/lib/session.ts) rather than wagmi's
disconnect: the embedded wallet keeps its own state — EMAIL, LAST_USED_CHAIN_KEY
— in IndexedDB, where localStorage holds only wagmi's pointer to it. Clearing the
pointer alone leaves a wallet that reconnects to an account nobody is logged into.
Every name is a subname of hoodfi.eth, so the parent has to outlive them all. The goal is its expiry at 2127, 100 years past launch.
Donations funded the first 15 years and closed on 2026-10-27, when ENSv2 went
live. HoodfiDonations renewed through ENS's v1 ETHRegistrarController, which
ENSv2 retired, so donate() and extend() now revert before any ETH moves.
The HoodFi Safe renews instead. hoodfi.eth is an unmigrated v1 name (v2 registry
status 1, RESERVED), and those renew through ENS's ETHRenewerV1
(0x56c447F308F463A01121C41E72b0E7e7557fCb68), paid in USDC. Anyone may renew any
name; the Safe does it once a month in 6-year blocks until the expiry reaches 2127:
| Block length | ENS price per year (5+ character name) |
|---|---|
| 1 year | $8.00 |
| 2 years | $7.00 |
| 3–5 years | $5.50 |
| 6+ years | $4.50 |
Six years is where the curve bottoms out, and six years ($27) costs less than five
($27.50). From the 2042 expiry, 85 years is 15 blocks — $382.50, against $680 as
single-year renewals. contracts/bash/SafeRenewHoodfi.sh builds each month's Safe
Transaction Builder batch: USDC.approve(renewer, quote) then renew(...). The
approval is the exact quote — it carries dust (six years is 27000071, not
27000000), and a rounded approval reverts. Every renewal is a public Safe
transaction, and the site reads progress from nameExpires(), not from the frozen
donation counter.
Each year donated earned one credit, and one credit mints any 1–3 character name free — before or after the public sale, with no expiry:
HoodfiDonations.donate() atomically called the official
ETHRegistrarController.renew() and credited shortCredits(address) on mainnet.
The contract has no withdraw function and never held funds. 15 credits were
issued before it closed.mintShortWithVoucher never checks shortsOpen, which is why opening the sale
took nothing from credit holders.build.hoodfi.name is a second Vercel project from this
same repo (root directory builder/). A name owner picks one of five templates, fills a
form, and publishes; the site is pinned to IPFS and their own contenthash record serves
it at <label>.hoodfi.eth.link. No hosting, no renewals, no account.
The first site on a name is free; republishing it costs $9.99. Live prices, read
straight off HoodfiSites:
| ETH | USDG | |
|---|---|---|
| First site on a name | free | free |
| Republish (any content change) | 0.003998 ETH | 9.99 |
Both launched at zero and ran that way through the paid path in full — the same
transaction, the same records, the same code — so the price that went live in August
2026 switched on a path already exercised rather than a branch nobody took. It moved
with one setPrices call and no redeploy, and the first build stays free.
setPrices is the only source of truth here; if this table and the chain ever disagree,
the chain is right.
publish(node, templateId, cid) records paidFor[node][keccak(cid)], so the receipt is
the CID:
AlreadyPaid rather than charging for a publish already owned.isPaid(node, cid) is a public read. The gateway pins when it
returns true — not as a favour it does, but as a fact it checks.isPaid before paying, so a run that fails after
the payment lands — a dropped connection, a closed laptop — is retried, not restarted.The alternative was a credit counter spent by a trusted "recorder", which would have meant giving the gateway a funded hot key purely to write down something the payer could state themselves. This way nothing holds a key and nothing has to be trusted to decrement.
publish(node, templateId, cid), in ETH or USDGisPaid on chainsetContenthash themselvesStep 5 costs a second signature and is the point: this service never holds a key to anybody's name. Anything pinned and never paid for is swept after 24 hours by a nightly cron, because step 2 has to be free for someone to see their site before buying it.
Five self-contained HTML documents — terminal, editorial, manifesto, product,
links — with their fonts embedded as base64. A published site fetches nothing at view time. A
page that silently reflows into a system face years later because a font host moved is not
the thing someone paid for. Faces are subsetted to Latin-1 + Latin Extended-A, 5–17KB
each; template ids are keccak256(slug), matching what the deploy script registered, so
the code and the on-chain registry cannot drift apart without the publish reverting.
HoodFi Tree (template id links) is the link-in-bio template: an ordered list of blocks (buttons, section
headers, YouTube/Spotify/SoundCloud players, images, an ETH tip jar and an email signup),
a social icon row, and an appearance system — six presets, dark or light, six accents, six
fonts, three button shapes, four backgrounds. It has its own form in the editor
(components/LinksForm.tsx) and keeps its rows in SiteData.blocks, so switching template
and back loses nothing. Its faces live in links-fonts.ts, regenerated by
scripts/build-links-fonts.sh, and a page embeds only the one it uses. Two deliberate
exceptions to "fetches nothing": a player embed loads from its provider, and the tip jar
talks to the visitor's own wallet — there is no other way for either to work. Embed
sources are rebuilt from the id in the pasted URL, never passed through, and tips go
straight to the owner's address on Robinhood Chain.
Every field is attacker-controlled and the output is a permanent pin on a hoodfi.eth
subdomain, so all text goes through HTML escaping and every URL through a scheme
allowlist. data: is refused alongside javascript: — a data URL carrying HTML executes
on the visitor's origin. There is no fixing a bad CID in the next deploy.
A template can be supplied by an NFT project rather than by us. Its holders build on it, and the project earns 30% of every publish, first sites and rebuilds alike.
struct Template { address payee; address collection; uint16 shareBps; bool active; }
publish(). The collections live on Robinhood
Chain — the same chain as the contract — so it is a plain balanceOf. No attestation to
sign, expire or forge. (An earlier design assumed mainnet collections and needed a
signed voucher; being on one chain deleted it.)owedEth / owedUsdg and the payee
calls withdraw(). Paying on publish would let one partner with a reverting receive()
brick publishing for their entire community — and it would present as "the builder is
down". The treasury is just another payee, so one accounting path covers both.setTemplate is owner-only, we accept
OpenSea-verified collections only, and every design is reviewed and built by us before
it goes live. Only our code ever emits the published HTML: a partner-supplied script
would be a wallet-drainer wearing a hoodfi.eth subdomain. active retires a template
instantly, with no redeploy.shareBps is capped at 50% — not a business rule, a fat-finger guard. An input of
100000 where 10000 was meant would otherwise pay ten times the price.Submissions are open at build.hoodfi.name/partner. The form validates the collection address against Robinhood Chain as it is typed, and reports "couldn't reach the chain" separately from "not a valid collection" — a read that did not happen is not a read that returned no.
https://hoodfi-mcp.com/mcp — a Model Context Protocol server over
streamable-HTTP, its own Cloudflare Worker in mcp/. It lets an AI agent search,
register and manage a name on Robinhood Chain conversationally. ENS has no
first-party equivalent.
| Tool | What it does |
|---|---|
hoodfi_check_name | Availability, the reason when a name is unavailable, and the price in both ETH and USDG |
hoodfi_build_registration_tx | The unsigned mint. Paying in USDG returns two steps (approve, then register) to run in order |
hoodfi_resolve_name | Owner, address records, text records and the website a name serves |
hoodfi_build_set_contenthash_tx | Points a name at an IPFS/IPNS site. Accepts a bare CID, an ipfs:///ipns:// URI or a gateway URL; "" clears it |
hoodfi_build_set_address_tx | Sets Ethereum, Bitcoin, Solana and XRP Ledger records, batched through multicall so the owner signs once |
hoodfi_list_names | Every name a wallet holds, subnames included, and which one it goes by |
hoodfi_build_set_text_tx | Profile records (avatar, description, url, X, GitHub, Discord, Telegram, email), batched, any depth |
hoodfi_build_create_subnames_tx | Up to 20 subnames under a name you own, kept or gifted, in one registry.multicall |
hoodfi_build_transfer_tx | Moves a name to another wallet; warns it is irreversible and that the address records stay behind |
hoodfi_build_set_primary_name_tx | The mainnet ReverseRegistrar.setName that makes the name the wallet's ENS primary |
hoodfi_prepare_send | Send ETH (Ethereum or Robinhood Chain) or SOL to a name: reads its address from the L2 registry and returns PayBox request_transfer arguments; refuses unminted names and missing records, flags a name that changed hands or had its address changed in the last two days. BTC is look-up only |
hoodfi_send_transaction | Broadcasts a signed transaction from a signing-only wallet (PayBox), only of the kinds above |
The server holds no keys and signs nothing. Every write tool returns steps for the
caller's own wallet, each with a complete transaction (nonce, gas, fees, chainId) so a
signing-only wallet such as PayBox signs it as given; hoodfi_send_transaction then
broadcasts what was already signed, and only HoodFi's own kinds of transaction. It
cannot mint a name, move one, or touch a record on its own. Write tools also render a
HoodFi card in clients that support MCP Apps (Claude, ChatGPT).
Adding it: no sign-in — in Claude, ChatGPT or Grok add a custom connector with the URL above and nothing else. Pair it with the PayBox connector to sign by voice; the steps are on hoodfi.name/mcp. Proven live 2026-10-18: registration, avatar/URL records and a mainnet primary name, all through PayBox.
Three things the tool descriptions state outright, because agents infer them wrong
otherwise: the registrar mints to msg.sender, so the name lands with whoever
signs and there is no recipient argument; 1–3 character names cost their premium
tier price, so the agent quotes it before building anything; and setting an
"ethereum" address writes two records —
mainnet coinType 60 and the Robinhood Chain coinType — because a name carrying only
one resolves in some clients and silently fails in others.
Address validation is refused rather than stored when it cannot be parsed, and the response says how much the check actually proved: a Bitcoin address is checksummed and a typo is caught; an Ethereum address is only verified when it carries EIP-55 capitalisation; a Solana address has no checksum at all, so a mistyped character is a different, equally valid-looking address that nothing here can detect. Agents are told to relay that distinction rather than report a uniform "valid".
0x…GET https://ccip.hoodfi-mcp.com/owner/{address}/names — no key, CORS open.
Forward resolution (gm.hoodfi.eth → address) works in every ENS library with no
setup. The other direction works only for holders who have set one as their ENS primary
name — one setName transaction on the mainnet ReverseRegistrar, from the
Use this as your primary name panel on /manage. getEnsName / useEnsName then return
it like any other primary. Most holders never pay for a mainnet transaction, though, so for
them getEnsName is null. Any app that starts from an address — a launchpad's "created by",
a trade feed, a chat sender — should ask this endpoint, which answers for both.
curl https://ccip.hoodfi-mcp.com/owner/0x645Cf432E829f9dEF6EB8E3974D3Aee4580CBCdd/names
{
"address": "0x645Cf432E829f9dEF6EB8E3974D3Aee4580CBCdd",
"primary": "nft.hoodfi.eth",
"primarySource": "inferred",
"names": [
{ "name": "nft.hoodfi.eth", "node": "0x…", "addr": "0x645C…BCdd", "resolves": true, "avatar": "ipfs://…" },
{ "name": "interns.hoodfi.eth", "node": "0x…", "addr": "0x645C…BCdd", "resolves": true, "avatar": null }
]
}
const res = await fetch(`https://ccip.hoodfi-mcp.com/owner/${address}/names`)
if (res.ok) {
const { primary } = await res.json()
label = primary ?? shortAddress(address)
} // on a 502 keep the address and retry later; don't cache "no name"
primary is the name to display, or null. It is only ever a name whose ETH
record resolves back to this address — the rule ENS applies to primary names — so a
name bought on secondary that still points at the seller labels neither of them.primarySource is "ens" when the holder set that name as their ENS primary (their
choice wins), "inferred" when they didn't and one was picked — top-level names over
subnames, then shorter, then alphabetical — and null with no primary. A reverse record
naming something this address doesn't hold, or that doesn't resolve back, is ignored.names is every name the address holds right now (ownership re-checked on chain,
not read from an index), with resolves and avatar for each.400 for a malformed address; 502 when the chain could not be read —
never an empty list, so "no name" and "couldn't check" stay distinguishable.Many wallets hold several hoodfi names, so the most common integration bug is showing the wrong one — usually the shortest. If the holder has set an ENS primary name, that is the name they chose; show it. Only fall back to a pick of your own when they haven't.
primary already prefers the holder's ENS
primary (primarySource: "ens") over the inferred pick.const ensName = await mainnetClient.getEnsName({ address }) // verified: resolves back to address
const owned = await yourHoodfiNames(address) // e.g. sorted shortest-first
const display =
userPickInYourApp ?? // an explicit in-app choice, if you offer one
(ensName && owned.includes(ensName) ? ensName : null) ?? // their primary, when it's a hoodfi name
owned[0] ?? // your default
ensName ?? // a non-hoodfi primary (e.g. alice.eth)
shortAddress(address)
getEnsName / useEnsName already apply ENS's forward check, so a reverse record
alone can't make an address claim someone else's name. Holders set their primary at
hoodfi.name/manage — one transaction on Ethereum
mainnet; L2 primary names (Base, etc.) aren't set by that page.
a–z, 0–9, hyphens (not leading/trailing), 1–32 characters onchain; every length
mints publicly since openShorts() (2026-10-29). Infra labels (www, api, hood,
robinhood, base, mcp, rpc, ens, eth, rh, …) are blocklisted on chain — no policy, no takedown
process, and no way to mint robinhood.hoodfi.eth and point it anywhere. Frontends
should ENSIP-15-normalize before submitting.
ETHEREUM MAINNET ROBINHOOD CHAIN (4663)
┌──────────────────────────┐ ┌───────────────────────────┐
│ HoodfiDonations (closed) │ │ L2Registry (Durin) │
│ shortCredits[donor] │ gateway reads │ ERC-721 subnames + │
│ nameExpires() │ shortCredits │ addr/text/contenthash │
├──────────────────────────┤ and SIGNS a ├───────────────────────────┤
│ HoodFi Safe (2-of-3) │ voucher │ HoodfiRegistrar │
│ renews monthly through │ ───────────────► │ register() ETH / USDG │
│ ETHRenewerV1, in USDC │ (no bridge) │ mintShortWithVoucher() │
├──────────────────────────┤ │ shortsOpen = true │
│ HoodfiL1Resolver │ │ │
│ apex records ONCHAIN │ └───────────▲───────────────┘
│ *.hoodfi.eth → CCIP │ EIP-3668 reads │ RPC
│ (ENSIP-10 wildcard) │ ◄────────────► CF Worker ───┘
└──────────────────────────┘ signed responses
▲
ENS Universal Resolver (0xeEeE…EeEe) + batch gateways
→ *.hoodfi.eth resolves in every ENS-aware wallet and app
Resolution design notes:
HoodfiL1Resolver (PublicResolver-
style storage + events). The hoodfi.eth website contenthash and address resolve
with zero gateway dependency, including for legacy non-ENSIP-10 clients and
eth.limo.OffchainLookup with a stuffedResolveCall(name, data, chainId, registry)
payload; the gateway forwards the inner record call to the L2Registry's own
onchain resolve() and returns a signed response the resolver verifies
(0x1900 ‖ resolver ‖ expires ‖ keccak(request) ‖ keccak(result), raw digest).supportsInterface advertises ENSIP-10 and the record profiles
(addr, multicoin addr, text, contenthash) — ethers-based clients probe
these before querying.stuffedResolveCall, tooling's resolve(bytes,bytes)
(0x9061b923), and the legacy raw abi.encode(name,data) tuple. It answers
GET (/v1/{sender}/{data}.json, .json optional) and POST.fallback(),
and distinguishes the two failure modes by walking viem's error chain.| Path | What |
|---|---|
contracts/ | Foundry workspace. Vendored Durin (MIT) with the hoodfi contracts in src/hoodfi/ |
contracts/src/hoodfi/HoodfiDonations.sol | Mainnet donations (closed since ENSv2) + the short-name credit ledger credits are still spent against |
contracts/src/hoodfi/HoodfiRegistrar.sol | Robinhood Chain registrar: paid mints + voucher mints |
contracts/src/hoodfi/HoodfiL1Resolver.sol | Mainnet apex + wildcard resolver |
contracts/src/hoodfi/HoodfiSites.sol | The website builder's paywall: publishes keyed by CID, partner template registry, pull payouts |
contracts/src/hoodfi/LabelUtils.sol | Shared label validation (mirrored in frontend/lib/labels.ts) |
contracts/scripts/hoodfi/ | Deploy scripts (donations / L2 stack / L1 resolver / UpgradeRegistrar) |
contracts/test/hoodfi/ | Unit + mainnet-fork tests, including a full rehearsal of the registrar upgrade |
contracts/bash/SafeRenewHoodfi.sh | Builds the monthly hoodfi.eth renewal as a Safe Transaction Builder batch: exact-quote USDC approval + ETHRenewerV1 renew, 6 years by default |
contracts/bash/SafeOpenShorts.sh | Builds the registrar's setBlocklist / openShorts() batches for the Safe on Robinhood Chain (BLOCKLIST_ONLY=1 for the blocklist alone) |
gateway/ | Cloudflare Worker (Hono + viem): CCIP-Read gateway, credit-voucher signer, NFT metadata and token art, share pages and cards, donation ledger, analytics sink |
gateway/src/rpc.ts | Shared clients. Dedicated RPC first, public endpoints behind it via fallback() — a public RPC rate-limits the worker's shared egress even when the same call works from a laptop |
gateway/src/ccip-read/query.ts | Separates "the chain answered no" from "we couldn't reach the chain"; only the first is ever signed |
gateway/src/handlers/getVoucher.ts |
HoodfiDonations (mainnet) — closed since ENSv2donate and extend call the v1 ETHRegistrarController, which reverts since ENSv2
went live on 2026-10-27, so both revert too. The view functions still answer, and
shortCredits is what every credit mint is checked against. totalYearsDonated
stays at 15 and finalize() can no longer be reached; the goal is tracked by
nameExpires() instead.
| Function | Access | Notes |
|---|---|---|
donate(uint256 numYears) payable | — (reverts) | Renewed hoodfi.eth atomically via the v1 controller and credited shortCredits[msg.sender]; refunded all excess |
extend(uint256 numYears) payable | — (reverts) | Renew without earning credits |
finalize() | anyone, once totalYearsDonated ≥ 100 | Unreachable now |
shortCredits(address) view | — | Cumulative credits earned — what the gateway attests |
quote(uint256 numYears) view | — | Live ETH cost from the ENS oracle |
nameExpires() view | — | hoodfi.eth expiry straight from the .eth registrar |
goalReached() / yearsRemaining() view | — | Progress against GOAL_YEARS (100) |
Events: Donated(donor, numYears, ethPaid, newExpiry, credits, totalYears),
GoalReached, Extended.
Invariant: contract balance is zero after every transaction (no withdraw
function exists).
HoodfiRegistrar (Robinhood Chain)| Function | Access | Notes |
|---|---|---|
register(string label) payable | anyone (4+ chars, or any length once shorts open) | Tier-priced ETH mint, excess refunded |
registerWithUsdc(string label) | same | Tier-priced USDG (6 decimals), straight to treasury |
mintShortWithVoucher(string label, uint256 totalCredits, uint256 expiry, bytes sig) | any credit holder, always | Free 1–3 character mint against a signed credit attestation |
status(string) / priceOf(string) view | — | 0 available · 1 taken · 2 locked · 3 invalid · 4 blocked; (wei, usdg) prices |
creditsAvailable(address, uint256) / voucherDigest(...) view | — | Reproduce the gateway's accounting and signing digest yourself |
openShorts() | owner, one-way | Opens 1–3 character names to public sale at tier prices. Called by the Safe 2026-10-29 |
setPaused(bool) / setCreditSigner(address) / setBlocklist(bytes32[], bool) | owner | Emergency stop, signer rotation, infra labels |
setPrices / setUsdc / setTreasury / withdraw | owner / anyone (withdraw → treasury) | Ops |
Minting sets default forward addresses for ETH (coinType 60) and Robinhood Chain
(ENSIP-11 coinType 0x80000000 | 4663 = 2147488311). After minting, owners
manage their own records directly on the L2Registry (addr/text/contenthash) —
no permission from the registrar needed.
HoodfiSites (Robinhood Chain)The website builder's paywall. Sells the right to have a site pinned; takes no authority over the name itself.
| Function | Notes |
|---|---|
publish(bytes32 node, bytes32 templateId, string cid) | Pay in ETH for one site on one name. Records paidFor[node][keccak(cid)]; excess refunded |
publishWithUsdg(bytes32 node, bytes32 templateId, string cid) | The same in USDG |
isPaid(bytes32 node, string cid) → bool | The gateway's entire authorisation check, and a plain read anyone can make |
quote(node, templateId, buyer) | Price in both currencies, eligibility, and how many sites the name has published |
canUseTemplate(templateId, buyer) → bool | Drives the picker; batched across templates through Multicall3 |
withdraw() | Pays out everything owed to the caller, both currencies. Pull, never push |
setTemplate(id, payee, collection, shareBps, active) | Owner only. shareBps capped at 5000 |
setPrices(firstWei, republishWei, firstUsdg, republishUsdg) | Owner only. All four launched at zero; since August 2026 the first site is still free and republishing is 0.003998 ETH / 9.99 USDG |
Only the name's owner may publish on it — otherwise a stranger could flip a name onto the
republish price permanently. Holdership for a gated template is checked with a low-level
staticcall, not try IERC721(...).balanceOf: try/catch does not catch a return-data
decoding failure, so a collection answering with empty data — or a mistyped address with
no code — would blow past the guard and revert the publish.
HoodfiL1Resolver (mainnet)ENSIP-10 resolve(bytes,bytes) routes apex queries to onchain storage
(self-staticcall dispatch) and subname queries to the gateway via
OffchainLookup. Owner functions: setAddr / setText / setContenthash
(apex records), setUrl, setSigner, setL2Registry.
# Contracts — unit tests (fast, no network)
cd contracts && forge test --match-path "test/hoodfi/*"
# + fork tests against the LIVE ENS controller
MAINNET_RPC_URL=https://ethereum-rpc.publicnode.com forge test --match-path "test/hoodfi/*"
# Gateway — typecheck, local run, signed-response smoke test (all 3 wire shapes)
cd gateway && bun install && bunx tsc --noEmit
SIGNER_PRIVATE_KEY=0x… L2_REGISTRY_ADDRESS=0x… bun src/index.ts # terminal 1
bun scripts/smoke.ts # terminal 2
# MCP server — its own worker, its own deploy
cd mcp && npm install
npm run typecheck
npm test # coin encoding vectors
npm run dev # wrangler dev; POST /mcp, GET / describes it
# PayBox path on a fork (see the header of test/paybox.fork.mjs for the anvil + esbuild lines)
BUNDLE=/tmp/hoodfi-node/index.mjs node test/paybox.fork.mjs
BUNDLE=/tmp/hoodfi-node/index.mjs node test/shortnames.fork.mjs
# Safe batches (Transaction Builder JSON; nothing is signed or sent)
contracts/bash/SafeRenewHoodfi.sh # monthly hoodfi.eth renewal, mainnet
contracts/bash/SafeOpenShorts.sh [labels...] # blocklist and/or openShorts, Robinhood Chain
# Builder (HoodFi Sites) — its own Next app, its own Vercel project
cd builder && npm install && npm run build # static export in out/
node scripts/make-og.mjs http://localhost:8899 # OG cards, shot from the built pages
bash scripts/build-fonts.sh # regenerate the subsetted faces
# Frontend
cd frontend && npm install && npm run dev
npm test # contenthash codec vectors
# production: npm run build → static export in out/ → bash pin.sh (needs PINATA_JWT)
frontend/.env.local (see .env.example): Reown project id, Pinata JWT, and the
deployed contract addresses (NEXT_PUBLIC_*). The UI degrades gracefully to a
pre-deploy state when addresses are unset.
gateway/scripts/e2e-local.ts drives the full pipeline against two local anvil
chains (L1 + a chain-id-4663 L2) and a locally running gateway: it registers a
name on the L2, then resolves it through the L1 resolver's complete EIP-3668
loop, verifying the signed proof onchain.
DEPLOY.md is the runbook, in the order it has to run: both v2
contracts, the gateway secrets, the frontend env for each of the two deployment
targets, and the post-deploy checklist.
The upgrade path matters more than the deploy itself. UpgradeRegistrar.s.sol
deploys the new registrar, loads the blocklist, wires USDG, then calls
addRegistrar(new) before removeRegistrar(old) so there is never a window
with no authorized registrar — and it leaves the L2Registry untouched, which
is what preserves existing names, the NFT baseURI and the CCIP wiring.
test/hoodfi/HoodfiRegistrar.fork.t.sol rehearses the whole thing against live
chain state.
The website ships to two origins from one export: a conventional host and IPFS.
NEXT_PUBLIC_SITE_URL differs per build (share-card images must be same-origin to
be fetchable), while NEXT_PUBLIC_CANONICAL_URL is identical on both so the two
copies don't compete in search.
Done, from the HoodFi Safe on Robinhood Chain, 2026-10-29:
setBlocklist([mcp, rpc, ens, eth, rh], true) — before the sale, so those labels
were never sellable for even a block.openShorts() — one-way. Credits keep minting short names free afterwards, so
nobody who donated lost anything by the sale opening.Ongoing, on Ethereum mainnet: one renewal a month, 6 years at a time, until
hoodfi.eth expires in 2127. Fund the Safe with the quoted USDC plus gas ETH, run
contracts/bash/SafeRenewHoodfi.sh, load the JSON into Safe{Wallet} → Transaction
Builder, and collect 2 of 3 signatures. DEPLOY.md §8 has the detail.
Since 2026-10-22 the HoodFi 2-of-3 Safe (0x837F…1330) holds the admin surface
rather than one key: hoodfi.eth's registry ownership and registrant, the registrar,
the L2Registry root (registry admin) and the L1 resolver. The donation contract's
owner was not moved — it is closed and holds no funds, so the only power left there
is its blocklist.
What that covers is bounded, and worth stating precisely, because most of the system is outside anyone's control:
HoodfiDonations has no withdraw function.HoodfiSites
cannot write, move or remove a contenthash record.What the Safe covers is the remaining admin surface: registry admin, setPaused,
setCreditSigner, setBlocklist, pricing, setTemplate, custody of the parent name,
and its renewal. Renouncing those instead would freeze the system permanently, which
is why the answer is a multisig and not renounceOwnership.
| Component | Guarantee |
|---|---|
| Parent-name renewal | Trusted to keep doing it. The Safe renews hoodfi.eth monthly through ENS's own renewer; nothing on chain forces it to. What is verifiable is the result: nameExpires() on the official .eth registrar, shown live on the site, so a missed month is visible to anyone. |
| Donations (closed) | Were trustless: ETH went to the official ENS controller inside the donor's own tx; the contract never held funds. |
| Minted names | Seizure-proof ERC-721s. No admin burn/transfer exists; re-minting an existing name reverts. Owners set their own records. |
| Short-name credits | Earned onchain on L1 (shortCredits), readable by anyone. The gateway only attests that public value — it grants nothing a donation didn't already earn, and the registrar enforces spending on-chain. A compromised signer could mint short names, not touch existing ones. |
| CCIP gateway | Trusted-signer: a malicious gateway could misreport records to L1 clients, but cannot touch L2 ownership. Signer is rotatable (setSigner); upgrade path to Arbitrum storage-proof verification with no ABI change. |
| Registry admin | Can approve registrars (which can edit records, never seize names). Held by the 2-of-3 Safe since 2026-10-22 — see Launch operations; renouncing instead would freeze the system permanently. |
| Published sites | Yours, and outlive us. The site is a static bundle on IPFS and the pointer is a contenthash on a name you own — neither depends on this service. The CID is handed to you at publish time precisely so you can re-pin it anywhere. HoodfiSites records payment; it cannot write, move or remove your record. |
| Site pinning | Trusted for availability, nothing else. We keep the pin alive; if we stop, the site stops resolving from our gateway but the CID stays valid and anyone can re-pin it. Unpaid pins are swept after 24 hours — paid ones never are, and the sweeper asks the chain rather than a flag we control. |
| Partner templates | Curated by hand, OpenSea-verified collections only, and only our code emits the published HTML. A partner supplies a design, never markup or script — a partner-authored script would run on a subdomain. Any template can be switched off on-chain with no redeploy. |
Known client limitation (inherited from ENS wildcard architecture): Trust Wallet's non-ETH coin send screen doesn't follow wildcard/CCIP resolution for offchain subnames; ETH addresses resolve everywhere.
Built on Durin by NameStone (MIT) —
contracts/src/{L2Registry,L2RegistryFactory,L2Resolver,L1Resolver}.sol and the
gateway skeleton are vendored from it; HoodfiL1Resolver is a modified fork.
NameStone's copyright notice is kept at contracts/LICENSE.
Everything else in this repository is MIT as well — see LICENSE.
Security reports: SECURITY.md.
Reads shortCredits on L1, signs the voucher the registrar accepts |
gateway/src/handlers/getSharePage.ts · getNameCard.ts | Per-name OG tags, and the 1200×630 card they point at (satori) |
gateway/src/handlers/getDonations.ts | The donation ledger, read with the private archive RPC |
gateway/src/handlers/postSite.ts | Pins a rendered site on an owner signature, confirms it against isPaid, and sweeps unpaid pins nightly. Writes nothing back to Pinata — the chain already answers whether a site was paid for |
gateway/src/handlers/postPartner.ts | Partner enquiries → one email via Resend. Delivers only to the fixed PARTNER_NOTIFY_TO; a caller-chosen recipient would make a public endpoint an open relay |
frontend/ | Next.js 16 static export → hosted + IPFS |
frontend/app/mint/ · app/manage/ · app/search/ | Search-and-mint, record editing for names you own, and the public name lookup |
frontend/components/MintPanel.tsx | The search card: live status, tier pricing, credit vouchers |
frontend/components/RenewalPledge.tsx | The Safe's renewal pledge on /short-names: block size, live ENS quote, live expiry, years and blocks left — all mainnet reads |
frontend/components/SearchPanel.tsx | Public lookup: reads the L2Registry directly, so it keeps working — and keeps telling the truth — when the gateway in front of it doesn't |
frontend/components/ProfileCard.tsx | The shareable card, used read-only on /search and as a live draft preview on /manage |
frontend/app/partner/ | Partner enquiries. Posts to the worker, since a static export has no server of its own; AddressToName.tsx makes the case by typing a 42-character address out and replacing it with a name |
frontend/lib/resolution.ts | Mainnet resolution check and the mint-date lookup, shared by both pages |
frontend/app/providers.tsx | AppKit + wagmi setup: email/social login, and the customRpcUrls entry that points the embedded wallet at Reown's Blockchain API for 4663 |
frontend/lib/useWriteContractCompat.ts | useWriteContract minus the chain assertion the embedded wallet cannot answer. chain: null, for the AUTH connector only |
frontend/lib/session.ts | Sign-out that clears IndexedDB, not just wagmi's localStorage pointer — the embedded wallet's own state lives there |
frontend/vercel.json | Rewrites /{label}, /n/, /card/ onto the worker. Not next.config — rewrites there are unsupported under output: export |
builder/ | HoodFi Sites — the website builder at build.hoodfi.name. A second Next.js static export from this repo; its own Vercel project with root directory builder/ |
builder/lib/templates/ | The five templates. One render function each, used for both the live preview and the pinned file — two renderers would let the preview disagree with what someone paid for |
builder/lib/templates/html.ts | Escaping and a URL scheme allowlist. Every field is attacker-controlled and the output is a permanent pin on a hoodfi.eth subdomain |
builder/lib/templates/fonts.ts | Subsetted woff2 faces as base64, so a published site fetches nothing at view time. Regenerate with scripts/build-fonts.sh; the output is committed |
builder/lib/publish.ts | The client half of the publish protocol. sitePublishMessage must match gateway/src/handlers/postSite.ts byte for byte |
builder/components/PublishPanel.tsx | Five named steps. A single spinner over two wallet prompts and a chain read is how somebody pays twice |
builder/components/ConnectionGuard.tsx | Detects a rehydrated WalletConnect connector stub by the method it is missing (getChainId); storage looks healthy in that state and is a dead end |
builder/shared/ | Generated copy of frontend/shared/ — Turbopack will not resolve a symlink out of the project root, so the codec is copied on prebuild |
mcp/ | The MCP server at hoodfi-mcp.com. Its own Cloudflare Worker (Hono + viem), streamable-HTTP transport, twelve tools |
mcp/src/tools.ts | The tool list and dispatch. Every write tool returns unsigned steps — the server holds no keys |
mcp/src/agent-tools.ts | The six tools added for wallet-signed agents (list names, text records, subnames, transfer, mainnet primary name, send) and the relay's allowlist |
mcp/src/complete.ts | Fills each step into a complete transaction — pending nonce (+1 per earlier step on the same chain), gas +25%, fees — so a signing-only wallet signs it as given |
mcp/src/card.ts | The HoodFi card rendered in MCP Apps clients. Every value is written with textContent: names and records are other people's text |
mcp/test/paybox.fork.mjs | The PayBox path end to end on an anvil fork of Robinhood Chain: register, records, subnames, transfer, relay refusals |
mcp/test/shortnames.fork.mjs | An agent mints a 1–3 character name end to end on a fork; blocklisted short labels are still refused |
mcp/src/coins.ts | Bitcoin, Solana and XRP Ledger address parsing into each chain's own encoding, and the honest per-chain account of what a check proves |
DEPLOY.md | The v2 deploy runbook, in the order it must be run; §8 is the post-ENSv2 Safe routine |
SECURITY.md | How to report a vulnerability |
docs/make-banner.mjs | Renders the README banner in the OG cards' own system. Lives outside frontend/public/ so it isn't pinned to IPFS on every deploy |
hoodfi.eth