A name registry
First come, first served — and the transfer rules that decide whether a name is really yours.
A registry maps a human-readable name to an owner. Every naming system on every chain is a variation on three operations:
- claim a name nobody holds
- transfer a name you hold
- release a name you no longer want, returning it to the pool
The interesting design questions are all about the edges. Can a name be re-claimed after release? Can you transfer to yourself? Is Alice the same name as alice?
Normalise before you store, or you have sold the same name twice. If Alice and alice are different keys, two people each believe they own "alice" and both are right according to the contract. Every dispute after that is unresolvable.
Pick a rule — lowercase ASCII only, say — enforce it in claim, and reject anything that does not conform rather than silently mangling it. A rejected claim is a clear error; a silently normalised one surprises someone later.
contracts/examples/registry is deliberately minimal — set, get, has, remove keyed by address. The ownership and normalisation rules below are what you add when the thing being registered is *scarce* rather than personal.
Your turn
Implement claim, owner_of, transfer and release. Names must be non-empty lowercase ASCII (reject anything else with b"bad_name"). A claimed name cannot be re-claimed; only its owner may transfer or release it; a released name becomes claimable again.
Hints
!= b"".One way to do it
from stdlib import abi, events, storage
def _uget(key: bytes) -> int:
raw = storage.get(key, b"")
return 0 if raw == b"" else int.from_bytes(raw, "big")
def _uset(key: bytes, value: int) -> None:
v = int(value)
abi.require(v >= 0, b"negative")
if v == 0:
storage.delete(key)
return
storage.set(key, v.to_bytes(max(1, (v.bit_length() + 7) // 8), "big"))
def _flag(key: bytes) -> bool:
return storage.get(key, b"") == b"\x01"
def _set_flag(key: bytes) -> None:
storage.set(key, b"\x01")
def _k_name(name: bytes) -> bytes:
return b"reg:name:" + name
def _valid(name: bytes) -> bool:
if len(name) == 0 or len(name) > 32:
return False
for ch in name:
ok = (97 <= ch <= 122) or (48 <= ch <= 57) or ch == 45
if not ok:
return False
return True
def owner_of(name: bytes) -> bytes:
return storage.get(_k_name(bytes(name)), b"")
def claim(name: bytes) -> bytes:
n = bytes(name)
# Reject rather than normalise: a silently rewritten name surprises someone.
abi.require(_valid(n), b"bad_name")
abi.require(storage.get(_k_name(n), b"") == b"", b"taken")
storage.set(_k_name(n), abi.caller())
events.emit(b"Claimed", {"name": n, "owner": abi.caller()})
return storage.get(_k_name(n), b"")
def transfer(name: bytes, to: bytes) -> bytes:
n = bytes(name)
dest = bytes(to)
abi.require(storage.get(_k_name(n), b"") == abi.caller(), b"not_owner")
abi.require(len(dest) > 0, b"bad_address")
storage.set(_k_name(n), dest)
events.emit(b"Transferred", {"name": n, "to": dest})
return storage.get(_k_name(n), b"")
def release(name: bytes) -> bool:
n = bytes(name)
abi.require(storage.get(_k_name(n), b"") == abi.caller(), b"not_owner")
storage.delete(_k_name(n))
events.emit(b"Released", {"name": n})
return True
Claim your 10 ANM
Finish this lesson and claim 10 ANM, once per address. Paid from the Animica treasury in batches — allow a few minutes.
Stuck? Ask
Answered by Animica's own free inference network. It is donated GPU capacity, so give it 20-30 seconds.