A simple namespace on Ethereum named after the smallest unit of ether.
Contract: 0x0000000000696760E15f265e828DB644A0c242EB (Ethereum Mainnet)
Subdomain Registrar: 0x53745292f0d30d68204a63002C17bDa16C772bf7
Gateway: wei.domains - resolves name.wei.domains to IPFS content
Dapp: wei.domains (hosted via IPFS)
WNS provides .wei names as NFTs (ERC-721). Names can:
- Resolve to an Ethereum address (receive payments)
- Host a website via IPFS contenthash
- Have unlimited free subdomains
- Store multi-coin addresses and text records (ENS-compatible resolver)
- Display as your wallet's identity (reverse resolution)
The contract is a single, non-upgradeable Solidity file (NameNFT.sol) that combines ERC-721 ownership, registration logic, and resolver functionality.
- Solidity:
^0.8.30 - License: MIT
NameNFT
├── ERC721 (solady) — gas-optimized NFT
├── Ownable (solady) — admin access control
└── ReentrancyGuard (soledge) — reentrancy protection
Token IDs are computed as uint256(namehash), following the ENS namehash algorithm (EIP-137).
namehash("") = bytes32(0)
namehash("wei") = keccak256(abi.encodePacked(namehash(""), keccak256("wei")))
namehash("alice.wei") = keccak256(abi.encodePacked(namehash("wei"), keccak256("alice")))
namehash("sub.alice.wei") = keccak256(abi.encodePacked(namehash("alice.wei"), keccak256("sub")))
The precomputed constant:
WEI_NODE = namehash("wei")
= keccak256(abi.encodePacked(bytes32(0), keccak256("wei")))
= 0xa82820059d5df798546bcc2985157a77c3eef25eba9ba01899927333efacbd6f
JavaScript example:
import { ethers } from 'ethers';
const WEI_NODE = '0xa82820059d5df798546bcc2985157a77c3eef25eba9ba01899927333efacbd6f';
function computeTokenId(label) {
const labelHash = ethers.keccak256(ethers.toUtf8Bytes(label));
return BigInt(ethers.keccak256(ethers.concat([WEI_NODE, labelHash])));
}
function computeSubdomainId(label, parentId) {
const parentNode = ethers.zeroPadValue(ethers.toBeHex(parentId), 32);
const labelHash = ethers.keccak256(ethers.toUtf8Bytes(label));
return BigInt(ethers.keccak256(ethers.concat([parentNode, labelHash])));
}| Constant | Value | Description |
|---|---|---|
WEI_NODE |
0xa828...bd6f |
Namehash of "wei" TLD |
MAX_LABEL_LENGTH |
255 bytes | Maximum label byte length |
MIN_LABEL_LENGTH |
1 byte | Minimum label byte length |
MIN_COMMITMENT_AGE |
60 seconds | Minimum wait before reveal |
MAX_COMMITMENT_AGE |
86400 seconds (24h) | Commitment expiration |
REGISTRATION_PERIOD |
365 days | Duration of one registration |
GRACE_PERIOD |
90 days | Post-expiry renewal window |
MAX_SUBDOMAIN_DEPTH |
10 | Maximum nesting of subdomains |
COIN_TYPE_ETH |
60 | SLIP-44 coin type for ETH |
MAX_PREMIUM_CAP |
10,000 ETH | Admin cap on premium setting |
MAX_DECAY_PERIOD |
3,650 days | Admin cap on decay period setting |
DEFAULT_FEE |
0.001 ETH | Initial default registration fee |
constructor() payable {
_initializeOwner(tx.origin);
defaultFee = DEFAULT_FEE; // 0.001 ether
maxPremium = 100 ether;
premiumDecayPeriod = 21 days;
}Note: Owner is set to tx.origin, not msg.sender. This is intentional for deployment via CREATE2 factory patterns where msg.sender would be the factory contract. The owner controls fee settings and ETH withdrawal.
A two-step commit-reveal pattern prevents frontrunning:
- Commit — Submit
keccak256(abi.encode(normalizedLabel, owner, secret))on-chain. The commitment uses the normalized label bytes (ASCII lowercased), not the raw input. - Wait — At least 60 seconds (
MIN_COMMITMENT_AGE). - Reveal — Submit the label, secret, and payment. The commitment must be no older than 24 hours (
MAX_COMMITMENT_AGE).
The commitment is deleted after a successful reveal. An expired commitment (>24h) can be overwritten by a new commit().
Off-chain commitment computation:
// IMPORTANT: normalize the label the same way the contract does (lowercase ASCII)
const normalized = label.toLowerCase(); // for ASCII-only labels
const commitment = ethers.keccak256(
ethers.AbiCoder.defaultAbiCoder().encode(
['bytes', 'address', 'bytes32'],
[ethers.toUtf8Bytes(normalized), ownerAddress, secret]
)
);- Registration lasts 365 days from the time of reveal.
- The name is active while
block.timestamp <= expiresAt. - While active: transfers, resolver writes, and resolution all work.
- After
expiresAt, the name enters a 90-day grace period. - During grace: the name is not active — transfers are blocked, resolver reads return empty, resolver writes revert.
- During grace: renewal is allowed and extends from the original
expiresAt(not from current time). - Anyone can call
renew()for any name (not restricted to the owner).
- After
expiresAt + GRACE_PERIOD, the name is fully expired. - It can be re-registered by anyone through a new commit-reveal cycle.
- Re-registration increments the
epoch, invalidating all existing subdomains. - Re-registration increments
recordVersion, clearing all resolver data.
Immediately after a name fully expires (grace period ends), a premium is charged on top of the base fee. The premium starts at maxPremium (default: 100 ETH) and decays linearly to 0 over premiumDecayPeriod (default: 21 days).
premium = maxPremium * (premiumDecayPeriod - elapsed) / premiumDecayPeriod
Where elapsed is seconds since expiresAt + GRACE_PERIOD. After the decay period, premium is 0.
The contract deploys with defaultFee = 0.001 ETH for all label lengths. No length-specific fees are set at deployment.
The owner can set per-length fees via setLengthFees(). When a length-specific fee is set, it overrides the default. The owner can also change the default fee.
getFee(length):
if lengthFeeSet[length] → return lengthFees[length]
else → return defaultFee
The fee is determined by bytes(label).length (UTF-8 byte length, not character count). For example, an emoji label may be 4 bytes despite being 1 "character".
Renewal costs the same as the base registration fee for that label length. No premium is charged on renewal.
- Parent owner calls
registerSubdomain(label, parentId)orregisterSubdomainFor(label, parentId, to). - Subdomains are free (no fee).
- Subdomains have no independent expiry — they are active as long as the parent chain is active.
- Maximum nesting depth: 10 levels below the top-level name.
Each name record has an epoch counter. When a subdomain is created, it stores parentEpoch — the parent's epoch at creation time. A subdomain is considered stale (inactive) if its parentEpoch does not match the parent's current epoch.
This happens when:
- The parent name expires and is re-registered (epoch increments).
- The parent owner reclaims the subdomain via
registerSubdomain()(burns the old token, mints a new one with incremented epoch).
Stale subdomains:
- Return empty strings from resolver reads.
- Show
[Invalid]intokenURI. - Cannot be transferred (blocked by
_isActivecheck in_beforeTokenTransfer).
The parent owner can always call registerSubdomain() with an existing subdomain label. This burns the old token (clearing the previous owner's holding), increments the epoch, and mints a fresh token to the parent owner. The previous owner's primaryName is cleared if it pointed to the reclaimed token.
Note: isAvailable() returns false for active subdomains, even though the parent owner can overwrite them. Parent owners should call registerSubdomain() directly — it will succeed for reclaim regardless of isAvailable() result.
Each token has a recordVersion counter. All resolver data (address, contenthash, multi-coin addresses, text records) is keyed by (tokenId, recordVersion). When a name is re-registered after expiry, recordVersion is incremented, effectively clearing all previous resolver data without paying gas to delete storage.
resolve(tokenId):
if name is not active → return address(0)
if explicit address is set → return that address
else → return ownerOf(tokenId)
The fallback to ownerOf means a freshly registered name resolves to its owner by default.
Users set a primary name via setPrimaryName(tokenId). The caller must be the token owner or the address that the token resolves to.
reverseResolve(addr):
if primaryName[addr] is 0, or name is not active, or resolve(tokenId) != addr → return ""
else → return "label.wei" (or "sub.label.wei" etc.)
Setting primaryName to tokenId = 0 clears the primary name.
setAddrForCoin(tokenId, coinType, addr) stores addresses for any SLIP-44 coin type. For coin type 60 (ETH), the addr() function first checks the explicit coin address, then falls back to resolve().
Standard key-value text records via setText / text. Common keys: avatar, url, description, com.twitter, com.github, etc.
setContenthash / contenthash for IPFS/Swarm/etc. content addressing. Used by the gateway (wei.domains) to serve websites.
The contract enforces:
- Label byte length: 1–255 bytes
- Valid UTF-8 encoding (rejects invalid sequences, overlong encodings, surrogates, codepoints above U+10FFFF)
- No control characters (0x00–0x1F), space (0x20), dot (0x2E), or DEL (0x7F)
- No leading or trailing hyphens
- ASCII A–Z is lowercased to a–z
The contract does not perform Unicode normalization (NFC/NFD), confusable detection, or script restriction. These are delegated to the client layer.
For proper Unicode safety, callers SHOULD pre-normalize labels using ENSIP-15 via the @adraffy/ens-normalize library before calling the contract.
import { ens_normalize } from '@adraffy/ens-normalize';
function normalizeLabel(label) {
try {
const normalized = ens_normalize(label);
if (normalized.includes('.')) return null; // No dots in labels
return normalized;
} catch (e) {
return null; // Invalid (confusables, invisible chars, etc.)
}
}- Future-proof — Normalization standards evolve (ENSIP-15 replaced ENSIP-1, Unicode updates yearly). On-chain rules would be frozen or require expensive upgrades.
- Ecosystem alignment — ENS, DNS, and other naming systems handle normalization at the application layer.
- International support — Overly restrictive on-chain validation could block legitimate international names.
- Gas efficiency — Full Unicode normalization tables are impractical on-chain.
normalize(label)— On-chain validation + ASCII lowercasing. Reverts on invalid input.isAsciiLabel(label)— Returnstrueif label is pure ASCII. If true, on-chain normalization is sufficient.computeNamehash(fullName)— Computes namehash for a full name (e.g.,"sub.name"or"sub.name.wei"). Lowercases ASCII but does not validate label characters (no UTF-8 check, no hyphen rules). Does reject empty labels (leading/trailing/consecutive dots). Strips.weisuffix if present.computeId(fullName)— Returnsuint256(computeNamehash(fullName)).
| Function | Access |
|---|---|
commit |
Anyone |
reveal |
Anyone (must match commitment owner) |
registerSubdomain / registerSubdomainFor |
Parent token owner only |
renew |
Anyone (for any name) |
setAddr, setContenthash, setAddrForCoin, setText |
Token owner only |
setPrimaryName |
Token owner or resolved address |
setDefaultFee, setLengthFees, clearLengthFee, setPremiumSettings |
Contract owner only |
withdraw |
Contract owner only |
The _beforeTokenTransfer hook blocks transfers of inactive tokens. A token is inactive when:
- Top-level name:
block.timestamp > expiresAt(after expiry, including during grace period) - Subdomain: parent epoch mismatch, or parent chain is inactive
Mint (from == address(0)) and burn (to == address(0)) are always allowed regardless of active status.
The following functions have the nonReentrant modifier:
reveal— uses_safeMintwhich callsonERC721Receivedon contract recipientsregisterSubdomain/registerSubdomainFor— also uses_safeMintrenew— sends ETH refundwithdraw— sends ETH
reveal and renew refund excess ETH to msg.sender via SafeTransferLib.safeTransferETH. If the caller cannot receive ETH (e.g., a contract without a receive function), the transaction reverts.
The commit-reveal scheme requires a 60-second minimum delay between commit and reveal, preventing miners/searchers from observing a reveal transaction and frontrunning it.
When a name is re-registered or a subdomain is reclaimed, if the previous owner's primaryName pointed to that token, it is deleted.
// Registration helpers
function makeCommitment(string label, address owner, bytes32 secret) pure returns (bytes32)
function isAvailable(string label, uint256 parentId) view returns (bool)
function getFee(uint256 length) view returns (uint256)
function getPremium(uint256 tokenId) view returns (uint256)
function normalize(string label) pure returns (string)
function isAsciiLabel(string label) pure returns (bool)
// Lookup
function computeId(string fullName) pure returns (uint256)
function computeNamehash(string fullName) pure returns (bytes32)
function getFullName(uint256 tokenId) view returns (string)
// Expiration
function expiresAt(uint256 tokenId) view returns (uint256)
function isExpired(uint256 tokenId) view returns (bool) // true after expiresAt + GRACE_PERIOD
function inGracePeriod(uint256 tokenId) view returns (bool) // true between expiresAt and expiresAt + GRACE_PERIOD
// Resolution (uint256 tokenId overloads)
function resolve(uint256 tokenId) view returns (address)
function reverseResolve(address addr) view returns (string)
function contenthash(uint256 tokenId) view returns (bytes)
function text(uint256 tokenId, string key) view returns (string)
function addr(uint256 tokenId, uint256 coinType) view returns (bytes)
// Resolution (bytes32 node overloads — ENS-compatible)
function addr(bytes32 node) view returns (address)
function addr(bytes32 node, uint256 coinType) view returns (bytes)
function text(bytes32 node, string key) view returns (string)
function contenthash(bytes32 node) view returns (bytes)
// ERC-165
function supportsInterface(bytes4 interfaceId) view returns (bool)
// Supported: ERC-721, ERC-165, addr(bytes32) [0x3b3b57de], addr(bytes32,uint256) [0xf1cb7e06],
// text [0x59d1d43c], contenthash [0xbc1c58d1]
// ERC-721 read functions
function name() pure returns (string) // "Wei Name Service"
function symbol() pure returns (string) // "WEI"
function tokenURI(uint256 tokenId) view returns (string)
function ownerOf(uint256 tokenId) view returns (address)
function balanceOf(address owner) view returns (uint256)
function getApproved(uint256 tokenId) view returns (address)
function isApprovedForAll(address owner, address operator) view returns (bool)
// Storage accessors (auto-generated)
function records(uint256 tokenId) view returns (string label, uint256 parent, uint64 expiresAt, uint64 epoch, uint64 parentEpoch)
function recordVersion(uint256 tokenId) view returns (uint256)
function commitments(bytes32) view returns (uint256)
function primaryName(address) view returns (uint256)
function defaultFee() view returns (uint256)
function maxPremium() view returns (uint256)
function premiumDecayPeriod() view returns (uint256)
function lengthFees(uint256) view returns (uint256)
function lengthFeeSet(uint256) view returns (bool)
function WEI_NODE() view returns (bytes32)// Commit-reveal registration
function commit(bytes32 commitment)
function reveal(string label, bytes32 secret) payable returns (uint256 tokenId)
// Subdomains
function registerSubdomain(string label, uint256 parentId) returns (uint256 tokenId)
function registerSubdomainFor(string label, uint256 parentId, address to) returns (uint256 tokenId)
// Renewal
function renew(uint256 tokenId) payable
// Resolver writes (token owner only)
function setAddr(uint256 tokenId, address addr)
function setContenthash(uint256 tokenId, bytes hash)
function setAddrForCoin(uint256 tokenId, uint256 coinType, bytes addr)
function setText(uint256 tokenId, string key, string value)
// Reverse resolution
function setPrimaryName(uint256 tokenId)
// Admin (contract owner only)
function setDefaultFee(uint256 fee)
function setLengthFees(uint256[] lengths, uint256[] fees)
function clearLengthFee(uint256 length)
function setPremiumSettings(uint256 maxPremium, uint256 decayPeriod)
function withdraw()
// Standard ERC-721
function transferFrom(address from, address to, uint256 tokenId)
function safeTransferFrom(address from, address to, uint256 tokenId)
function safeTransferFrom(address from, address to, uint256 tokenId, bytes data)
function approve(address to, uint256 tokenId)
function setApprovalForAll(address operator, bool approved)// Registration
event NameRegistered(uint256 indexed tokenId, string label, address indexed owner, uint256 expiresAt)
event SubdomainRegistered(uint256 indexed tokenId, uint256 indexed parentId, string label)
event NameRenewed(uint256 indexed tokenId, uint256 newExpiresAt)
event PrimaryNameSet(address indexed addr, uint256 indexed tokenId)
event Committed(bytes32 indexed commitment, address indexed committer)
// ENS-compatible resolver events
event AddrChanged(bytes32 indexed node, address addr)
event ContenthashChanged(bytes32 indexed node, bytes contenthash)
event AddressChanged(bytes32 indexed node, uint256 coinType, bytes addr)
event TextChanged(bytes32 indexed node, string indexed key, string value)
// Admin
event DefaultFeeChanged(uint256 fee)
event LengthFeeChanged(uint256 indexed length, uint256 fee)
event LengthFeeCleared(uint256 indexed length)
event PremiumSettingsChanged(uint256 maxPremium, uint256 decayPeriod)| Error | Condition |
|---|---|
Expired() |
Operation requires active name but name is expired/inactive |
TooDeep() |
Subdomain nesting exceeds MAX_SUBDOMAIN_DEPTH (10) |
EmptyLabel() |
Label is empty or name contains consecutive dots |
InvalidName() |
Label contains invalid characters or fails validation |
InvalidLength() |
Label byte length outside 1–255 range |
LengthMismatch() |
setLengthFees called with mismatched array lengths |
NotParentOwner() |
Subdomain registration attempted by non-parent-owner |
PremiumTooHigh() |
Admin tried to set premium > 10,000 ETH |
InsufficientFee() |
msg.value less than required fee + premium |
AlreadyCommitted() |
Commitment already exists and hasn't expired |
CommitmentTooNew() |
Reveal attempted before MIN_COMMITMENT_AGE (60s) |
CommitmentTooOld() |
Reveal attempted after MAX_COMMITMENT_AGE (24h) |
AlreadyRegistered() |
Top-level name still active or in grace period |
CommitmentNotFound() |
No matching commitment on-chain |
DecayPeriodTooLong() |
Admin tried to set decay period > 3,650 days |
The contract also uses inherited errors:
Unauthorized()(from Ownable) — used insetAddr,setContenthash,setAddrForCoin,setText,setPrimaryName, andrenew(subdomains cannot be renewed)TokenDoesNotExist()(from ERC721) — used intokenURIandrenewwhen the token has no record
// Fee configuration
uint256 public defaultFee;
uint256 public maxPremium;
uint256 public premiumDecayPeriod;
mapping(uint256 => uint256) public lengthFees;
mapping(uint256 => bool) public lengthFeeSet;
// Name records
mapping(uint256 => NameRecord) public records; // tokenId → record
mapping(uint256 => uint256) public recordVersion; // tokenId → version (increments on re-registration)
// Commitments
mapping(bytes32 => uint256) public commitments; // commitment hash → timestamp
// Reverse resolution
mapping(address => uint256) public primaryName; // address → tokenId
// Versioned resolver data (keyed by tokenId, recordVersion)
mapping(uint256 => mapping(uint256 => address)) internal _resolvedAddress;
mapping(uint256 => mapping(uint256 => bytes)) internal _contenthash;
mapping(uint256 => mapping(uint256 => mapping(uint256 => bytes))) internal _coinAddr;
mapping(uint256 => mapping(uint256 => mapping(string => string))) internal _text;
struct NameRecord {
string label; // Normalized label (ASCII lowercased)
uint256 parent; // Parent token ID (0 for top-level)
uint64 expiresAt; // Expiry timestamp (0 for subdomains)
uint64 epoch; // Increments on re-registration
uint64 parentEpoch; // Parent's epoch at time of subdomain creation
}To host a website at name.wei.domains:
- Pin your site to IPFS (Pinata, web3.storage, etc.)
- Get the CID (
Qm...orbaf...) - Call
setContenthash(tokenId, encodedHash)
Encoding:
// Contenthash = 0xe3 (IPFS namespace) + CID bytes
function encodeContenthash(cid) {
let cidBytes;
if (cid.startsWith('Qm')) {
// CIDv0 -> CIDv1
cidBytes = new Uint8Array([0x01, 0x70, ...base58Decode(cid)]);
} else if (cid.startsWith('baf')) {
// CIDv1 base32
cidBytes = base32Decode(cid.slice(1));
}
return ethers.concat(['0xe3', cidBytes]);
}The Cloudflare Worker at wei.domains:
- Extracts name from subdomain (
name.wei.domains) - Queries contract for contenthash
- Decodes CID and fetches from IPFS
- Serves content with caching
Root domain (wei.domains) resolves to wns.wei (the official dapp).
The official dapp includes a "verify name" helper:
- Enter a token ID (from OpenSea URL, etc.)
- See the actual on-chain name and byte representation
- Check ENSIP-15 normalization status
- Compare against an expected name
Useful for secondary market purchases or inspecting unfamiliar names.
For efficient batching, use Multicall3:
const MULTICALL3 = '0xcA11bde05977b3631167028862bE2a173976CA11';
const results = await multicall.aggregate3([
{ target: WNS, callData: encodeFunctionData('isAvailable', [name, 0]) },
{ target: WNS, callData: encodeFunctionData('getFee', [byteLength]) },
{ target: WNS, callData: encodeFunctionData('getPremium', [tokenId]) }
]);- Normalize input with ENSIP-15 before registration (same as ENS)
- Use the verification tool or compute expected token IDs when buying on secondary markets
- Display normalization warnings for names that don't pass ENSIP-15
- Link to the official dapp (
wei.domains/#name) for name lookups - Check
isActivestate before displaying resolver data — expired names return empty from all resolver reads - Handle refund failures — if your contract calls
revealorrenew, ensure it can receive ETH refunds
WeiDAO.sol is a conviction-voting DAO + treasury built entirely on WNS primitives, deployed to Ethereum mainnet. Named in homage to Wei Dai. Solady-only (accounts/Receiver, utils/Multicallable, utils/LibString, utils/SSTORE2); no Merkle tree, no off-chain indexer, no server — treasury, proposals, roles, and even the frontend are read and rendered live on-chain. It is designed to own NameNFT, so the withdrawal fees and every onlyOwner setter become governable.
| WeiDAO | 0x00000007988A79d16cf76B5dc4cF54dc3Af24936 (verified) |
| Parent name | dao.wei — owned by the DAO, reverse-resolves to it |
veto.dao.wei / exec.dao.wei |
0x006CD14F36F65eCbB29b2519cCBe63A0DC8549F2 — timelocked multisig |
alpha |
999998853923940000 — 7-day conviction half-life |
threshold |
159446457364257519435776 — ≈10% of live WNS weight sustained one half-life passes |
proposalFee |
0.002 ETH |
executionDelay |
3 days — the veto-window floor |
The DAO pulls dao.wei in and mints both role subdomains to the multisig atomically in the constructor on deploy (via a pre-approval + CreateX CREATE3 — see ops/DEPLOY.md).
You support(id, tokenId) a proposal with a name you own; its conviction accrues over time from the weight backing it and decays when support is withdrawn:
conviction' = conviction · α^Δ + supportWeight · (1 − α^Δ) / (1 − α)
A proposal executes once conviction ≥ threshold. Because conviction starts at 0 and needs sustained support, freshly-minted weight has ~no effect — the ramp itself is the flash-mint defence and the warning window. One support per name (transfer-safe). Support-only: opposition is withholding/withdrawing support, or the veto.
Calibration (7-day half-life): alpha = 999998853923940000 (= round(2^(-1/604800)·1e18)). With threshold = convictionMax(W_req)/2 (where convictionMax(w) = w·1e18/(1e18−alpha)), a proposal holding sustained weight W_req passes in exactly one half-life.
weightOf(name) = getFee(byteLength(label)) × (expiresAt − now) / 365 days
Weight tracks the two things that cost ETH in WNS: the length fee tier (short = pricier = more weight) and the paid-ahead runway (renewing boosts weight; a near-expiry name is nudged to renew). Subdomains cost no ETH → 0 weight, so they can't be spam-minted for votes. Active top-level names only.
Two roles are resolved live from names under the DAO's parent dao.wei, so holding the name is holding the role and handing it off is just an NFT transfer:
| Role name | Power |
|---|---|
veto.dao.wei |
Its holder may veto(id) any not-yet-executed proposal (negative power only). |
exec.dao.wei |
God-mode operator: veto, force-execute bypassing conviction, and call the DAO's admin setters directly. |
The exec role is meant as a launch multisig that can rescue WNS if a bug appears — e.g. force-execute NameNFT.transferOwnership(safe) — then be relinquished by transferring/burning exec.dao.wei to progressively decentralise.
Because subdomains lapse when their parent expires or is re-registered, both roles auto-lapse if dao.wei isn't maintained — a dead-man's-switch (vetoer()/executor() return address(0)). Role resolution is activity-aware, so a stale/expired role never retains power.
html() returns the DAO's entire frontend as a self-contained HTML page, rendered live from chain state — treasury, knobs, roles, and recent proposals with their conviction and descriptions — with an inlined vanilla-JS wallet bridge for connect / support / unsupport / execute / propose. Because the DAO owns dao.wei, the name resolves to the contract, so an ERC-8244 / ERC-4804 (resolveMode + request) gateway renders the dapp at dao.wei with no server. The page is a governable shell (setHtml), and the large CSS/JS blobs live in SSTORE2 data contracts to keep runtime under EIP-170.
Constructor: WeiDAO(nameNFT, alpha, threshold, proposalFee, executionDelay, roleHolder). The intended deploy uses CreateX CREATE3 for a deterministic vanity address, pre-approved for dao.wei, so the constructor pulls dao.wei in, sets the DAO's primary name, and mints veto.dao.wei / exec.dao.wei to roleHolder — all in one transaction (the role mints are mandatory once the pull succeeds, so a deploy can't launch without its backstop). Runbook, calibration, and a mainnet-fork rehearsal are in ops/: DEPLOY.md, LAUNCH.md.
The final step hands WNS to governance: NameNFT.transferOwnership(weiDAO) from the current owner. After that, WNS admin (fee schedule, withdraw, ownership) runs through a passed proposal or exec.rescue — and exec can always reclaim ownership while the role is held. The role/parent names are hardcoded namehash constants (PROPOSAL_PARENT, VETO_ROLE, EXEC_ROLE for dao.wei / veto.dao.wei / exec.dao.wei).
nft and the role/parent namehashes are immutable. alpha, threshold, proposalFee, and executionDelay (the timelock floor, capped at 30 days) are adjustable by governance (a passed proposal) or the executor. threshold in particular should rise as participation grows — re-run ops/weight_scan.py to recalibrate against the live book.
While the DAO holds dao.wei, every proposal atomically mints <id>.dao.wei to the DAO and writes its description into that name's resolver — governance becomes a browsable, resolvable WNS namespace (add a contenthash record and each proposal name can serve a page via the wei.domains gateway). A proposer must hold a WNS primary name (reverseResolve), and propose emits it, so feeds read as alice.wei proposed ….
Support-only (no explicit "against"). Weight is captured at support time along with the name's epoch and expiry; a transferred name stays valid, but once its supported runway elapses or the name is re-registered, anyone may prune it. Roles are recognised only while the DAO owns the active dao.wei, so a lapse-and-re-register can't hand them to a new parent owner. The fixed-point α^Δ is differential-tested against an independent exp/ln to ~1 ppm and analysed in ops/MATH_REVIEW.md. The exec god-mode key is fully trusted until relinquished.
AI-assisted audits performed on the codebase:
| Audit | Scope | Findings | Status |
|---|---|---|---|
| Plainshift AI | NameNFT, SubdomainRegistrar | 1 High, 1 Medium | All fixed |
| Cantina Apex | NameNFT, Dapp | 3 Medium | All patched |
| Zellic V12 | NameNFT, SubdomainRegistrar | 1 Medium, 1 Low | Both invalid |
Plainshift AI found two valid SubdomainRegistrar issues: subdomain hijacking via missing isAvailable check (High), and stale escrow controller enabling NFT theft via epoch mismatch (Medium). Both were fixed in the redeployed SubdomainRegistrar.
Cantina Apex found three valid dapp/integration issues: XSS via unescaped name in innerHTML, router commit-reveal frontrunning, and refund misdirection through router. All were patched in the dapp and zRouter. NameNFT contract was not affected. (SubdomainRegistrar not included.)
Zellic V12 reported two findings on SubdomainRegistrar, both self-invalidated: flash mode transferFrom does not trigger onERC721Received (incorrect premise), and tx.origin in constructor is intentional for CREATE2/CREATE3 deployment.
WeiDAO was hardened across several independent AI-assisted review passes (one recorded in ops/AUDIT.md) plus a fixed-point precision analysis (ops/MATH_REVIEW.md) and a mainnet-fork deploy rehearsal (test/ForkDeploySim.t.sol). The one serious finding — role seizure via a re-registered dao.wei — was fixed and regression-tested; a machine-checked pass on _pow/_accrue is still recommended before large treasury value.
- Dapp: https://wei.domains
- Contract: https://etherscan.io/address/0x0000000000696760E15f265e828DB644A0c242EB
- Subdomain Registrar: https://etherscan.io/address/0x53745292f0d30d68204a63002C17bDa16C772bf7
- OpenSea: https://opensea.io/collection/wei-name-service
- ENSIP-15: https://docs.ens.domains/ensip/15/
- ens-normalize: https://github.com/adraffy/ens-normalize.js