Skip to content

Instantly share code, notes, and snippets.

@qruz-hq
Created December 19, 2022 17:55
Show Gist options
  • Save qruz-hq/d5465d7b31358b842c19030488adeeb5 to your computer and use it in GitHub Desktop.
Save qruz-hq/d5465d7b31358b842c19030488adeeb5 to your computer and use it in GitHub Desktop.
An extension of the ERC721 interface to allow the creation of authentication gated burnable Soulbound tokens. The type of authentication gives the burn permission to either the Issuer, Owner or both parties.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import '@openzeppelin/contracts/token/ERC721/ERC721.sol';
import '@openzeppelin/contracts/token/ERC721/IERC721.sol';
import './interfaces/IERC721Soulbound.sol';
/// @title ERC721Soulbound
/// @author Gui "Qruz" Rodrigues
/// @notice An ERC-721 Soulbound contract that disables any transfers except from and to the null address (0x0) allowing minting and burning of tokens
/// @dev IERC721Soulbound is a custom made interface according to the EIP-5484 proposal (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-5484.md)
abstract contract ERC721Soulbound is ERC721, IERC721Soulbound {
address issuer;
mapping(uint256 => BurnAuth) _burnAuth;
constructor(address _issuer) {
issuer = _issuer;
}
function setIssuer(address _issuer) external {
require(_issuer != issuer, 'ERC721Soulbound: Issuer is the same');
issuer = _issuer;
}
function _setBurnAuth(uint256 _tokenId, BurnAuth _auth) private {
require(
_exists(_tokenId),
'ERC721Soulbound: BurnAuth set of nonexistent token'
);
_burnAuth[_tokenId] = _auth;
}
function burnAuth(uint256 tokenId) external view virtual returns (BurnAuth) {
_requireMinted(tokenId);
return _burnAuth[tokenId];
}
function _soulbind(
address to,
uint256 tokenId,
BurnAuth auth
) internal virtual {
_mint(to, tokenId);
_setBurnAuth(tokenId, auth);
emit Issued(issuer, to, tokenId, auth);
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(
bytes4 interfaceId
) public view virtual override(IERC165, ERC721) returns (bool) {
return
interfaceId == type(IERC721Soulbound).interfaceId ||
super.supportsInterface(interfaceId);
}
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override {
if (from != address(0)) {
if (to != address(0)) {
// If this transfer is not a burn transaction
revert('ERC721Soulbound: Soulbound tokens cannot be transfered');
}
BurnAuth auth = _burnAuth[tokenId];
if (auth == BurnAuth.IssuerOnly) {
if (msg.sender != issuer) {
revert('ERC721Soulbound: IssuerOnly Transfers');
}
} else if (auth == BurnAuth.OwnerOnly) {
if (msg.sender != ownerOf(tokenId)) {
revert('ERC721Soulbound: OwnerOnly Transfers');
}
} else if (auth == BurnAuth.Neither) {
revert('ERC721Soulbound: Neither Transfers');
}
}
super._beforeTokenTransfer(from, to, tokenId);
}
}
interface IERC721Soulbound is IERC721 {
/// A guideline to standardlize burn-authorization's number coding
enum BurnAuth {
IssuerOnly,
OwnerOnly,
Both,
Neither
}
/// @notice Emitted when a soulbound token is issued.
/// @dev This emit is an add-on to nft's transfer emit in order to distinguish sbt
/// from vanilla nft while providing backward compatibility.
/// @param from The issuer
/// @param to The receiver
/// @param tokenId The id of the issued token
event Issued (
address indexed from,
address indexed to,
uint256 indexed tokenId,
BurnAuth burnAuth
);
/// @notice provides burn authorization of the token id.
/// @dev unassigned tokenIds are invalid, and queries do throw
/// @param tokenId The identifier for a token.
function burnAuth(uint256 tokenId) external view returns (BurnAuth);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment