Sharing some stuff about ERC721A and ERC721R and how to implement them in your smart contract.
ERC721 is a standard for representing ownership of non-fungible tokens, that is, where each token is unique. More info on this can be found online and out of scope for this post.
This is an extension to the ERC721 standard. The main idea here is to use counters instead of enumerables and save significantly on gas (for bulk mints).
From their website,
ERC721A is an improved implementation of the IERC721 standard that supports minting multiple tokens for close to the cost of one.
On a high level, ERC721A saves gas on batch mints by having less writes to the blockchain.
By updating owner balances once instead of multiple times with batch minting.
By updating owner data once instead of per minted NFT.
Let’s look at how to use ERC721A in your smart contract
npm install --save-dev erc721a
import "erc721a/contracts/ERC721A.sol";
contract CoolNFT is ERC721A {
constructor() ERC721A("Your NFT project", "PROJ") {}
function mint(uint256 quantity) external payable {
// _safeMint's second argument now takes in a quantity, not a tokenId.
_safeMint(msg.sender, quantity);
}
}
The main difference between a standard ERC721 implementation and this one is the _safeMint function. In ERC721A, the second parameter is the quantity and not the tokenID.
Recent update from the team behind ERC721A - v4.0.0 no longer has the OpenZeppelin dependency. More info on it here
The main idea here is “refundability” - The ability for the seller/owner to refund the amount paid by the buyer.
From their website,
ERC721R adds trustless refunds to NFT smart contracts allowing minters to return the NFTs minted at cost within a given refund period.
There are some good benefits. For the project owner, it helps protect the floor price for the collection. And most importantly for the community, gives more sense of accountability. It can also help reduce rug pulls (Note: only to an extent. There are always ways to bypass)
Let’s look at how to implement the refund logic in your smart contract
uint256 public constant refundPeriod = 30 days;
uint256 public refundEndTime;
address public refundAddress;
constructor() ERC721A("ERC721RExample", "ERC721R") {
refundAddress = msg.sender;
toggleRefundCountdown();
}
function isRefundGuaranteeActive() public view returns (bool) {
return (block.timestamp <= refundEndTime);
}
function getRefundGuaranteeEndTime() public view returns (uint256) {
return refundEndTime;
}
function refund(uint256[] calldata tokenIds) external {
require(isRefundGuaranteeActive(), "Refund expired");
for (uint256 i = 0; i < tokenIds.length; i++) {
uint256 tokenId = tokenIds[i];
require(msg.sender == ownerOf(tokenId), "Not token owner");
transferFrom(msg.sender, refundAddress, tokenId);
}
uint256 refundAmount = tokenIds.length * mintPrice;
Address.sendValue(payable(msg.sender), refundAmount);
}
function toggleRefundCountdown() public onlyOwner {
refundEndTime = block.timestamp + refundPeriod;
}
function setRefundAddress(address _refundAddress) external onlyOwner {
refundAddress = _refundAddress;
}
These different ideas have a bunch of pros and cons. For example ERC721A contracts cost more for transfers because the transferFrom and safeTransferFrom transactions are more expensive. Similarly does ERC721R really protect buyers from a rug pull? The seller can simply choose to wait out the refund time period and then disappear. So basic rule of thumb is to always DYOR (do your own research) but hope these insights help get a better idea.
What are your thoughts on these different strategies? Let me know what you think. Also I will be X-posting this content on my Build3 newsletter. Subscribe if you like content like this.
Fin

