Skip to content

Instantly share code, notes, and snippets.

@ArpitxGit
Last active June 23, 2022 06:42
Show Gist options
  • Save ArpitxGit/f244d282eed4cda445288d05bd4108ad to your computer and use it in GitHub Desktop.
Save ArpitxGit/f244d282eed4cda445288d05bd4108ad to your computer and use it in GitHub Desktop.
SoulBound/AccountBount Token
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.6;
/*
Compiliing reference implmentation of EIP4973
By accumulating all mentioned interfaces
And solving few errors
*/
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import {IERC721Metadata} from "./IERC721Metadata.sol";
import {IERC4973} from "./IERC4973.sol";
abstract contract EIP4973 is ERC165, IERC721Metadata, IERC4973 {
string private _name;
string private _symbol;
mapping(uint256 => address) private _owners;
mapping(uint256 => string) private _tokenURIs;
constructor(
string memory name_,
string memory symbol_
) {
_name = name_;
_symbol = symbol_;
}
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
return
interfaceId == type(IERC721Metadata).interfaceId ||
interfaceId == type(IERC4973).interfaceId ||
super.supportsInterface(interfaceId);
}
function name() public view virtual override returns (string memory) {
return _name;
}
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
require(_exists(tokenId), "tokenURI: token doesn't exist");
return _tokenURIs[tokenId];
}
function _exists(uint256 tokenId) internal view virtual returns (bool) {
return _owners[tokenId] != address(0);
}
function ownerOf(uint256 tokenId) override public view virtual returns (address) {
address owner = _owners[tokenId];
require(owner != address(0), "ownerOf: token doesn't exist");
return owner;
}
function _mint(
address to,
uint256 tokenId,
string calldata uri
) internal virtual returns (uint256) {
require(!_exists(tokenId), "mint: tokenID exists");
_owners[tokenId] = to;
_tokenURIs[tokenId] = uri;
emit Attest(to, tokenId);
return tokenId;
}
function _burn(uint256 tokenId) internal virtual {
address owner = ownerOf(tokenId);
delete _owners[tokenId];
delete _tokenURIs[tokenId];
emit Revoke(owner, tokenId);
}
}
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.6;
/// @title Account-bound tokens
/// @dev See https://eips.ethereum.org/EIPS/eip-4973
/// Note: the ERC-165 identifier for this interface is 0x6352211e.
interface IERC4973 /* is ERC165, ERC721Metadata */ {
/// @dev This emits when a new token is created and bound to an account by
/// any mechanism.
/// Note: For a reliable `_from` parameter, retrieve the transaction's
/// authenticated `from` field.
event Attest(address indexed _to, uint256 indexed _tokenId);
/// @dev This emits when an existing ABT is revoked from an account and
/// destroyed by any mechanism.
/// Note: For a reliable `_from` parameter, retrieve the transaction's
/// authenticated `from` field.
event Revoke(address indexed _to, uint256 indexed _tokenId);
/// @notice Find the address bound to an ERC4973 account-bound token
/// @dev ABTs assigned to zero address are considered invalid, and queries
/// about them do throw.
/// @param _tokenId The identifier for an ABT
/// @return The address of the owner bound to the ABT
function ownerOf(uint256 _tokenId) external view returns (address);
/// @notice Destroys `tokenId`. At any time, an ABT receiver must be able to
/// disassociate themselves from an ABT publicly through calling this
/// function.
/// @dev Must emit a `event Revoke` with the `address _to` field pointing to
/// the zero address.
/// @param _tokenId The identifier for an ABT
function burn(uint256 _tokenId) external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
pragma solidity ^0.8.0;
/**
* @title ERC-721 Non-Fungible Token Standard, optional metadata extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Metadata {
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
}
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;
import "./EIP4973.sol";
/*
As our contract EIP4973 was an abstract contract.
So we are creating a SoulBoundToken SBT contract using the abstract contract.
Abstract contracts are contracts that have at least one function without its implementation.
An instance of an abstract cannot be created.
Abstract contracts are used as base contracts so that the child contract can inherit and utilize its functions.
*/
contract SBT is EIP4973 {
//owner for implementation of ownable functionality of SBT
address public owner;
//for count the tokens
uint256 public count = 0;
constructor () EIP4973("SoulBoundToken", "SBT") {
//setting the owner in constructor
owner = msg.sender;
}
function burn(uint256 _tokenId) external override {
//what if receiver of the token tries to burn the token
require(ownerOf(_tokenId) == msg.sender || msg.sender == owner, "Only owner can revoke this token, not the receiver!!!");
_burn(_tokenId);
}
//to issue a token to a specific address
function issue(address _issuee, string calldata _uri) external onlyOwner {
_mint(_issuee, count, _uri);
count += 1;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not the Owner!!!");
_;
}
}

A SoulBound Token(SBT) or an AccountBound Token(ABT)

Is a token which belongs to a specific address or attached to your specific address.
You cannot transfer or sell it!
You can only revoke this token, or give it back to the account who issued it.

Concept of soulbound items, once picked up, cannot be transferred or sold.

Purpose of soulbound items, it keeps the interest and excitement.

POAP is an excellent example of an NFT that works better if it could be soulbound.

If someone is looking at your POAP, they are not interested in whether or not you paid someone who attended some event.
They are interested in whether or not you personally attended that event.

Uses:-

  1. It acts as a verification which is done off-chain.
    for ex: Certification, i can certify you for an accomplishment and take it back if there's an issue.
  2. A trophy, as transferability is not desired in everything.
  3. Reputation, let's say there are some kind of rewards or access for accounts with certain reputation, so here it's good. As no-one wants to transfer reputation.

Implementation:-

  1. An NFT which cannot be transferred and can just burnt in case.
    • Create an NFT only owner can mint and send it ot somebody else.
    • Only owner or holder can burn.
    • Holder cannot transfer token except burning it.
  2. EIP-4973: Account-bound Tokens still a draft.
    • interface IERC4973, two events(Attest, Revoke), two functions(ownerOf, burn)
    • Attest is emitted when a new token is created and bounded to ana ccount.
    • Revoke is emitted when a token is revoked/removed.
    • ownerOf returns the owner of a token with a specific token ID.
    • burn function revokes a specific token ID.
      And for the metadata, It is mentioned to refer to ERC721 metadata.
      And also there's a reference implementation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment