// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)pragmasolidity ^0.8.20;/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/abstractcontractContext{
function_msgSender() internalviewvirtualreturns (address) {
returnmsg.sender;
}
function_msgData() internalviewvirtualreturns (bytescalldata) {
returnmsg.data;
}
function_contextSuffixLength() internalviewvirtualreturns (uint256) {
return0;
}
}
Contract Source Code
File 2 of 12: ENSRent.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;import { ERC721Holder } from"@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import { ERC1155Holder } from"@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import { Ownable } from"@openzeppelin/contracts/access/Ownable.sol";
import { IBaseRegistrar } from"./interfaces/IBaseRegistrar.sol";
import { INameWrapper } from"./interfaces/INameWrapper.sol";
import { IENSRent } from"./interfaces/IENSRent.sol";
/**
* @title ENSRent
* @author Alex Netto (@alextnetto)
* @author Lucas Picollo (@pikonha)
* @notice ENS domain rental contract with a fixed price per second
* @dev Implements rental functionality for both wrapped (ERC1155) and unwrapped (ERC721) ENS names
*/contractENSRentisIENSRent, ERC721Holder, ERC1155Holder, Ownable{
/**
* @notice Fee percentage taken from rental payments
*/uint256public feeBasisPoints;
/**
* @notice The ENS base registrar contract for managing .eth domains
* @dev Used for handling ERC721 transfers and domain management
*/
IBaseRegistrar publicimmutable baseRegistrar;
/**
* @notice The ENS name wrapper contract for handling wrapped domains
* @dev Used for ERC1155 transfers and wrapped domain management
*/
INameWrapper publicimmutable nameWrapper;
/**
* @notice Storage structure for domain rental information
* @param lender Owner of the domain
* @param minPricePerSecond Floor price set by owner
* @param maxEndTimestamp Latest possible rental end date
* @param currentBorrower Address currently renting the domain
* @param rentalEnd Current rental end timestamp (or auction start if never rented)
* @param nameNode Namehash of the ENS domain
* @param tokenId ERC721 token ID of the domain
*/structRentalTerms {
address lender;
uint256 minPricePerSecond;
uint256 maxEndTimestamp;
address currentBorrower;
uint256 rentalEnd;
bytes32 nameNode;
uint256 tokenId;
}
/**
* @notice Maps domain token IDs to their rental terms
* @dev Primary storage for rental information
*/mapping(uint256=> RentalTerms) public rentalTerms;
/**
* @notice Initialize the rental contract
* @param _nameWrapper Address of ENS NameWrapper contract
* @param _baseRegistrarAddress Address of ENS BaseRegistrar contract
* @param _feeBasisPoints Fee percentage taken from rentals (100 = 1%)
* @dev Sets up immutable contract references
*/constructor(address _nameWrapper,
address _baseRegistrarAddress,
uint256 _feeBasisPoints,
address _owner
)
Ownable(_owner)
{
nameWrapper = INameWrapper(_nameWrapper);
baseRegistrar = IBaseRegistrar(_baseRegistrarAddress);
_setFeeBasisPoints(_feeBasisPoints);
}
/**
* @notice List domain for rent and start initial auction
* @param tokenId Domain's ERC721 token ID
* @param minPricePerSecond Minimum rental price per second
* @param maxEndTimestamp Latest possible rental end time
* @param nameNode Domain's namehash
* @dev Handles both wrapped and unwrapped domains
*/functionlistDomain(uint256 tokenId,
uint256 minPricePerSecond,
uint256 maxEndTimestamp,
bytes32 nameNode,
stringcalldata name
)
external{
// Validate listing parametersif (minPricePerSecond ==0) revert ZeroPriceNotAllowed();
if (maxEndTimestamp <=block.timestamp) revert MaxEndTimeMustBeFuture();
if (maxEndTimestamp >= baseRegistrar.nameExpires(tokenId)) revert MaxEndTimeExceedsExpiry();
// Handle domain transfer based on wrapper statusif (baseRegistrar.ownerOf(tokenId) ==address(nameWrapper)) {
// For wrapped domains: transfer ERC1155 and unwrap
nameWrapper.safeTransferFrom(msg.sender, address(this), uint256(nameNode), 1, "");
nameWrapper.unwrapETH2LD(bytes32(tokenId), address(this), address(this));
} else {
// For unwrapped domains: transfer ERC721 and claim ownership
baseRegistrar.safeTransferFrom(msg.sender, address(this), tokenId);
baseRegistrar.reclaim(tokenId, address(this));
}
// Store rental terms and start auction
rentalTerms[tokenId] = RentalTerms({
lender: msg.sender,
minPricePerSecond: minPricePerSecond,
maxEndTimestamp: maxEndTimestamp,
currentBorrower: address(0),
rentalEnd: block.timestamp, // Start first auction immediately
nameNode: nameNode,
tokenId: tokenId
});
emit DomainListed(name, tokenId, msg.sender, minPricePerSecond, maxEndTimestamp, nameNode);
}
/**
* @notice Rent domain at current auction price
* @param tokenId Domain's ERC721 token ID
* @param desiredEndTimestamp Requested rental end time
* @dev Handles both initial auction and post-rental auctions
*/functionrentDomain(uint256 tokenId, uint256 desiredEndTimestamp) externalpayable{
RentalTerms storage terms = rentalTerms[tokenId];
// Validate rental requestif (terms.lender ==address(0)) revert DomainNotListed();
if (desiredEndTimestamp > terms.maxEndTimestamp) revert ExceedsMaxEndTime();
if (desiredEndTimestamp <=block.timestamp) revert EndTimeMustBeFuture();
if (terms.currentBorrower !=address(0)) {
if (block.timestamp< terms.rentalEnd) revert DomainCurrentlyRented();
}
uint256 duration = desiredEndTimestamp -block.timestamp;
uint256 totalPrice = terms.minPricePerSecond * duration;
// Verify paymentif (msg.value< totalPrice) revert InsufficientPayment();
// Calculate feeuint256 fee = (totalPrice * feeBasisPoints) /10_000;
uint256 lenderPayment = totalPrice - fee;
// Transfer domain control
baseRegistrar.reclaim(tokenId, msg.sender);
// Update rental terms
terms.currentBorrower =msg.sender;
terms.rentalEnd = desiredEndTimestamp;
// Transfer payment to domain owner
(bool sent,) =payable(terms.lender).call{ value: lenderPayment }("");
if (!sent) revert EtherTransferFailed();
// Refund excess paymentif (msg.value> totalPrice) {
(bool refundSent,) =payable(msg.sender).call{ value: msg.value- totalPrice }("");
if (!refundSent) revert EtherTransferFailed();
}
emit DomainRented(tokenId, msg.sender, desiredEndTimestamp, totalPrice, terms.minPricePerSecond);
}
/**
* @notice Returns the ownership to the Rent contract to be rented again
* @param tokenId Domain's ERC721 token ID
*/functionhandleRentalEnd(uint256 tokenId) external{
RentalTerms storage terms = rentalTerms[tokenId];
if (terms.lender ==address(0) ||block.timestamp> terms.maxEndTimestamp) revert DomainNotListed();
if (block.timestamp< terms.rentalEnd) revert DomainCurrentlyRented();
baseRegistrar.reclaim(tokenId, address(this));
terms.currentBorrower =address(0);
terms.rentalEnd =0;
}
/**
* @notice Allow owner to reclaim domain after rental
* @param tokenId Domain's ERC721 token ID
* @dev Can only be called after rental period ends
*/functionreclaimDomain(uint256 tokenId) external{
RentalTerms storage terms = rentalTerms[tokenId];
// Validate reclaim requestif (msg.sender!= terms.lender) revert NotLender();
if (block.timestamp< terms.rentalEnd) revert DomainCurrentlyRented();
// Return domain control
baseRegistrar.reclaim(terms.tokenId, terms.lender);
baseRegistrar.safeTransferFrom(address(this), terms.lender, tokenId);
// Clean up storagedelete rentalTerms[tokenId];
emit DomainReclaimed(tokenId, terms.lender);
}
/**
* @notice Update the fee percentage taken from rentals
* @param _feeBasisPoints New fee in basis points
* @dev Can only be called by contract owner
*/functionsetFeeBasisPoints(uint256 _feeBasisPoints) externalonlyOwner{
_setFeeBasisPoints(_feeBasisPoints);
}
/**
* @notice Internal function to validate and set fee basis points
* @param _feeBasisPoints New fee in basis points
*/function_setFeeBasisPoints(uint256 _feeBasisPoints) private{
require(_feeBasisPoints <=1000, "Fee cannot exceed 10%");
feeBasisPoints = _feeBasisPoints;
}
receive() externalpayable{ }
functionwithdraw() externalonlyOwner{
(bool sent,) =payable(owner()).call{ value: address(this).balance }("");
if (!sent) revert EtherTransferFailed();
}
}
Contract Source Code
File 3 of 12: ERC1155Holder.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/utils/ERC1155Holder.sol)pragmasolidity ^0.8.20;import {IERC165, ERC165} from"../../../utils/introspection/ERC165.sol";
import {IERC1155Receiver} from"../IERC1155Receiver.sol";
/**
* @dev Simple implementation of `IERC1155Receiver` that will allow a contract to hold ERC-1155 tokens.
*
* IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be
* stuck.
*/abstractcontractERC1155HolderisERC165, IERC1155Receiver{
/**
* @dev See {IERC165-supportsInterface}.
*/functionsupportsInterface(bytes4 interfaceId) publicviewvirtualoverride(ERC165, IERC165) returns (bool) {
return interfaceId ==type(IERC1155Receiver).interfaceId||super.supportsInterface(interfaceId);
}
functiononERC1155Received(address,
address,
uint256,
uint256,
bytesmemory) publicvirtualoverridereturns (bytes4) {
returnthis.onERC1155Received.selector;
}
functiononERC1155BatchReceived(address,
address,
uint256[] memory,
uint256[] memory,
bytesmemory) publicvirtualoverridereturns (bytes4) {
returnthis.onERC1155BatchReceived.selector;
}
}
Contract Source Code
File 4 of 12: ERC165.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol)pragmasolidity ^0.8.20;import {IERC165} from"./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*/abstractcontractERC165isIERC165{
/**
* @dev See {IERC165-supportsInterface}.
*/functionsupportsInterface(bytes4 interfaceId) publicviewvirtualreturns (bool) {
return interfaceId ==type(IERC165).interfaceId;
}
}
Contract Source Code
File 5 of 12: ERC721Holder.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/utils/ERC721Holder.sol)pragmasolidity ^0.8.20;import {IERC721Receiver} from"../IERC721Receiver.sol";
/**
* @dev Implementation of the {IERC721Receiver} interface.
*
* Accepts all token transfers.
* Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or
* {IERC721-setApprovalForAll}.
*/abstractcontractERC721HolderisIERC721Receiver{
/**
* @dev See {IERC721Receiver-onERC721Received}.
*
* Always returns `IERC721Receiver.onERC721Received.selector`.
*/functiononERC721Received(address, address, uint256, bytesmemory) publicvirtualreturns (bytes4) {
returnthis.onERC721Received.selector;
}
}
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;interfaceIENSRent{
//// EVENTS /////**
* @notice Emitted when a domain is listed for rent
* @param tokenId Domain's ERC721 token ID
* @param lender Domain owner's address
* @param minPricePerSecond Floor price for rentals
* @param maxEndTimestamp Latest allowed rental end time
* @param nameNode Domain's namehash
* @dev Triggered in listDomain function when domain is first listed
*/eventDomainListed(string name,
uint256indexed tokenId,
addressindexed lender,
uint256 minPricePerSecond,
uint256 maxEndTimestamp,
bytes32 nameNode
);
/**
* @notice Emitted when a domain is rented
* @param tokenId Domain's ERC721 token ID
* @param borrower Renter's address
* @param rentalEnd Rental end timestamp
* @param totalPrice Total price paid for rental
* @param pricePerSecond Rate paid per second
* @dev Includes actual price paid from Dutch auction
*/eventDomainRented(uint256indexed tokenId, addressindexed borrower, uint256 rentalEnd, uint256 totalPrice, uint256 pricePerSecond
);
/**
* @notice Emitted when owner reclaims their domain
* @param tokenId Domain's ERC721 token ID
* @param lender Owner's address
* @dev Only emitted after rental period ends
*/eventDomainReclaimed(uint256indexed tokenId, addressindexed lender);
//// ERRORS /////**
* @notice Minimum price must be greater than zero
* @dev Thrown in listDomain when minPricePerSecond = 0
*/errorZeroPriceNotAllowed();
/**
* @notice Maximum rental end time must be future timestamp
* @dev Thrown when maxEndTimestamp <= current time
*/errorMaxEndTimeMustBeFuture();
/**
* @notice Maximum end time cannot exceed domain expiry
* @dev Ensures domain doesn't expire during rental period
*/errorMaxEndTimeExceedsExpiry();
/**
* @notice Domain must be listed before renting
* @dev Thrown when attempting to rent unlisted domain
*/errorDomainNotListed();
/**
* @notice Rental end time exceeds maximum allowed
* @dev Enforces owner-set maximum rental duration
*/errorExceedsMaxEndTime();
/**
* @notice Rental end time must be in the future
* @dev Basic timestamp validation
*/errorEndTimeMustBeFuture();
/**
* @notice Cannot rent domain during active rental
* @dev Prevents overlapping rentals
*/errorDomainCurrentlyRented();
/**
* @notice Payment must cover calculated rental cost
* @dev Ensures sufficient payment for desired duration
*/errorInsufficientPayment();
/**
* @notice ETH transfer failed
* @dev Safety check for ETH transfers
*/errorEtherTransferFailed();
/**
* @notice Only domain owner can perform action
* @dev Access control for owner operations
*/errorNotLender();
}
Contract Source Code
File 8 of 12: IERC1155Receiver.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/IERC1155Receiver.sol)pragmasolidity ^0.8.20;import {IERC165} from"../../utils/introspection/IERC165.sol";
/**
* @dev Interface that must be implemented by smart contracts in order to receive
* ERC-1155 token transfers.
*/interfaceIERC1155ReceiverisIERC165{
/**
* @dev Handles the receipt of a single ERC-1155 token type. This function is
* called at the end of a `safeTransferFrom` after the balance has been updated.
*
* NOTE: To accept the transfer, this must return
* `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
* (i.e. 0xf23a6e61, or its own function selector).
*
* @param operator The address which initiated the transfer (i.e. msg.sender)
* @param from The address which previously owned the token
* @param id The ID of the token being transferred
* @param value The amount of tokens being transferred
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
*/functiononERC1155Received(address operator,
addressfrom,
uint256 id,
uint256 value,
bytescalldata data
) externalreturns (bytes4);
/**
* @dev Handles the receipt of a multiple ERC-1155 token types. This function
* is called at the end of a `safeBatchTransferFrom` after the balances have
* been updated.
*
* NOTE: To accept the transfer(s), this must return
* `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
* (i.e. 0xbc197c81, or its own function selector).
*
* @param operator The address which initiated the batch transfer (i.e. msg.sender)
* @param from The address which previously owned the token
* @param ids An array containing ids of each token being transferred (order and length must match values array)
* @param values An array containing amounts of each token being transferred (order and length must match ids array)
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
*/functiononERC1155BatchReceived(address operator,
addressfrom,
uint256[] calldata ids,
uint256[] calldata values,
bytescalldata data
) externalreturns (bytes4);
}
Contract Source Code
File 9 of 12: IERC165.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)pragmasolidity ^0.8.20;/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/interfaceIERC165{
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/functionsupportsInterface(bytes4 interfaceId) externalviewreturns (bool);
}
Contract Source Code
File 10 of 12: IERC721Receiver.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721Receiver.sol)pragmasolidity ^0.8.20;/**
* @title ERC-721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC-721 asset contracts.
*/interfaceIERC721Receiver{
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be
* reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/functiononERC721Received(address operator,
addressfrom,
uint256 tokenId,
bytescalldata data
) externalreturns (bytes4);
}
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)pragmasolidity ^0.8.20;import {Context} from"../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/abstractcontractOwnableisContext{
addressprivate _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/errorOwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/errorOwnableInvalidOwner(address owner);
eventOwnershipTransferred(addressindexed previousOwner, addressindexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/constructor(address initialOwner) {
if (initialOwner ==address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/modifieronlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/functionowner() publicviewvirtualreturns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/function_checkOwner() internalviewvirtual{
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/functionrenounceOwnership() publicvirtualonlyOwner{
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/functiontransferOwnership(address newOwner) publicvirtualonlyOwner{
if (newOwner ==address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/function_transferOwnership(address newOwner) internalvirtual{
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}