Developer Guide
Name Wrapper

Name Wrapper

The Name Wrapper allows you to "wrap" any .vet name into an ERC-1155 NFT.

Without the Name Wrapper, .vet names were only associated with ERC-721 NFTs, unless the owner created a separate custom contract.

Unwrapped .vet names distinguish between a separate Owner (Registrant) and Manager (Controller).

This distinction is eliminated once you wrap the name, as a single account then serves as both the Owner and Manager for the wrapped name.

While the functionality for the Name Wrapper is fully available, the user interface of vet.domains does not yet provide full functionality. It is also noteworthy that, as of now, no marketplace in Vechain supports trading for the ERC-1155 NFTs issued by the Name Wrapper.

Wrapping and Unwrapping

When wrapping a .vet name, you transfer the Owner (Registrant) of the ERC-721 NFT to the Name Wrapper contract.

The contract will then automatically take over the Manager (Controller) for the name as well.

You can do this by calling the wrapETH2LD method. Or, you can directly transfer the ERC-721 NFT to the Name Wrapper contract. In return, the contract issues you an ERC-1155 NFT.

NameWrapper.wrapETH2LD(string label, address wrappedOwner, uint16 ownerControlledFuses, address resolver)
 
// For example
wrapETH2LD(
    "hello", // "hello.vet" but only the label
    0x1234..., // The address you want to own the wrapped name
    0, // The owner-controlled fuse bits OR'd together, that you want to burn
    0x1234... // The address of the resolver you want to use
)

When wrapping any other ENS name, you transfer the Manager (Controller) of the name to the Name Wrapper contract. You can do this by calling the wrap method. In return, the contract issues you an ERC-1155 NFT.

NameWrapper.wrap(bytes name, address wrappedOwner, address resolver)
 
// For example
wrapETH2LD(
    0x03737562046e616d650365746800, // The DNS-encoded version of "sub.hello.vet" ('0x' + require('dns-packet').name.encode('sub.hellovet').toString('hex'))
    0x1234..., // The address you want to own the wrapped name
    0x1234... // The address of the resolver you want to use
)

As the owner of the wrapped name, you can unwrap at any time by calling either unwrapETH2LD or unwrap. You can do this as long as the permission to unwrap has not been revoked.

NameWrapper.unwrapETH2LD(bytes32 labelhash, address registrant, address controller)
 
// For example
unwrapETH2LD(
    0x952f..., // "hello.vet" but only the labelhash: keccak256('hello')
    0x1234..., // The address you want to own the unwrapped name
    0x1234... // The address you want to be the manager of the unwrapped name
)
 
NameWrapper.unwrap(bytes32 parentNode, bytes32 labelhash, address controller)
 
// For example
unwrap(
    0x6cbc..., // The namehash of the parent node, e.g. "hello.vet"
    0xfa1e..., // The labelhash of the child to unwrap, e.g. keccak256('sub')
    0x1234... // The address you want to be the manager of the unwrapped name
)

Burning Fuses / Setting Expiry

If you are wrapping an existing .vet name, then you can pass in the owner-controlled fuses at that time, see the above "Wrapping and Unwrapping" section. If you are creating a new subname, and you want to burn fuses at the same time, see the below "Creating Subnames" section.

For other existing wrapped names, you can burn fuses with either the setFuses or setChildFuses methods.

The setFuses method is used for a name that you own, but you do not necessarily own the parent of. You have the ability to burn any Owner-Controlled Fuses you want. Note that your name must first be Emancipated in order for you to be able to burn any owner-controlled fuses. All .vet names are automatically emancipated upon wrapping.

When burning owner-controlled fuses, at a minimum you must burn the CANNOT_UNWRAP fuse (if it has not already been burned).

NameWrapper.setFuses(bytes32 node, uint16 ownerControlledFuses)
 
// For example
setFuses(
    0x6cbc..., // The namehash of the node, e.g. "hello.vet"
    1 // The owner-controlled fuse bits OR'd together, that you want to burn
)

The setChildFuses method is used for a subname that you own the parent of. As long as the subname has not yet been Emancipated, you can burn whatever Parent-Controlled Fuses and Owner-Controlled Fuses you want. At the same time, you must set an expiry for those fuses, if one is not already set. Note that your name must first be Locked in order for you to burn fuses on any subnames.

If you are only burning parent-controlled fuses, then there are no further restrictions. However, if you are burning owner-controlled fuses, then you must at a minimum burn both PARENT_CANNOT_CONTROL and CANNOT_UNWRAP on the subname to lock it at the same time.

NameWrapper.setChildFuses(bytes32 parentNode, bytes32 labelhash, uint32 fuses, uint64 expiry)
 
// For example
setChildFuses(
    0x6cbc..., // The namehash of the parent node, e.g. "hello.vet"
    0xfa1e..., // The labelhash of the child, e.g. keccak256('sub')
    65537, // The fuse bits OR'd together, that you want to burn
    2021232060 // The expiry for the subname
)

Creating Subnames

This is done very similarly to how unwrapped subnames are created. You call either setSubnodeOwner or setSubnodeRecord on the wrapper contract. When a name is wrapped, all subnames created will also be wrapped by default.

You can also pass in the fuses and expiry at the same time, so that the subname will be created in the fuse/permission state that you want, without needing to perform an extra transaction.

NameWrapper.setSubnodeOwner(bytes32 parentNode, string label, address owner, uint32 fuses, uint64 expiry)
 
// For example
setSubnodeOwner(
    0x6cbc..., // The namehash of the parent node, e.g. "hello.vet"
    "sub", // The label of the subname to create
    0x1234..., // The address you want to be the owner of the new subname
    65536, // The fuse bits OR'd together, that you want to burn
    2021232060 // The expiry for the subname
)
 
function setSubnodeRecord(bytes32 parentNode, string label, address owner, address resolver, uint64 ttl, uint32 fuses, uint64 expiry)
 
setSubnodeRecord(
    0x6cbc..., // The namehash of the parent node, e.g. "hello.vet"
    "sub", // The label of the subname to create
    0x1234..., // The address you want to be the owner of the new subname
    0x5678..., // The address of the resolver to set for the new subname
    0, // The TTL to set for the new subname
    65536, // The fuse bits OR'd together, that you want to burn
    2021232060 // The expiry for the subname
)

Approved Operators

Full-Control Operator Batch Approvals

Your wrapped name is an ERC-1155 NFT that supports the setApprovalForAll method. When you approve an address using this method, it will have full control over all wrapped ENS names that you own.

This method is typically used by NFT marketplace contracts.

Name-Specific Subname Renewal Manager Approvals

The Name Wrapper also supports the ERC-721 approve method. This method is used to approve a single "Subname Renewal Manager" for a specific name.

The "Renewal Manager" does not have full control over your wrapped name, it can only set / extend the expiry on subnames.

Further, if you burn the CANNOT_APPROVE fuse on your name, then the approved renewal manager can no longer be changed. You can use this to "lock in" that contract, so that you can guarantee to all subname owners that renewals/extensions can always be done.

This approved renewal manager will be reset if the wrapped NFT is burned or re-minted, which happens if you unwrap the name, or if an expired name gets re-registered. It will also be reset if the wrapped NFT is transferred, unless the CANNOT_APPROVE fuse is burned.

Example - Subname Registrar Contract

You can use these operator approval methods to setup a separate contract that can take certain actions on your behalf. One example is setting up a "subname registrar" to allow users to register/renew subnames.

That subname registrar contract would act on your behalf and allow users to register subnames. To allow this, you would call setApprovalForAll to give that contract full control over your name (and thus the ability to create subnames).

Then, to enable "unruggable renewals", you could call approve on that same contract (or a separate one specific to renewals if you wish) and burn CANNOT_APPROVE to lock in subname renewals for that contract.

If you need to later on, you would still be able to revoke with setApprovalForAll. So the contract would lose full control over your name (and the ability to create new subnames), but it would still be able to perpetually renew/extend existing subnames.

And you can do all of this without needing to send your wrapped NFT to that contract.

Interface

pragma solidity >=0.8.4;
 
interface INameWrapper {
    // Events
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);
    event ExpiryExtended(bytes32 indexed node, uint64 expiry);
    event FusesSet(bytes32 indexed node, uint32 fuses);
    event NameUnwrapped(bytes32 indexed node, address owner);
    event NameWrapped(bytes32 indexed node, bytes name, address owner, uint32 fuses, uint64 expiry);
    event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values);
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
    event URI(string value, uint256 indexed id);
 
    // Functions
    function allFusesBurned(bytes32 node, uint32 fuseMask) external view returns (bool);
    function approve(address to, uint256 tokenId) external;
    function balanceOf(address account, uint256 id) external view returns (uint256);
    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory);
    function canModifyName(bytes32 node, address addr) external view returns (bool);
    function ens() external view returns (address);
    function extendExpiry(bytes32 node, bytes32 labelhash, uint64 expiry) external returns (uint64);
    function getApproved(uint256 tokenId) external view returns (address);
    function getData(uint256 id) external view returns (address, uint32, uint64);
    function isApprovedForAll(address account, address operator) external view returns (bool);
    function isWrapped(bytes32 node) external view returns (bool);
    function metadataService() external view returns (address);
    function name() external view returns (string memory);
    function names(bytes32) external view returns (bytes memory);
    function ownerOf(uint256 id) external view returns (address);
    function registerAndWrapETH2LD(string calldata label, address wrappedOwner, uint256 duration, address resolver, uint16 ownerControlledFuses) external returns (uint256);
    function registrar() external view returns (address);
    function renew(uint256 labelHash, uint256 duration) external returns (uint256);
    function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external;
    function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
    function setApprovalForAll(address operator, bool approved) external;
    function setChildFuses(bytes32 parentNode, bytes32 labelhash, uint32 fuses, uint64 expiry) external;
    function setFuses(bytes32 node, uint16 ownerControlledFuses) external returns (uint32);
    function setMetadataService(address _metadataService) external;
    function setRecord(bytes32 node, address owner, address resolver, uint64 ttl) external;
    function setResolver(bytes32 node, address resolver) external;
    function setSubnodeOwner(bytes32 node, string calldata label, address newOwner, uint32 fuses, uint64 expiry) external returns (bytes32);
    function setSubnodeRecord(bytes32 node, string calldata label, address owner, address resolver, uint64 ttl, uint32 fuses, uint64 expiry) external returns (bytes32);
    function setTTL(bytes32 node, uint64 ttl) external;
    function setUpgradeContract(address _upgradeAddress) external;
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
    function unwrap(bytes32 node, bytes32 label, address owner) external;
    function unwrapETH2LD(bytes32 label, address newRegistrant, address newController) external;
    function upgrade(bytes calldata name, bytes calldata extraData) external;
    function upgradeContract() external view returns (address);
    function uri(uint256 tokenId) external view returns (string memory);
    function wrap(bytes calldata name, address wrappedOwner, address resolver) external;
    function wrapETH2LD(string calldata label, address wrappedOwner, uint16 ownerControlledFuses, address resolver) external returns (uint64);
}