RNS

Docs

RNS follows ENS's interfaces, so tooling that already speaks ENS speaks this with a different registry address. Everything below is live on Robinhood Chain 4663.

Start here

There is one read entry point. UniversalResolver walks the node chain, handles the offchain revert, and does the reverse forward check for you. Call it rather than reading the registry directly, and you get the safe behaviour by default.

import { createPublicClient, http } from "viem";

const client = createPublicClient({
  transport: http("https://rpc.mainnet.chain.robinhood.com"),
});

const UNIVERSAL = "0xc01ee200ca8e9de3c5f835733fa92b421fea30c0";

Resolve a name

profile(label, textKeys) returns the owner, the address record, the expiry, whether it is in grace, the resolver, and any text records you asked for, in one round trip.

const [owner, addr, expires, inGrace, resolver, texts] =
  await client.readContract({
    address: UNIVERSAL,
    abi: universalAbi,
    functionName: "profile",
    args: ["vlad", ["avatar", "url", "com.twitter"]],
  });

A name past its expiry returns the zero address on purpose. Resolution stops the moment a name expires, so nobody keeps sending funds to a name its old owner no longer controls.

Reverse resolution

UniversalResolver.reverse returns the name for an address together with a verified flag. It forward resolves the answer and confirms it points back at the same address, so the safe behaviour is the default and you get it in one call.

const [name, verified] = await client.readContract({
  address: UNIVERSAL,
  abi: universalAbi,
  functionName: "reverse",
  args: ["0x8f3a...b24d"],
});

// show the address, not the name, when verified is false
const display = verified ? name : shortAddress(addr);

Ticker namespace

Every listed equity symbol is held by the registry, so tsla.hood cannot be registered by anyone. Once an issuer claims one, its address record is the canonical tokenized contract and its rns.ticker text record carries the symbol and the attestation.

bytes32 node = namehash("tsla.hood");
address token = IResolver(registry.resolver(node)).addr(node);

Read it rather than pasting an address into a config file. An issuer can rotate a contract without every app shipping a release.

Register from code

Two transactions, on purpose. The first publishes a hash so nobody watching the sequencer can register your name ahead of you. Minimum wait is 60 seconds, maximum is 24 hours.

const secret = crypto.getRandomValues(new Uint8Array(32));

const commitment = await client.readContract({
  address: CONTROLLER, abi, functionName: "makeCommitment",
  args: [label, owner, duration, secret, RESOLVER, [], true, ZERO],
});
await wallet.writeContract({ ...c, functionName: "commit", args: [commitment] });

// wait 60 seconds, then

const [base, premium] = await client.readContract({
  address: CONTROLLER, abi, functionName: "rentPrice", args: [label, duration],
});
await wallet.writeContract({
  address: CONTROLLER, abi, functionName: "register",
  args: [label, owner, duration, secret, RESOLVER, [], true, ZERO],
  value: base + premium,
});

Overpaying is safe: the excess is refunded inside the same call, after every state change. Renewal needs no commitment and anyone may renew anyone's name.

Addresses

Notes

A full breakdown of every contract and what it can and cannot do to a name lives at contracts.

Homographs. Registration is by labelhash, so the contract works on hashes rather than strings, exactly as ENS does. Normalization lives in the client. NameChecker.isSimpleAscii is there to flag anything outside plain lowercase ASCII so your interface can surface it.

Price oracle. No Chainlink or Pyth ETH/USD feed is live on this chain yet, so pricing falls back to an owner set rate behind a 48 hour timelock. isTrustedRate() returns false while that is the case, and the site says so on the page.

Indexing. The my-names page reads logs directly today, which keeps the read path dependency free. A Ponder indexer is the next addition once volume makes it worth one.