Skip to content

Commit 67179ae

Browse files
moose-codeclaude
andcommitted
Add Registrars plugin (Phase 5)
Unified registration lifecycle tracking across all subregistries (Ethnames .eth, Basenames .base.eth, Lineanames .linea.eth). Tracks subregistry metadata, registration lifecycles, and logical registrar actions that aggregate data from multiple events in the same transaction. - Add 4 new schema entities (Subregistry, RegistrationLifecycle, RegistrarAction, RegistrarActionMetadata) and RegistrarActionType enum - Create registrar-helpers.ts with CAIP-10 ID generation, bytes32 referrer decoding, lifecycle management, and action aggregation - Add UniversalRenewal contract (RenewalReferred event) on mainnet - Merge registrar logic into existing BaseRegistrar + Controller handlers across all 3 chains (17 events updated) - Add transaction.from to BaseRegistrar field_selection for registrant Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 09d861b commit 67179ae

File tree

8 files changed

+900
-43
lines changed

8 files changed

+900
-43
lines changed

MIGRATION_PLAN.md

Lines changed: 84 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
- [Phase 2: Tests for Phase 1](#phase-2-tests-for-phase-1) — DONE
1717
- [Phase 3: Multi-Chain Subgraph Extensions](#phase-3-multi-chain-subgraph-extensions)
1818
- [Phase 4: Protocol Acceleration Plugin](#phase-4-protocol-acceleration-plugin) — DONE
19-
- [Phase 5: Registrars Plugin](#phase-5-registrars-plugin)
19+
- [Phase 5: Registrars Plugin](#phase-5-registrars-plugin) — DONE
2020
- [Phase 6: TokenScope Plugin](#phase-6-tokenscope-plugin)
2121
- [Phase 7: ENSv2 Plugin](#phase-7-ensv2-plugin)
2222
- [Contract Reference](#contract-reference)
@@ -38,7 +38,7 @@ The ENSNode Ponder indexer is a monorepo with **8 plugins** across **6 chains**
3838
| 3b | Lineanames (Linea L2) | DONE ||
3939
| 3c | ThreeDNS (Optimism + Base) | DONE ||
4040
| 4 | Protocol Acceleration | DONE ||
41-
| 5 | Registrars | NOT STARTED | Medium |
41+
| 5 | Registrars | DONE | |
4242
| 6 | TokenScope | NOT STARTED | Medium |
4343
| 7 | ENSv2 | NOT STARTED | Low (future protocol) |
4444

@@ -48,20 +48,20 @@ The ENSNode Ponder indexer is a monorepo with **8 plugins** across **6 chains**
4848

4949
### What's Done
5050

51-
The HyperIndex indexer covers the **Subgraph Plugin for Ethereum Mainnet** (Phase 1), **Basenames on Base L2** (Phase 3a), **Lineanames on Linea L2** (Phase 3b), **ThreeDNS on Optimism + Base** (Phase 3c), and the **Protocol Acceleration Plugin** (Phase 4):
51+
The HyperIndex indexer covers the **Subgraph Plugin for Ethereum Mainnet** (Phase 1), **Basenames on Base L2** (Phase 3a), **Lineanames on Linea L2** (Phase 3b), **ThreeDNS on Optimism + Base** (Phase 3c), the **Protocol Acceleration Plugin** (Phase 4), and the **Registrars Plugin** (Phase 5):
5252

5353
- **6 chains**: Ethereum Mainnet (1), Base (8453), Linea (59144), Optimism (10), Arbitrum (42161), Scroll (534352)
54-
- **20 contracts** configured in `config.yaml` (10 mainnet + 6 Base + 2 Linea + ThreeDNSToken shared + StandaloneReverseRegistrar across 5 chains, with Registry, Resolver, and NameWrapper reused cross-chain)
54+
- **21 contracts** configured in `config.yaml` (11 mainnet + 6 Base + 2 Linea + ThreeDNSToken shared + StandaloneReverseRegistrar across 5 chains, with Registry, Resolver, and NameWrapper reused cross-chain)
5555
- **8 handler files**: `Registry.ts`, `Registrar.ts`, `NameWrapper.ts`, `Resolver.ts`, `BaseRegistrar.ts`, `LineaRegistrar.ts`, `ThreeDNS.ts`, `ReverseRegistrar.ts`
56-
- **2 helper libraries**: `helpers.ts` (20 utility functions), `protocol-acceleration.ts` (PA helper module with ID generators, coin type utilities, interpretation functions, DB helpers)
57-
- **32 schema entities** (7 core + 18 event logs + 7 PA entities)
58-
- **~52 event types** handled (26 mainnet + 9 Base + 7 Linea + 4 ThreeDNS + 3 DNS record + 1 StandaloneReverseRegistrar + PA logic merged into existing handlers)
56+
- **3 helper libraries**: `helpers.ts` (20 utility functions), `protocol-acceleration.ts` (PA helper module), `registrar-helpers.ts` (registrar action tracking, referrer decoding, lifecycle management)
57+
- **37 schema entities** (7 core + 18 event logs + 7 PA entities + 1 enum + 4 registrar entities)
58+
- **~53 event types** handled (27 mainnet + 9 Base + 7 Linea + 4 ThreeDNS + 3 DNS record + 1 StandaloneReverseRegistrar + registrar/PA logic merged into existing handlers)
5959
- All handlers are production-quality — no TODOs, stubs, or placeholders
6060

6161
### What's Missing
6262

63-
- **~15 additional contracts** (registrars, tokenscope, ensv2)
64-
- **~17+ additional entities** (ensv2, registrars, tokenscope schemas)
63+
- **~14 additional contracts** (tokenscope, ensv2)
64+
- **~14+ additional entities** (ensv2, tokenscope schemas)
6565
- **3 remaining plugins** worth of handler logic
6666

6767
---
@@ -426,48 +426,92 @@ PA logic is **merged into existing handlers** via PA helper function calls appen
426426

427427
## Phase 5: Registrars Plugin
428428

429-
**Status: NOT STARTED**
430-
**Priority: Medium**
429+
**Status: DONE**
430+
**Priority: **
431431
**Effort: Medium**
432432
**Source:** `ensnode/apps/ensindexer/src/plugins/registrars/`
433433

434-
Unified registration lifecycle tracking across all subregistries (Ethnames, Basenames, Lineanames).
434+
Unified registration lifecycle tracking across all subregistries (Ethnames, Basenames, Lineanames). Tracks subregistry metadata, registration lifecycles (current state per managed name), and "logical registrar actions" that aggregate data from multiple events in the same transaction (BaseRegistrar events + Controller events).
435435

436-
### New Schema Entities
436+
### Schema Entities Added (1 enum + 4 entities)
437437

438438
| Entity | Purpose |
439439
|--------|---------|
440-
| `subregistries` | Subregistry metadata (CAIP-10 ID, namehash) |
441-
| `registrationLifecycles` | Current registration state per managed name |
442-
| `registrarActions` | Logical registration/renewal actions with cost breakdown |
440+
| `RegistrarActionType` (enum) | `registration` / `renewal` |
441+
| `Subregistry` | Subregistry metadata (CAIP-10 ID `eip155:{chainId}:{address}`, managed namehash) |
442+
| `RegistrationLifecycle` | Current registration state per managed name (node → expiresAt) |
443+
| `RegistrarAction` | Logical registration/renewal actions with cost breakdown, referral data, and multi-event aggregation |
444+
| `RegistrarActionMetadata` | Singleton tracking the "current" logical action being built across events |
443445

444-
### Handler Work
446+
### Contract Added
445447

446-
- [ ] Add `subregistries`, `registrationLifecycles`, `registrarActions` entities to schema
447-
- [ ] Migrate `Ethnames_Registrar.ts` — BaseRegistrar events for .eth
448-
- [ ] Migrate `Ethnames_RegistrarController.ts` — controller events with cost/referrer parsing
449-
- [ ] Migrate `Ethnames_UniversalRegistrarRenewalWithReferrer.ts` — universal renewal handler
450-
- [ ] Migrate `Basenames_Registrar.ts` — BaseRegistrar events for .base.eth
451-
- [ ] Migrate `Basenames_RegistrarController.ts` — Base controller events
452-
- [ ] Migrate `Lineanames_Registrar.ts` — BaseRegistrar events for .linea.eth
453-
- [ ] Migrate `Lineanames_RegistrarController.ts` — 4 events: `OwnerNameRegistered`, `PohNameRegistered`, `NameRegistered`, `NameRenewed`
454-
- [ ] Port shared library logic:
455-
- `registrar-action.ts` — logical action aggregation
456-
- `registrar-events.ts` — BaseRegistrar event handlers (NameRegistered, NameRenewed)
457-
- `registrar-controller-events.ts` — controller event handlers (NameRegistered, NameRenewed by controller)
458-
- `registration-lifecycle.ts` — lifecycle state machine
459-
- `subregistry.ts` — subregistry tracking
460-
- `universal-registrar-renewal-with-referrer-events.ts` — referrer tracking for universal renewals
461-
462-
### Additional Mainnet Contract
448+
| Contract | HyperIndex Name | Chain | Address | Start Block |
449+
|----------|----------------|-------|---------|-------------|
450+
| UniversalRegistrarRenewalWithReferrer | `UniversalRenewal` | Mainnet (1) | `0xf55575bde5953ee4272d5ce7cdd924c74d8fa81a` | 23,784,217 |
463451

464-
| Contract | Address | Start Block |
465-
|----------|---------|-------------|
466-
| UniversalRegistrarRenewalWithReferrer | `0xf55575bde5953ee4272d5ce7cdd924c74d8fa81a` | 23,784,217 |
452+
### Implementation Summary
453+
454+
**Files changed:**
455+
456+
| File | Action | Changes |
457+
|------|--------|---------|
458+
| `schema.graphql` | Modified | Added 1 enum + 4 entities (Subregistry, RegistrationLifecycle, RegistrarAction, RegistrarActionMetadata) |
459+
| `config.yaml` | Modified | Added UniversalRenewal contract definition + mainnet chain entry; added `from` to `transaction_fields` for all BaseRegistrar NameRegistered/NameRenewed events across mainnet, Base, and Linea |
460+
| `abis/UniversalRegistrarRenewalWithReferrer.json` | Created | ABI for RenewalReferred event |
461+
| `src/lib/registrar-helpers.ts` | Created | ~280 lines: ID generators (CAIP-10 subregistry, logical event key), referrer decoding (bytes32 → checksummed address with left-zero-padding validation), subregistry upsert, registration lifecycle management, registrar action creation, handler helpers |
462+
| `src/handlers/Registrar.ts` | Modified | Appended registrar calls to BaseRegistrar.NameRegistered/NameRenewed + all 3 controller NameRegistered/NameRenewed handlers; added new UniversalRenewal.RenewalReferred handler |
463+
| `src/handlers/BaseRegistrar.ts` | Modified | Appended registrar calls to BaseRegistrar_Base.NameRegistered/NameRegisteredWithRecord/NameRenewed + all 3 Base controller NameRegistered/NameRenewed handlers |
464+
| `src/handlers/LineaRegistrar.ts` | Modified | Appended registrar calls to BaseRegistrar_Linea.NameRegistered/NameRenewed + all 4 Linea controller event handlers |
465+
466+
**Registrar logic appended to existing handlers (17 events total):**
467+
468+
*Mainnet (.eth):*
469+
- `BaseRegistrar.NameRegistered``handleRegistrarRegistration()` — creates Subregistry + RegistrationLifecycle + initial RegistrarAction
470+
- `BaseRegistrar.NameRenewed``handleRegistrarRenewal()` — creates renewal RegistrarAction + updates lifecycle expiry
471+
- `LegacyController.NameRegistered/NameRenewed``handleRegistrarControllerEvent()` — updates action with baseCost/total pricing
472+
- `WrappedController.NameRegistered``handleRegistrarControllerEvent()` — updates action with baseCost + premium pricing
473+
- `WrappedController.NameRenewed``handleRegistrarControllerEvent()` — updates action with cost pricing
474+
- `UnwrappedController.NameRegistered/NameRenewed``handleRegistrarControllerEvent()` — updates action with pricing + decoded referrer
475+
- `UniversalRenewal.RenewalReferred``handleUniversalRenewalEvent()` — updates action with referral data only (new handler)
476+
477+
*Base (.base.eth):*
478+
- `BaseRegistrar_Base.NameRegistered/NameRegisteredWithRecord``handleRegistrarRegistration()`
479+
- `BaseRegistrar_Base.NameRenewed``handleRegistrarRenewal()`
480+
- `EAController_Base/RegController_Base/UpgController_Base.NameRegistered/NameRenewed``handleRegistrarControllerEvent()` with pricing=undefined (unknown per Ponder TODO)
481+
482+
*Linea (.linea.eth):*
483+
- `BaseRegistrar_Linea.NameRegistered``handleRegistrarRegistration()`
484+
- `BaseRegistrar_Linea.NameRenewed``handleRegistrarRenewal()`
485+
- `EthController_Linea.OwnerNameRegistered/PohNameRegistered``handleRegistrarControllerEvent()` with baseCost=0, premium=0, total=0
486+
- `EthController_Linea.NameRegistered``handleRegistrarControllerEvent()` with baseCost + premium
487+
- `EthController_Linea.NameRenewed``handleRegistrarControllerEvent()` with cost
488+
489+
### Key Design Decisions
490+
491+
1. **Metadata singleton**: `RegistrarActionMetadata` entity with `id: "current"` replaces Ponder's `internal_registrarActionMetadata` singleton. Tracks which registrar action is being built across multiple events in the same TX.
492+
2. **Pricing simplification**: Ponder uses complex `PriceEth` / `RegistrarActionPricingAvailable` types with currency wrappers. We store raw bigint amounts directly (all prices in Wei). `undefined` = pricing unknown (Basenames controllers).
493+
3. **No CAIP dependency**: CAIP-10 formatting implemented inline (`eip155:{chainId}:{address}`) rather than importing the `caip` npm package.
494+
4. **Event ordering assumption**: Within a block, events are processed in logIndex order. BaseRegistrar events precede Controller events in the same TX because BaseRegistrar fires first in the call chain. Critical for the metadata aggregation pattern.
495+
5. **Referrer decoding**: Ported from `ensnode-sdk/src/registrars/encoded-referrer.ts` — validates left-zero-padding (first 12 bytes must be zeros), extracts last 20 bytes as checksummed address. Non-zero padding returns zero address.
496+
6. **`transaction.from` requirement**: Added `from` to `transaction_fields` for BaseRegistrar events on all 3 chains. Used as the `registrant` field on RegistrarAction. Falls back to `zeroAddress` for TypeScript safety.
497+
498+
### Completed Tasks
499+
500+
- [x] Add 1 enum + 4 entities to `schema.graphql` (RegistrarActionType, Subregistry, RegistrationLifecycle, RegistrarAction, RegistrarActionMetadata)
501+
- [x] Create `abis/UniversalRegistrarRenewalWithReferrer.json` ABI file
502+
- [x] Add UniversalRenewal contract definition + mainnet chain entry to `config.yaml`
503+
- [x] Add `from` to `transaction_fields` for BaseRegistrar events across all 3 chains
504+
- [x] Create `src/lib/registrar-helpers.ts` — CAIP-10 IDs, referrer decoding, lifecycle management, action creation, handler helpers
505+
- [x] Merge registrar logic into `Registrar.ts` — 7 existing handlers updated + 1 new handler (UniversalRenewal.RenewalReferred)
506+
- [x] Merge registrar logic into `BaseRegistrar.ts` — 8 existing handlers updated
507+
- [x] Merge registrar logic into `LineaRegistrar.ts` — 6 existing handlers updated
508+
- [x] Run `pnpm codegen` — generates types for all new entities + UniversalRenewal contract + enum
509+
- [x] Type check passes (`pnpm tsc --noEmit` — zero errors)
510+
- [x] Existing tests pass (all 59 registrar-related tests pass)
467511

468512
### Architecture Note
469513

470-
> The registrars plugin tracks the same BaseRegistrar/Controller events as the subgraph plugin but writes to different entities. Same merge/compose consideration as Phase 4.
514+
> Like Phase 4 (Protocol Acceleration), registrar logic is **merged into existing handlers** via helper function calls appended after subgraph logic. This preserves zero changes to subgraph behavior while adding registrar functionality. All registrar helper functions live in `src/lib/registrar-helpers.ts`.
471515
472516
---
473517

@@ -628,10 +672,10 @@ Next-generation ENS protocol with new registry and registrar contracts.
628672
|--------|--------------|----------------|-------|
629673
| Subgraph (Phase 1) | 5 | 20 | 25 |
630674
| Protocol Acceleration (Phase 4) | 7 | 0 | 7 |
631-
| Registrars (Phase 5) | 3 | 0 | 3 |
675+
| Registrars (Phase 5) | 5 (incl. 1 enum) | 0 | 5 |
632676
| TokenScope (Phase 6) | 2 | 0 | 2 |
633677
| ENSv2 (Phase 7) | 12 | 0 | 12 |
634-
| **Total** | **29** | **20** | **~49** |
678+
| **Total** | **31** | **20** | **~51** |
635679

636680
---
637681

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"anonymous": false,
4+
"inputs": [
5+
{ "indexed": false, "internalType": "string", "name": "label", "type": "string" },
6+
{ "indexed": true, "internalType": "bytes32", "name": "labelHash", "type": "bytes32" },
7+
{ "indexed": false, "internalType": "uint256", "name": "cost", "type": "uint256" },
8+
{ "indexed": false, "internalType": "uint256", "name": "duration", "type": "uint256" },
9+
{ "indexed": false, "internalType": "bytes32", "name": "referrer", "type": "bytes32" }
10+
],
11+
"name": "RenewalReferred",
12+
"type": "event"
13+
}
14+
]

config.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@ contracts:
4949
field_selection:
5050
transaction_fields:
5151
- hash
52+
- from
5253
- event: "NameRenewed(uint256 indexed id, uint256 expires)"
5354
field_selection:
5455
transaction_fields:
5556
- hash
57+
- from
5658
- event: "Transfer(address indexed from, address indexed to, uint256 indexed tokenId)"
5759
field_selection:
5860
transaction_fields:
@@ -127,14 +129,17 @@ contracts:
127129
field_selection:
128130
transaction_fields:
129131
- hash
132+
- from
130133
- event: "NameRegisteredWithRecord(uint256 indexed id, address indexed owner, uint256 expires, address resolver, uint64 ttl)"
131134
field_selection:
132135
transaction_fields:
133136
- hash
137+
- from
134138
- event: "NameRenewed(uint256 indexed id, uint256 expires)"
135139
field_selection:
136140
transaction_fields:
137141
- hash
142+
- from
138143
- event: "Transfer(address indexed from, address indexed to, uint256 indexed tokenId)"
139144
field_selection:
140145
transaction_fields:
@@ -176,10 +181,12 @@ contracts:
176181
field_selection:
177182
transaction_fields:
178183
- hash
184+
- from
179185
- event: "NameRenewed(uint256 indexed id, uint256 expires)"
180186
field_selection:
181187
transaction_fields:
182188
- hash
189+
- from
183190
- event: "Transfer(address indexed from, address indexed to, uint256 indexed tokenId)"
184191
field_selection:
185192
transaction_fields:
@@ -283,6 +290,17 @@ contracts:
283290
transaction_fields:
284291
- hash
285292

293+
# ─── UniversalRenewal (Registrars plugin) ──────────────────────────────────
294+
- name: UniversalRenewal
295+
abi_file_path: ./abis/UniversalRegistrarRenewalWithReferrer.json
296+
handler: ./src/handlers/Registrar.ts
297+
events:
298+
- event: "RenewalReferred(string label, bytes32 indexed labelHash, uint256 cost, uint256 duration, bytes32 referrer)"
299+
field_selection:
300+
transaction_fields:
301+
- hash
302+
- from
303+
286304
# ─── StandaloneReverseRegistrar (PA plugin) ────────────────────────────────
287305
- name: StandaloneReverseRegistrar
288306
events:
@@ -323,6 +341,10 @@ chains:
323341
address:
324342
- "0x59e16fccd424cc24e280be16e11bcd56fb0ce547"
325343
start_block: 22764821
344+
- name: UniversalRenewal
345+
address:
346+
- "0xf55575bde5953ee4272d5ce7cdd924c74d8fa81a"
347+
start_block: 23784217
326348
- name: Resolver
327349
address:
328350
# Static reverse resolver addresses (PA plugin)

schema.graphql

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,3 +306,45 @@ type ReverseNameRecord {
306306
type MigratedNode {
307307
id: ID!
308308
}
309+
310+
# ─── Registrars Plugin Entities ────────────────────────────────────────────
311+
312+
enum RegistrarActionType {
313+
registration
314+
renewal
315+
}
316+
317+
type Subregistry {
318+
id: ID! # CAIP-10: "eip155:{chainId}:{address}"
319+
node: String! # namehash of managed name (e.g. ETH_NODE)
320+
}
321+
322+
type RegistrationLifecycle {
323+
id: ID! # node hash (domain namehash)
324+
subregistryId: String! # FK to Subregistry
325+
expiresAt: BigInt! # Unix timestamp
326+
}
327+
328+
type RegistrarAction {
329+
id: ID! # first event ID (from BaseRegistrar event)
330+
type: RegistrarActionType!
331+
subregistryId: String! # FK to Subregistry
332+
node: String! # domain namehash
333+
incrementalDuration: BigInt!
334+
baseCost: BigInt # nullable (unknown for some controllers)
335+
premium: BigInt # nullable
336+
total: BigInt # nullable
337+
registrant: String! # transaction.from address
338+
encodedReferrer: String # nullable, raw bytes32
339+
decodedReferrer: String # nullable, decoded address
340+
blockNumber: BigInt!
341+
timestamp: BigInt!
342+
transactionHash: String!
343+
eventIds: [String!]! # array of contributing event IDs
344+
}
345+
346+
type RegistrarActionMetadata {
347+
id: ID! # always "current" (singleton)
348+
logicalEventKey: String! # "{node}:{transactionHash}"
349+
logicalEventId: String! # ID of registrarAction being built
350+
}

0 commit comments

Comments
 (0)