-
Notifications
You must be signed in to change notification settings - Fork 104
Description
Problem Statement
When a domain is registered directly via Registry.setSubnodeRecord() (user owns the baseNode at Registry level), the RegistrarController.register() function cannot be used because it requires BaseRegistrar contract to own the baseNode, as enforced by the live() modifier.
This creates a developer experience issue: Developers who choose direct Registry ownership for full control cannot use the convenient RegistrarController.register() method with its atomic batching and payment handling.
Current Behavior
Standard Flow (BaseRegistrar Owns BaseNode)
BaseRegistrarcontract owns the baseNode (e.g.,scenius.basetest.eth)RegistrarControlleris authorized as a controller- Users can call
RegistrarController.register()which:- Handles payment automatically
- Batches record setting atomically
- Sets expiry automatically
Direct Registration Flow (User Owns BaseNode)
- User registers domain directly via
Registry.setSubnodeRecord() - User owns the baseNode directly at Registry level
- Cannot use
RegistrarController.register()- reverts withRegistrarNotLiveerror - Must use manual approach:
Registry.setSubnodeRecord()(free, but separate transaction)- Individual resolver operations (
setAddr,setText) in separate transactions - No atomic batching at contract level
Code Reference
The issue is in RegistrarController.sol:
modifier live() {
if (registry.owner(baseNode) != address(this)) revert RegistrarNotLive();
_;
}This modifier prevents register() from working when the baseNode is owned directly by a user instead of BaseRegistrar.
Related Contracts:
BaseRegistrar.sol-live()modifier (line 151-154)RegistrarController.sol-register()function (line 438)Registry.sol-setSubnodeOwner()andsetSubnodeRecord()(lines 113-124, 85-92)
Trade-offs of Direct Registry Ownership
Advantages:
- ✅ FREE - No payment required (
setSubnodeRecord()is nonpayable) - ✅ Full Control - User maintains direct ownership at Registry level
- ✅ No Expiry - Subnames do NOT expire (permanent ownership)
Disadvantages:
- ❌ No Tokenization - Subnames are NOT ERC721 tokens (no NFT minting)
- ❌ No Expiry Mechanism - Subnames never expire (may be pro or con)
- ❌ No Atomic Batching - Cannot batch record setting in single contract call (must use multiple operations)
Real-World Impact
Our Use Case: Catalogue Platform
We (Catalogue Platform) registered scenius.basetest.eth directly to maintain full control. We need to:
- Create subnames programmatically (e.g.,
eros001.scenius.basetest.eth) - Set multiple ENS records atomically (address + 10 text records)
- Execute via Safe multisig transactions
Current Workaround:
We've implemented a workaround that executes 2 Safe transactions:
- Transaction 1:
Registry.setSubnodeRecord()- Creates subname - Transaction 2: Batched resolver operations -
setAddr+ 10setTextcalls
This works, but requires:
- Multiple Safe transactions (2 instead of 1)
- Manual gas estimation (we had to fix GS025 errors by removing
safeTxGas: '1') - No atomic guarantee at contract level (Safe batches, but not contract-level atomicity)
Verification:
We've successfully registered and verified multiple subnames:
eros006.scenius.basetest.eth✅eros007.scenius.basetest.eth✅- All 10 text records set correctly ✅
Proposed Solutions
Option 1: Extend RegistrarController to Support Direct Owners
Modify RegistrarController to support both:
- BaseRegistrar-owned baseNodes (current behavior)
- Direct owner baseNodes (new capability)
Implementation approach:
- Check if caller is authorized (owner or operator) for the baseNode
- Bypass the
live()check when caller is directly authorized - Maintain backward compatibility with existing BaseRegistrar flow
Pros:
- Single controller for both use cases
- Maintains existing API
- Backward compatible
Cons:
- Requires contract modification
- More complex authorization logic
Option 2: Create Alternative Controller Contract
Create a new controller contract (e.g., DirectOwnerController) that:
- Works with direct owners (no BaseRegistrar requirement)
- Provides same batching and payment features as
RegistrarController - Can be used alongside existing
RegistrarController
Pros:
- No changes to existing contracts
- Clear separation of concerns
- Can be deployed independently
Cons:
- Code duplication
- Two controllers to maintain
- Developer confusion about which to use
Option 3: Documentation & Helper Contracts
- Document the two registration paths clearly
- Provide helper contracts/libraries for direct owners
- Create examples for both approaches
- Add tooling to simplify direct owner workflows
Pros:
- No contract changes required
- Can be implemented immediately
- Improves developer experience
Cons:
- Doesn't solve the atomic batching limitation
- Still requires multiple transactions
- Less convenient than Option 1 or 2
Questions for Basenames Team
-
Architecture Intent: Is this architecture intentional (security/design decision)? Are there specific reasons why
RegistrarControllerrequiresBaseRegistrarownership? -
Future Plans: Are there plans to support direct owners in
RegistrarControlleror provide an alternative controller? -
Controller Contract: Would a new controller contract for direct owners be acceptable? Would the Basenames team be open to reviewing/accepting such a contract?
-
Recommended Approach: What is the recommended approach for developers who want direct Registry ownership but also need atomic batching?
-
Feature Trade-offs: Is there a way to get tokenization/expiry features while maintaining direct Registry ownership? Or are these features fundamentally incompatible with direct ownership?
-
Documentation: Should we document these trade-offs more clearly in the Basenames documentation? We're happy to contribute documentation if helpful.
Environment Details
Network: Base Sepolia Testnet
Contract Addresses:
- Registry:
0x1493b2567056c2181630115660963E13A8E32735 - BaseRegistrar:
0xa0c70ec36c010b55e3c434d6c6ebeec50c705794 - RegistrarController:
0x82c858CDF64b3D893Fe54962680edFDDC37e94C8 - Resolver:
0x85C87e548091f204C2d0350b39ce1874f02197c6
Test Subnames:
eros006.scenius.basetest.eth- Successfully registered with all recordseros007.scenius.basetest.eth- Successfully registered with all records
Additional Context
We've built a working implementation that:
- ✅ Successfully creates subnames via
Registry.setSubnodeRecord() - ✅ Sets all ENS records (address + 10 text records) via batched Safe transactions
- ✅ Verifies all records are set correctly
- ✅ Handles gas estimation properly (fixed GS025 errors)
However, we believe the developer experience could be significantly improved if direct owners could use a controller-like interface for atomic batching.