> For the complete documentation index, see [llms.txt](https://hyperliquid-names.gitbook.io/hyperliquid-names/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hyperliquid-names.gitbook.io/hyperliquid-names/dapp-integration/elysium-observatory.md).

# Elysium Observatory

Read .hl names natively on Elysium

The Elysium Observatory lets native applications on Elysium read a local representation of `.hl` ownership and expiry. Builders can use it for eligibility checks, identity-aware experiences, and other integrations without making a HyperEVM RPC request. The registry reflects the **latest delivered snapshot** from HyperEVM; it is not a live cross-chain read.

### Networks and deployments

|                            | Elysium Testnet                              | Elysium Mainnet                              |
| -------------------------- | -------------------------------------------- | -------------------------------------------- |
| Chain ID                   | `99801`                                      | `99901`                                      |
| RPC                        | `https://testnet-rpc.elysium.kinetiq.xyz`    | `https://rpc.elysium.kinetiq.xyz`            |
| `NameShadowRegistry` proxy | `0x0D394C6681CD1aF95ed1Bd88c8A0aEC5D8A199A6` | `0xAEb09D740Ea452E6476E073c250a51E3A90dC238` |
| `NameShadowNFT` proxy      | `0x72d73f1A116eB6F1158d1B2C166AE530c51d0cf1` | `0x1bBf4012475D8ae999F94881fE9bd20d8C34Dc05` |

### How it works

A keeper watches the HyperEVM for changes and submits a synchronization; a cross-chain message delivers the resulting ownership and expiry snapshot to Elysium. Delivery is asynchronous and requires a successful sync. A name that has never been synced has no representation on Elysium. A transfer, renewal, or other source change can remain invisible until a newer snapshot arrives. The contracts are upgradeable and the registry trusts its configured parent gateway.

* `activeOwnerOf(tokenId)` returns the last delivered owner if the name exists and is unexpired at the Elysium block's timestamp; otherwise it returns the zero address. Zero can mean unsynced, absent, or expired.
* `canonicalOwnerOf(tokenId)` returns the last delivered owner even if the registration has expired.
* `isMaterialized(tokenId)` distinguishes an unsynced name from one for which any snapshot, including a deletion, was delivered. `getShadow(tokenId)` includes the owner, expiry, source block, source timestamp, and sync sequence.
* `shadowAge(tokenId)` reports seconds since the source timestamp, or the maximum `uint256` for an unsynced name. A recent snapshot does **not** prove that no more recent HyperEVM transfer occurred.
* `NameShadowNFT` is a non-transferable ERC-721-compatible view. Its `ownerOf` reverts for inactive or unmaterialized names; transfers and approvals revert. Its transfer events represent changes to the delivered shadow, and expiry alone emits no transfer event.

HL Names uses the ERC-137 namehash as the NFT token ID ([HL Names contract documentation](https://hyperliquid-names.gitbook.io/hyperliquid-names/smart-contracts/core-smart-contracts)). The examples use simple lowercase second-level names such as `alice.hl`; subdomains are not shadow NFTs. For other input, follow HL Names' name validation and normalization rules before hashing. The [viem namehash documentation](https://viem.sh/docs/ens/utilities/namehash) describes its hashing utility.

For a large list, bound concurrency and retry RPC failures at the same block; the provider must retain state for that block. Record the block number and hash, account for chain reorganization/finality, and decide how to handle unsynced or stale names before producing a whitelist. For a high-value airdrop, reconcile against HyperEVM ownership and expiry at a defined source snapshot: an Elysium block number alone cannot establish that the shadow includes every source change. Authenticate the claimant separately and define whether eligibility is per name or per wallet.

### Setup

Install `viem` in your TypeScript project. The following setup uses Elysium Mainnet. Use the Testnet values from the table when targeting Testnet.

```tsx
import { createPublicClient, http, parseAbi, zeroAddress } from 'viem';
import { namehash } from 'viem/ens';

const registry = '0xaeb09d740ea452e6476e073c250a51e3a90dc238' as const;
const client = createPublicClient({
  transport: http('<https://rpc.elysium.kinetiq.xyz>'),
});
const registryAbi = parseAbi([
  'function activeOwnerOf(uint256 tokenId) view returns (address)',
]);

// Example input is already lowercase and is a second-level .hl name.
const tokenIdFor = (name: string) => BigInt(namehash(name));

if (await client.getChainId() !== 99901) throw new Error('Wrong network');
```

The ABI above is the `activeOwnerOf` read from the project contract interface. These are `eth_call` reads: they do not require a wallet or spend gas. See [viem's readContract reference](https://viem.sh/docs/contract/readContract).

### One-off whitelist check

Append this to the setup. Replace the illustrative address and name with the candidate's wallet and name. This checks that particular name, not whether the wallet owns any `.hl` name.

```tsx
const candidate = '0x1111111111111111111111111111111111111111';
const blockNumber = await client.getBlockNumber();
const owner = await client.readContract({
  address: registry,
  abi: registryAbi,
  functionName: 'activeOwnerOf',
  args: [tokenIdFor('alice.hl')],
  blockNumber,
});
const eligible = owner !== zeroAddress && owner.toLowerCase() === candidate.toLowerCase();
console.log({ blockNumber, owner, eligible });
```

### Batch whitelist check

Append this to the setup instead of the one-off example. Provide the actual candidate name/address pairs. These are parallel individual RPC reads, not an onchain multicall; all reads use the same Elysium block. A rejected read fails the batch rather than silently marking a candidate ineligible.

```tsx
import type { Address } from 'viem';

const candidates: { name: string; wallet: Address }[] = [
  { name: 'alice.hl', wallet: '0x1111111111111111111111111111111111111111' },
  { name: 'bob.hl', wallet: '0x2222222222222222222222222222222222222222' },
];
const blockNumber = await client.getBlockNumber();
const results = await Promise.all(candidates.map(async ({ name, wallet }) => {
  const owner = await client.readContract({
    address: registry,
    abi: registryAbi,
    functionName: 'activeOwnerOf',
    args: [tokenIdFor(name)],
    blockNumber,
  });
  return {
    name,
    wallet,
    owner,
    eligible: owner !== zeroAddress && owner.toLowerCase() === wallet.toLowerCase(),
  };
}));
console.log({ blockNumber, results });
```

### Future direction

V2 will read HyperEVM state directly if a future Elysium upgrade enables a system contract to do so. This is conditional on that capability becoming available.
