Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json",
"changelog": ["@changesets/changelog-github", { "repo": "ensdomains/ensjs" }],
"changelog": [
"@changesets/changelog-github",
{ "repo": "electroneum/ensjs" }
],
"commit": false,
"fixed": [],
"linked": [],
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ env:
jobs:
changelog:
name: PR or Release
if: ${{ github.repository_owner == 'ensdomains' }}
# auto-publishing is intentionally disabled on this fork — releases are
# published to npm manually (pnpm publish from packages/ensjs)
if: ${{ false }}
runs-on: ubuntu-latest
permissions:
contents: write
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ on:
jobs:
release:
name: Release
# auto-publishing is intentionally disabled on this fork — releases are
# published to npm manually (pnpm publish from packages/ensjs)
if: ${{ false }}
permissions:
id-token: write # required for npm OIDC trusted publishing
contents: write # required to push the version-bump commit
Expand All @@ -32,12 +35,12 @@ jobs:

- name: Bump version to ${{ github.event.release.tag_name }}
run: |
pnpm -F @ensdomains/ensjs ver ${{ github.event.release.tag_name }}
pnpm -F @etn-sc/ensjs ver ${{ github.event.release.tag_name }}
git add .
git commit -m "${{ github.event.release.tag_name }}"

- name: Publish
run: pnpm -F @ensdomains/ensjs publish --tag next --no-git-checks --provenance
run: pnpm -F @etn-sc/ensjs publish --tag next --no-git-checks --provenance

- name: Push changes
run: git push
12 changes: 6 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ jobs:

- run: pnpm install --frozen-lockfile

- name: Install foundry (anvil for the local test environment)
uses: foundry-rs/foundry-toolchain@v1

- name: Build ensjs
run: pnpm -F @ensdomains/ensjs build
run: pnpm -F @etn-sc/ensjs build

- name: Run tests
run: pnpm -F @ensdomains/ensjs tenv start --extra-time 11368000 --verbosity 1

- name: Publish to pkg.pr.new
run: pnpm dlx pkg-pr-new publish './packages/ensjs'
run: pnpm -F @etn-sc/ensjs tenv start --extra-time 11368000

lint:
name: Lint
Expand All @@ -46,4 +46,4 @@ jobs:
run: pnpm lint

- name: Type check
run: pnpm -F @ensdomains/ensjs build
run: pnpm -F @etn-sc/ensjs build
187 changes: 45 additions & 142 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,171 +4,74 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

ENSjs is the ultimate ENS JavaScript library for interacting with the Ethereum Name Service. It's built on top of viem and provides comprehensive functionality for ENS operations.
ENSjs is the ENS (Ethereum Name Service) JavaScript library, built on top of viem. This repo is the `electroneum/ensjs` fork of `ensdomains/ensjs`, adapted for **Electroneum ENS**: the registrar TLD is **`.etn`** (not `.eth`) and the only supported chain is Electroneum testnet (5201420, `electroneumTestnet` in viem; mainnet 52014 gets added once its contracts are deployed). The companion contracts repo is `../ens-contracts` (fork of ensdomains/ens-contracts, ABI-identical to upstream — only TLD constants differ). It's a pnpm workspace monorepo (requires Node >= 22, pnpm 11):

## Development Commands
- `packages/ensjs` — the main `@etn-sc/ensjs` library. Almost all work happens here.
- `packages/react` — `@etn-sc/ensjs-react` hooks (wagmi + tanstack-query). Excluded from Biome linting.

### Setup and Installation
```bash
# Install dependencies (uses pnpm)
pnpm install
```

### Development Environment
```bash
# Start test environment with ENS contracts
pnpm tenv start
Most scripts live in `packages/ensjs`, not the repo root. Run them from that directory, or from the root with `pnpm -F @etn-sc/ensjs <script>`. Root-level scripts are only `lint`, changeset commands (`chgset*`), `release`, and `publish:local:*`.

# Start development environment with extra time
pnpm denv
## Commands

# Start Anvil without ENS node
pnpm anvil
```

### Testing
```bash
# Run all tests (no file parallelism)
pnpm test

# Watch mode for tests
pnpm test:watch

# Run specific test file
pnpm vitest src/functions/public/getOwner.test.ts
pnpm install # install (root)
pnpm lint # Biome check (root)
pnpm biome check --write # auto-fix lint/format issues
```

### Build and Lint
```bash
# Build the project
pnpm build

# Run linter (using Biome)
pnpm lint

# Auto-fix linting issues
pnpm biome check --write

# Clean build artifacts
pnpm clean
```

#### Linting with Biome

This project uses **Biome** (not ESLint) for linting and formatting.
From `packages/ensjs`:

**Suppression Directives:**
- Line-level ignore: `// biome-ignore lint/rule-name: reason`
- Multiple rules: `// biome-ignore lint/rule1 lint/rule2: reason`
- **Note**: Biome does NOT support file-level `biome-ignore-all` directives. Use line-level comments only.

**Common Rules:**
- `lint/suspicious/noExplicitAny` - Avoid using `any` type
- `lint/complexity/noBannedTypes` - Avoid using `{}` as a type

**File Exclusion:**
- To exclude entire files/directories from linting, add them to `files.ignore` array in `biome.json`
- Example: `packages/react` is excluded from linting

**Important for Claude Code:**
When encountering Biome linter errors or suppression issues:
1. **Always use WebSearch** to look up the correct Biome syntax before suggesting alternatives
2. Check Biome documentation at https://biomejs.dev for the latest directives
3. Do not assume ESLint syntax will work with Biome
4. Remember: `biome-ignore-all` does NOT exist - only use line-level `biome-ignore`

### Package Management
```bash
# Version management
pnpm ver

# Changeset commands
pnpm chgset:run
pnpm chgset:version
pnpm chgset:version:prerelease

# Local publishing for testing
pnpm publish:local:ensjs
pnpm publish:local:ens-test-env
pnpm build # tsc build (also serves as the type check; CI runs this)
# NOTE: build first — the test env's fixture scripts import from dist/
pnpm tenv start # full test run: start anvil, deploy the .etn stack + fixtures, run vitest
pnpm denv # start the test environment only (anvil + contracts + fixtures), no tests
pnpm test # vitest --no-file-parallelism (requires denv running in another terminal)
pnpm test:watch # vitest watch mode (same requirement)
pnpm vitest src/functions/public/getOwner.test.ts # single test file (same requirement)
```

## Architecture

### Monorepo Structure
- `packages/ensjs` - Main ENS library
- `packages/react` - React hooks for ENS
- `packages/ens-test-env` - Testing environment
- `packages/query-core` - Query functionality
### Testing setup

### Client Types
Tests run against a real local deployment of the Electroneum contracts, not mocks. The environment is fully docker-free and driven by `scripts/testEnv.mjs`: it starts a local foundry `anvil` (chain 1337, `--odyssey` for the P256VERIFY precompile that the fork's DNSSEC algorithm-13 contract needs — the real Electroneum chains do NOT have this precompile), then runs `scripts/deployTestEnv.mjs`, which executes `@etn-sc/ens-contracts`' precompiled rocketh deploy pipeline in-process (tags mirror `electroneumTestnet`: no legacy/wrapped contracts) and registers the `.etn` test fixtures from `scripts/fixtures.mjs`. `scripts/rockethAliasHook.mjs` (a node resolve hook) makes the precompiled deploy scripts runnable under plain node. Deployed addresses reach tests via `DEPLOYMENT_ADDRESSES` in `.env.local`, consumed by `src/test/addTestContracts.ts`, which provides the shared `publicClient`/`walletClient`/`testClient` fixtures. `pnpm tenv start --extra-time 11368000` is the one-shot CI-style run (the extra time shifts the chain's start timestamp into the past so `expired*` fixtures are actually expired). Tests are colocated with source (`.test.ts`), run without file parallelism, and use the `node` vitest environment (a localStorage stub lives in `src/test/setup.ts`). `src/functions/subgraph/**` tests are excluded in `vitest.config.ts` until an Electroneum ENS subgraph exists.

ENSjs provides three specialized clients:
## Architecture

1. **Public Client** - Read-only blockchain operations
- Location: `src/clients/public.ts`
- Usage: `createEnsPublicClient({ chain, transport })`
### Chain and client typing

2. **Wallet Client** - Write operations requiring signer
- Location: `src/clients/wallet.ts`
- Usage: `createEnsWalletClient({ chain, transport, account })`
Everything is threaded through two types from `src/contracts/consts.ts`:

3. **Subgraph Client** - Query ENS subgraph
- Location: `src/clients/subgraph.ts`
- Usage: Part of public client or standalone
- `addEnsContracts(chain)` (`src/contracts/addEnsContracts.ts`) decorates a viem chain with ENS contract addresses, producing a `ChainWithEns`. Supported chain: `electroneumTestnet` (5201420) — addresses live in `src/contracts/consts.ts`, sourced from `../ens-contracts/deployments/electroneumTestnet/`. This includes `multicall3` (canonical address) because viem's Electroneum chain definitions don't ship one. No legacy/wrapped contract variants exist on Electroneum.
- The registrar TLD is defined once as `REGISTRAR_TLD = 'etn'` in `src/utils/consts.ts`; both the runtime TLD comparisons and the `` `${string}.etn` `` template-literal types derive from it. The `'eth-2ld'`/`'eth-tld'` NameType specifier strings are kept as opaque tags (they mean "registrar 2LD" and were not renamed, to avoid churning the public API).
- There is no ENS subgraph for Electroneum yet — `subgraphs` in `consts.ts` holds a localhost placeholder; `addEnsContracts(chain, { subgraphUrl })` overrides it. `/subgraph` functions won't work until one is deployed.
- Smoke test against the live testnet (read paths): `pnpm build && pnpm smoke:testnet` (in `packages/ensjs`).
- The Electroneum contracts are published as `@etn-sc/ens-contracts` (devDependency). `pnpm verify:addresses` (after `pnpm build`) checks `consts.ts` against its shipped deployment records — run it after bumping that package, and use its records as the source when adding mainnet addresses.
- `ClientWithEns` (a viem client on a `ChainWithEns`) is the first parameter of every function in the library. Contract addresses are resolved at call time via `getChainContractAddress`.

### Code Organization
### Three ways to consume the library

```
src/
├── clients/ # Client implementations
├── contracts/ # Contract definitions & addresses
├── functions/ # Core functionality
│ ├── public/ # Read operations
│ ├── wallet/ # Write operations
│ ├── subgraph/ # Subgraph queries
│ └── dns/ # DNS operations
├── utils/ # Utilities and helpers
└── errors/ # Error definitions
```
1. Standalone functions: `getOwner(client, { name: 'ens.eth' })` — imported from subpath exports.
2. Dedicated clients: `createEnsPublicClient` / `createEnsWalletClient` / `createEnsSubgraphClient` (`src/clients/`).
3. Extending an existing viem client: `.extend(ensPublicActions)` etc. — decorators in `src/clients/decorators/`.

### Key Contracts
Subpath exports (`@etn-sc/ensjs/public`, `/wallet`, `/subgraph`, `/dns`, `/contracts`, `/utils`) map to barrel files (`src/public.ts` etc.) via the `exports` field in `packages/ensjs/package.json`. New public functions must be exported from the matching barrel.

Contracts are defined in `src/contracts/consts.ts` for:
- Mainnet (chain ID: 1)
- Sepolia (chain ID: 11155111)
- Holesky (chain ID: 17000)
### Function structure

Main contracts:
- `ensRegistry` - Core ENS registry
- `ensPublicResolver` - Standard resolver
- `ensNameWrapper` - Name wrapper functionality
- `ensEthRegistrarController` - Name registration
- `ensUniversalResolver` - Batch operations
- `src/functions/public/` — read operations. Built with `generateFunction({ encode, decode })` from `src/utils/generateFunction.ts`: each function is split into an encode half (builds the transaction request) and a decode half (parses the return data). This is what enables `batch()` (`src/functions/public/batch.ts`) to combine multiple reads into one multicall — a function's `.batch(args)` returns its coders for the batch wrapper. Underscore-prefixed modules (`_getAddr.ts`, `_getText.ts`, …) are internal encode/decode pairs composed by the public-facing functions (e.g. `getRecords`). Most reads go through the Universal Resolver (`universalWrapper.ts`), with CCIP-read (offchain resolution) support.
- `src/functions/wallet/` — write operations (register, renew, records, subnames). Each exposes `makeFunctionData` for building calldata without sending.
- `src/functions/subgraph/` — ENS subgraph queries via `graphql-request`.
- `src/functions/dns/` — DNSSEC name import.
- `src/errors/` — custom error classes extending `src/errors/base.ts`, grouped by module.

### Testing Patterns
### Linting with Biome

- Tests use Vitest framework
- Test files are colocated with source (`.test.ts`)
- Uses `ens-test-env` for contract deployment
- No file parallelism to avoid conflicts
This project uses **Biome** (not ESLint). Key facts:

### Import Patterns

```typescript
// Main client
import { createEnsPublicClient } from '@ensdomains/ensjs'

// Specific modules
import { getOwner } from '@ensdomains/ensjs/public'
import { registerName } from '@ensdomains/ensjs/wallet'
import { getNamesForAddress } from '@ensdomains/ensjs/subgraph'
```
- Line-level suppression only: `// biome-ignore lint/rule-name: reason`. There is **no** file-level `biome-ignore-all` directive — do not invent one; if unsure of syntax, check https://biomejs.dev.
- File/directory exclusions go in the `files.ignore` array in `biome.json`. Already excluded: `packages/react`, `packages/ensjs/contracts`, and all `**/*.test.ts` files.
- Formatting: no semicolons, single quotes, 2-space indent (enforced by Biome's formatter).

## Important Notes
## Releases

- Always ensure tests pass before committing changes
- The library uses ESM modules (type: "module")
- Minimum Node.js version: 22
- Built with TypeScript for full type safety
- Supports tree-shaking for optimal bundle size
- Uses viem as the underlying Ethereum library
Versioning uses changesets (`pnpm chgset:run` to create one). CI (`.github/workflows/`) runs lint, build, and the full test env on push.
30 changes: 17 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,45 +1,49 @@
# ![ENSjs](https://user-images.githubusercontent.com/11844316/161689061-98ea01ee-b119-40ac-a512-5370eb8b4107.svg)
# ENSjs for Electroneum

The ultimate ENS javascript library, with [viem](https://github.com/wagmi-dev/viem) under the hood.
The Electroneum ENS javascript library, with [viem](https://github.com/wagmi-dev/viem) under the hood.

This is the [electroneum/ensjs](https://github.com/electroneum/ensjs) fork of [ensdomains/ensjs](https://github.com/ensdomains/ensjs), adapted for **Electroneum ENS**: the registrar TLD is **`.etn`** and the supported chain is Electroneum testnet (`electroneumTestnet`, chain id 5201420). The companion contracts live in [electroneum/ens-contracts](https://github.com/electroneum/ens-contracts) and are published as [`@etn-sc/ens-contracts`](https://www.npmjs.com/package/@etn-sc/ens-contracts).

## Features

- Super fast response times
- Easy call batchability
- Written in TypeScript
- Supports the most cutting edge ENS features
- Full tree-shaking support

## Installation

Install @ensdomains/ensjs, alongside [viem](https://github.com/wagmi-dev/viem).
Install @etn-sc/ensjs, alongside [viem](https://github.com/wagmi-dev/viem).

```sh
npm install @ensdomains/ensjs viem
npm install @etn-sc/ensjs viem
```

## Getting Started

The most simple way to get started is to create a public ENS client, with a supported
chain and transport imported from viem. The public client has all the read functions available on it,
as well as all subgraph functions.
The most simple way to get started is to create a public ENS client, with the
Electroneum chain and a transport imported from viem. The public client has all
the read functions available on it.

```ts
// Import viem transport, viem chain, and ENSjs
import { http } from 'viem'
import { mainnet } from 'viem/chains'
import { createEnsPublicClient } from '@ensdomains/ensjs'
import { electroneumTestnet } from 'viem/chains'
import { createEnsPublicClient } from '@etn-sc/ensjs'

// Create the client
const client = createEnsPublicClient({
chain: mainnet,
chain: electroneumTestnet,
transport: http(),
})

// Use the client
const ethAddress = client.getAddressRecord({ name: 'ens.eth' })
const etnAddress = client.getAddressRecord({ name: 'ens.etn' })
```

Note: there is no ENS subgraph for Electroneum yet, so the `/subgraph`
functions are not usable until one is deployed.

## Docs

Docs can be found [here](https://github.com/ensdomains/ensjs-v3/tree/main/docs).
Docs can be found in the upstream repo [here](https://github.com/ensdomains/ensjs/tree/main/docs) — the API is identical apart from the `.etn` TLD and supported chains.
2 changes: 1 addition & 1 deletion examples/basic-esm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"start": "node src/index.js"
},
"dependencies": {
"@ensdomains/ensjs": "workspace:*",
"@etn-sc/ensjs": "workspace:*",
"viem": "^2.37.12"
}
}
4 changes: 2 additions & 2 deletions examples/basic-esm/src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createEnsPublicClient } from '@ensdomains/ensjs'
import { getAddressRecord, getTextRecord } from '@ensdomains/ensjs/public'
import { createEnsPublicClient } from '@etn-sc/ensjs'
import { getAddressRecord, getTextRecord } from '@etn-sc/ensjs/public'
import { http } from 'viem'
import { mainnet } from 'viem/chains'

Expand Down
2 changes: 1 addition & 1 deletion examples/basic-tsnode-esm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"start": "ts-node-esm src/index.ts"
},
"dependencies": {
"@ensdomains/ensjs": "workspace:*",
"@etn-sc/ensjs": "workspace:*",
"ts-node": "^10.9.2",
"typescript": "^5.6.2",
"viem": "^2.37.12"
Expand Down
4 changes: 2 additions & 2 deletions examples/basic-tsnode-esm/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createEnsPublicClient } from '@ensdomains/ensjs'
import { getAddressRecord, getTextRecord } from '@ensdomains/ensjs/public'
import { createEnsPublicClient } from '@etn-sc/ensjs'
import { getAddressRecord, getTextRecord } from '@etn-sc/ensjs/public'
import { http } from 'viem'
import { mainnet } from 'viem/chains'

Expand Down
Loading