Skip to content

OnahProsperity/homeMadeERC721

Repository files navigation

Docs MIT License

📢 Version 0.0.1x. Please refer to the documentation for more details.

INTRODUCING Homemade ERC721, AN IMPROVED ERC721 IMPLEMENTATION

The goal of Homemade ERC721 is to provide an update on the implementation of IERC721 with significant gas savings for minting NFTs an added EIP721 Permit functionality. This project and implementation is likely going to be updated regularly and will continue to stay up to date with best practices.

Gas Savings

Onah Prosper created the improved version of ERC721 (Home Made ERC721) for a reason. EIP721 standard was created in 2018-01-24 and has not been updated for a while. There are some new improved functions like transfer(), permit(), permitForAll(), and setURI() that has been added to the Home Made ERC721. Special Thanks to Polygon Network as this won't have been done without them.

New Functionality

New Implementations

Custom Error in place of Require

Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures

revert("Insufficient funds.");
require(msg.sender != address(0), "here with a long strings to explain why");

but they are rather expensive, especially when it comes to deployment cost, and it is difficult to use dynamic information in them. Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries). More on Custom Error. So in Home Made ERC721, require statements are replaced with custom errors in other to save gas. See the percentage differences above.

Adding safe Transfer Function

Why do we need transfer function? Currently on EIP721, for instance, it is not possible for Alice to decide to transfer her NFT to Bob directly. Either this event has to be done before Bob can get the NFT from Alice.

  1. Alice uses the "approve()" function to approve Bob to be able to transfer the NFT from her and Bob has to trigger the "transferFrom()" method in other to transfer the NFT from Alice. This leads to spending of gas in multiple transaction. From what we have above, it will cost both Alice and Bob 134,747 gas fee.
  2. Second scenerio which is very difficult and cost more gas is using a smart contract as an intermidiary. So adding "safeTransfer()" function that only costs 59,543 instead of 134,747 saves about 44.19% gas fee.
function safeTransfer(
        address _to,
        uint256 tokenId
    ) external;

Saves from all the stress and funds. Home Made ERC721: safeTransfer() Transaction Hash

Adding Permit functionality

After Ethereum wallets like MetaMask implemented EIP721 standard for typed message signing that allows wallets to display data in signing prompts in a structured and readable format. EIP712 is a great step forward for security and usability because users will no longer need to sign off on inscrutable hexadecimal strings, which is a practice that can be confusing and insecure. Lots of Project have started implementing this logic. Popular Dai They have it implemented on their stable coin DAI Etherscan. Introducing EIP712 into Home Made ERC721 in order to allow for gasless transactions on open market. Instance: Bob signed a message and sends the signature to Alice. Alice spilts the signature in order to get the v,r,s and use it to approve herself in order to transfer NFT from Bob. A Note: might proceed to use ERC1271 due to that the release of ERC-4337 visit for more details.

function permit(
        address owner, 
        address operator, 
        uint256 tokenId,
        uint8 v, 
        bytes32 r, 
        bytes32 s
    ) external;

This permit works in a way to approve a single NFT from owner on recieving the signature and spliting it to get the v,r,s. Home Made ERC721: Permit() Transaction Hash

The other type of permit is:

Permit for All.

As the name implies, it allows unending transfer of NFT from owner in a way,

function permitForAll(
        address owner, 
        address operator, 
        bool allowed, 
        uint8 v, 
        bytes32 r, 
        bytes32 s
    ) external;

if Bob signs a message and sets "allowed" to be true, then it means unending transfer. If false, that will disapprove the operator (Alice) from future withdrawal. Home Made ERC721: PermitForAll() Transaction Hash

Base URL now setable

Token URL can now be setable.

function _setBaseURI(string memory _uri);

Introducing LibStorage

This contract could store a struct called libStorage at position keccak256("HOME MADE ERC721"); Find answer to what Library Storage here The struct contain all the state variables related to Home Made ERC721 functionality that the Home Made ERC721 contract reads and writes. There are a couple nice advantages to this:

  1. First that the Home Made ERC721 contract is reusable. The Home Made ERC721 contract can be deployed only once, and the deployed Home Made ERC721 contract can be used with multiple different contracts that use delegatecall with it and that are using different state variables.
  2. Another nice thing is that the Home Made ERC721 contract is not cluttered with state variable declarations of variables it doesn’t use.
library homeMadeMapped {
    struct libStorage {
        // Token name
    string _name;

    // Token symbol
    string _symbol;

    // base URI;
    string _baseURI;

    // --- EIP712 niceties ---
    bytes32 DOMAIN_SEPARATOR;

    // Mapping from token ID to owner address
    mapping(uint256 => address) _owners;

    // Mapping owner address to token count
    mapping(address => uint256) _balances;

    // Mapping from token ID to approved address
    mapping(uint256 => address) _tokenApprovals;

    // Mapping from owner to operator approvals
    mapping(address => mapping(address => bool)) _operatorApprovals;
    }

    // Note that different libraries will need to use different storage slots and so use a different keccak256 string. 
    // This is to prevent two or more libraries writing to the same locations in contract storage
    function diamondStorage() internal pure returns(libStorage storage ds) {
        bytes32 storagePosition = keccak256("HOME MADE ERC721");
        assembly {ds.slot := storagePosition}
    }
}

Deployment Link

Home Made ERC721 Contract Deployment

Usage

Once installed, you can use the contracts in the library by importing them:

pragma solidity ^0.8.4;

import "./HomeMadeERC721.sol"; // might consider NPM package

contract HMERC721 is HomeMadeERC721 {
    constructor() HomeMadeERC721("Home Made ERC721", "HMERC721") {}

    function mint(uint256 tokenId) external payable {
        _mint(msg.sender, tokenId);
    }
}

Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".

Don't forget to give the project a star! Thanks again!

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Running tests locally

  1. npm install
  2. npx hardhat compile
  3. npx hardhat test

Contact

Releases

No releases published

Packages

No packages published