Skip to content

Instantly share code, notes, and snippets.

@bergeron
Created October 4, 2023 15:21
Show Gist options
  • Save bergeron/8599eb33545b60bb32035df6fbeaf86e to your computer and use it in GitHub Desktop.
Save bergeron/8599eb33545b60bb32035df6fbeaf86e to your computer and use it in GitHub Desktop.
Smart contract to get token standard and details
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
contract GetTokenStandardAndDetails {
bytes4 private constant ERC721_INTERFACE = 0x80ac58cd;
bytes4 private constant ERC1155_INTERFACE = 0xd9b67a26;
string private constant ERC721 = "ERC721";
string private constant ERC1155 = "ERC1155";
string private constant ERC20 = "ERC20";
struct StandardAndDetails {
string standard;
string tokenURI;
string symbol;
string name;
uint8 decimals;
uint256 balance;
}
// Returns the contract standard and details for a token address
function getTokenStandardAndDetails(address tokenAddress) public view returns (StandardAndDetails memory) {
if (tokenAddress.code.length == 0 ) { revert("Address is an EOA, not a contract"); }
StandardAndDetails memory details = StandardAndDetails({
standard: "", symbol: "", name: "", tokenURI: "", decimals: 0, balance: 0
});
if (ERC165Checker.supportsInterface(tokenAddress, ERC721_INTERFACE)) {
details.standard = ERC721;
// The metadata extension is OPTIONAL for ERC721, so use try/catch
IERC721Metadata token = IERC721Metadata(tokenAddress);
try token.name() returns (string memory n) { details.name = n; } catch { }
try token.symbol() returns (string memory s) { details.symbol = s; } catch { }
} else if (ERC165Checker.supportsInterface(tokenAddress, ERC1155_INTERFACE)) {
details.standard = ERC1155;
// name() and symbol() aren't defined in ERC1155, so use try/catch
IERC1155WithNameAndSymbol token = IERC1155WithNameAndSymbol(tokenAddress);
try token.name() returns (string memory n) { details.name = n; } catch { }
try token.symbol() returns (string memory s) { details.symbol = s; } catch { }
} else {
IERC20Metadata token = IERC20Metadata(tokenAddress);
// ERC20 doesn't require ERC165 supportsInterface, so we'll determine based on
// symbol() and decimals() being defined, even though they're optional in ERC20.
try token.symbol() returns (string memory s) { details.symbol = s; }
catch { revert("Unable to determine contract standard"); }
try token.decimals() returns (uint8 d) { details.decimals = d; }
catch { revert("Unable to determine contract standard"); }
try token.name() returns (string memory n) { details.name = n; }
catch { }
details.standard = ERC20;
}
return details;
}
// Returns the contract standard and details for a token address and token id
function getTokenStandardAndDetails(address tokenAddress, uint256 tokenId) external view returns (StandardAndDetails memory) {
StandardAndDetails memory details = getTokenStandardAndDetails(tokenAddress);
if (equal(details.standard, ERC721)) {
// The metadata extension is OPTIONAL for ERC721
IERC721Metadata token = IERC721Metadata(tokenAddress);
try token.tokenURI(tokenId) returns (string memory u) { details.tokenURI = u; }
catch { }
} else if (equal(details.standard, ERC1155)) {
// The metadata extension is OPTIONAL for ERC1155
IERC1155MetadataURI token = IERC1155MetadataURI(tokenAddress);
try token.uri(tokenId) returns (string memory u) { details.tokenURI = u; }
catch { }
}
return details;
}
// Returns the contract standard and details for a token address and user address
function getTokenStandardAndDetails(address tokenAddress, address userAddress) external view returns (StandardAndDetails memory) {
StandardAndDetails memory details = getTokenStandardAndDetails(tokenAddress);
if (equal(details.standard, ERC20)) {
details.balance = IERC20Metadata(tokenAddress).balanceOf(userAddress);
}
return details;
}
function equal(string memory a, string memory b) internal pure returns (bool) {
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
}
}
// name() and symbol() aren't defined in ERC1155 but sometimes they're implemented anyway
interface IERC1155WithNameAndSymbol is IERC1155MetadataURI {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment