Skip to content

feat: add ENSv2 BENS subgraph - #3

Merged
JOY (JOY) merged 14 commits into
dosfrom
codex/ensv2-bens-subgraph
Aug 12, 2026
Merged

feat: add ENSv2 BENS subgraph#3
JOY (JOY) merged 14 commits into
dosfrom
codex/ensv2-bens-subgraph

Conversation

@JOY

Copy link
Copy Markdown

Summary

  • add a DOS ENSv2 subgraph compatible with the official BENS schema
  • index registry, registrar, resolver, ownership, expiry, and nested registry events
  • render immutable Testnet manifests from a deployment record
  • gate Docker publishing on subgraph tests and build validation

Validation

  • 15 Matchstick mapping tests pass
  • 6 manifest renderer tests pass
  • graph codegen and build pass
  • npm audit --omit=dev reports 0 vulnerabilities

Scope

The custom indexing adapter stays in DOS Names. DOScan consumes it by immutable commit and runs only official BENS, Graph Node, IPFS, and PostgreSQL images.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new subgraph adapter for the DOS Name Service (.dos), an ENSv2 fork on the DOS Chain compatible with Blockscout BENS. The review feedback highlights several key improvement opportunities: consistently using the namehash (node) as the ID for Registration entities to simplify lookup logic and prevent subdomain ID collisions; removing redundant Domain.load database reads in subregistry update handlers; cleaning up dead or redundant utility code (such as the custom byteArrayFromHex in favor of the built-in ByteArray.fromHexString); ensuring referential integrity by creating the Account entity for the empty address; and updating the test suite to reflect these entity ID changes.

Comment on lines +134 to +135
// Create Registration entity (id = labelHash hex)
let registration = new Registration(labelHash.toHexString());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The Registration entity is created here using labelHash.toHexString() as its ID, whereas in userRegistry.ts it is created using node (the namehash). This inconsistency can lead to load failures and requires complex null checks on domain.labelhash when loading registrations. Furthermore, using labelHash as the ID can cause collisions if subdomains in different registries share the same label (e.g., test.dos and test.alice.dos). Consistently using node (the namehash) as the Registration ID everywhere solves these issues and simplifies the code.

Suggested change
// Create Registration entity (id = labelHash hex)
let registration = new Registration(labelHash.toHexString());
// Create Registration entity (id = node namehash)
let registration = new Registration(node);

Comment on lines +249 to +255
if (domain.labelhash !== null) {
let registration = Registration.load(domain.labelhash!.toHexString());
if (registration !== null) {
registration.expiryDate = newExpiry;
registration.save();
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since Registration ID is now consistently set to node (the namehash), we can load it directly using node without checking domain.labelhash.

  let registration = Registration.load(node);
  if (registration !== null) {
    registration.expiryDate = newExpiry;
    registration.save();
  }

Comment on lines +314 to +320
if (domain.labelhash !== null) {
let registration = Registration.load(domain.labelhash!.toHexString());
if (registration !== null) {
registration.registrant = account.id;
registration.save();
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since Registration ID is now consistently set to node (the namehash), we can load it directly using node without checking domain.labelhash.

  let registration = Registration.load(node);
  if (registration !== null) {
    registration.registrant = account.id;
    registration.save();
  }

Comment on lines +371 to +378
if (domain.labelhash !== null) {
let registration = Registration.load(domain.labelhash!.toHexString());
if (registration !== null) {
registration.registrant = zeroAccount.id;
registration.expiryDate = event.block.timestamp;
registration.save();
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since Registration ID is now consistently set to node (the namehash), we can load it directly using mapping.domain without checking domain.labelhash.

  let registration = Registration.load(mapping.domain);
  if (registration !== null) {
    registration.registrant = zeroAccount.id;
    registration.expiryDate = event.block.timestamp;
    registration.save();
  }

Comment on lines +72 to +80
// Update registration with cost
if (domain.labelhash !== null) {
let registration = Registration.load(domain.labelhash!.toHexString());
if (registration !== null) {
registration.labelName = label;
registration.cost = cost;
registration.save();
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since Registration ID is now consistently set to node (the namehash), we can load it directly using node without checking domain.labelhash.

    // Update registration with cost
    let registration = Registration.load(node);
    if (registration !== null) {
      registration.labelName = label;
      registration.cost = cost;
      registration.save();
    }

Comment on lines +31 to +40
export function byteArrayFromHex(s: string): ByteArray {
if (s.length % 2 !== 0) {
throw new TypeError("Hex string must have an even number of characters");
}
let out = new Uint8Array(s.length / 2);
for (var i = 0; i < s.length; i += 2) {
out[i / 2] = parseInt(s.substring(i, i + 2), 16) as u32;
}
return changetype<ByteArray>(out);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The custom byteArrayFromHex function is redundant because @graphprotocol/graph-ts provides a built-in, highly optimized ByteArray.fromHexString static method. We can remove this custom function entirely.

Comment on lines +56 to +58
var dosNode: ByteArray = byteArrayFromHex(
DOS_NODE.slice(2) // strip 0x prefix
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

We can define dosNode directly using the built-in ByteArray.fromHexString method instead of the custom byteArrayFromHex function.

var dosNode = ByteArray.fromHexString(DOS_NODE);

Comment on lines +63 to +76
export function createOrLoadDomain(node: string): Domain {
let domain = Domain.load(node);
if (domain == null) {
domain = new Domain(node);
domain.owner = EMPTY_ADDRESS;
domain.isMigrated = true;
domain.createdAt = BigInt.fromI32(0);
domain.subdomainCount = 0;
domain.storedOffchain = false;
domain.resolvedWithWildcard = false;
domain.save();
}
return domain;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In createOrLoadDomain, domain.owner is set to EMPTY_ADDRESS. However, the corresponding Account entity for EMPTY_ADDRESS is not created or loaded. This can lead to broken references or referential integrity issues when querying the domain's owner. Ensuring that the Account entity is created/loaded via createOrLoadAccount(EMPTY_ADDRESS) resolves this.

Suggested change
export function createOrLoadDomain(node: string): Domain {
let domain = Domain.load(node);
if (domain == null) {
domain = new Domain(node);
domain.owner = EMPTY_ADDRESS;
domain.isMigrated = true;
domain.createdAt = BigInt.fromI32(0);
domain.subdomainCount = 0;
domain.storedOffchain = false;
domain.resolvedWithWildcard = false;
domain.save();
}
return domain;
}
export function createOrLoadDomain(node: string): Domain {
let domain = Domain.load(node);
if (domain == null) {
domain = new Domain(node);
let zeroAccount = createOrLoadAccount(EMPTY_ADDRESS);
domain.owner = zeroAccount.id;
domain.isMigrated = true;
domain.createdAt = BigInt.fromI32(0);
domain.subdomainCount = 0;
domain.storedOffchain = false;
domain.resolvedWithWildcard = false;
domain.save();
}
return domain;
}

handleNameRegisteredByRegistrar(registrarRegistration(123));

assert.fieldEquals("Domain", ALICE_DOS, "name", "alice.dos");
assert.fieldEquals("Registration", LABEL_HASH, "cost", "12");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since the Registration entity ID is now consistently set to node (the namehash, which is ALICE_DOS), we should assert on ALICE_DOS instead of LABEL_HASH.

Suggested change
assert.fieldEquals("Registration", LABEL_HASH, "cost", "12");
assert.fieldEquals("Registration", ALICE_DOS, "cost", "12");

Comment on lines +182 to +183
assert.fieldEquals("Registration", LABEL_HASH, "cost", "7");
assert.fieldEquals("Registration", LABEL_HASH, "expiryDate", "2100000000");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since the Registration entity ID is now consistently set to node (the namehash, which is ALICE_DOS), we should assert on ALICE_DOS instead of LABEL_HASH.

Suggested change
assert.fieldEquals("Registration", LABEL_HASH, "cost", "7");
assert.fieldEquals("Registration", LABEL_HASH, "expiryDate", "2100000000");
assert.fieldEquals("Registration", ALICE_DOS, "cost", "7");
assert.fieldEquals("Registration", ALICE_DOS, "expiryDate", "2100000000");

@JOY
JOY (JOY) merged commit 34c3651 into dos Aug 12, 2026
7 checks passed
@JOY
JOY (JOY) deleted the codex/ensv2-bens-subgraph branch August 12, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant