Last active
July 27, 2021 21:28
-
-
Save wchargin/3d50a922febe5b290f0a268a6563c70f to your computer and use it in GitHub Desktop.
SVG token with ownership and cosmetics embedded in token ID (100% untested)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: GPL-v2-only | |
pragma solidity ^0.8.0; | |
// 160 bits for address; 96 bits for payload, split into 64 bits of | |
// cosmetics and 32 bits of nonce. | |
struct TokenId { | |
address owner; | |
Cosmetics cosmetics; | |
uint32 nonce; | |
} | |
struct Cosmetics { | |
uint32 rgbaColor; | |
uint16 width; | |
uint16 height; | |
} | |
contract IntrinsicRectangles { | |
string internal constant ERR_ZERO_ADDRESS = "ERR_ZERO_ADDRESS"; | |
string internal constant ERR_IMMUTABLE = "ERR_IMMUTABLE"; | |
string internal constant ERR_RANGE = "ERR_RANGE"; | |
function supportsInterface(bytes4 _interfaceId) public pure returns (bool) { | |
return | |
_interfaceId == 0x01ffc9a7 || /* ERC-165 */ | |
_interfaceId == 0x80ac58cd || /* ERC-721 */ | |
_interfaceId == 0x5b5e139f || /* ERC-721 Metadata */ | |
_interfaceId == 0x780e9d63 || /* ERC-721 Enumerable */ | |
false; | |
} | |
function balanceOf(address _owner) external pure returns (uint256) { | |
require(_owner != address(0), ERR_ZERO_ADDRESS); | |
return 1 << 96; | |
} | |
function ownerOf(uint256 _tokenId) external pure returns (address) { | |
return decodeTokenId(_tokenId).owner; | |
} | |
function safeTransferFrom( | |
address, | |
address, | |
uint256 | |
) external pure { | |
revert(ERR_IMMUTABLE); | |
} | |
function safeTransferFrom( | |
address, | |
address, | |
uint256, | |
bytes memory | |
) external pure { | |
revert(ERR_IMMUTABLE); | |
} | |
function transferFrom( | |
address, | |
address, | |
uint256 | |
) external pure { | |
revert(ERR_IMMUTABLE); | |
} | |
function approve(address, uint256) external pure { | |
revert(ERR_IMMUTABLE); | |
} | |
function setApprovalForAll(address, uint256) external pure { | |
revert(ERR_IMMUTABLE); | |
} | |
function getApproved(uint256) external pure returns (address) { | |
return address(0); | |
} | |
function isApprovedForAll(address, address) external pure returns (bool) { | |
return false; | |
} | |
function name() external pure returns (string memory) { | |
return "Intrinsic Rectangles"; | |
} | |
function symbol() external pure returns (string memory) { | |
return ""; | |
} | |
function tokenURI(uint256 _tokenId) external pure returns (string memory) { | |
return string(generateTokenUri(decodeTokenId(_tokenId).cosmetics)); | |
} | |
function totalSupply() external pure returns (uint256) { | |
return ~uint256(1 << 96) + 1; | |
} | |
function tokenByIndex(uint256 _index) external pure returns (uint256) { | |
// Tokens not owned by `address(0)` start at `1 << 96`. | |
require(_index < ~uint256(1 << 96) + 1, ERR_RANGE); | |
return uint256(1 << 96) + _index; | |
} | |
function tokenOfOwnerByIndex(address _owner, uint256 _index) | |
external | |
pure | |
returns (uint256) | |
{ | |
require(_index < (1 << 96), ERR_RANGE); | |
return (uint256(uint160(_owner)) << 96) + _index; | |
} | |
function decodeTokenId(uint256 _tokenId) | |
internal | |
pure | |
returns (TokenId memory) | |
{ | |
uint32 _nonce = uint32(_tokenId); | |
_tokenId >>= 32; | |
uint16 _height = uint16(_tokenId); | |
_tokenId >>= 16; | |
uint16 _width = uint16(_tokenId); | |
_tokenId >>= 16; | |
uint32 _rgbaColor = uint32(_tokenId); | |
_tokenId >>= 32; | |
address _owner = address(uint160(_tokenId)); | |
require(_owner != address(0), ERR_ZERO_ADDRESS); | |
return | |
TokenId({ | |
owner: _owner, | |
cosmetics: Cosmetics(_rgbaColor, _width, _height), | |
nonce: _nonce | |
}); | |
} | |
function generateTokenUri(Cosmetics memory _cosmetics) | |
internal | |
pure | |
returns (bytes memory) | |
{ | |
bytes memory _svg = generateSvgImage(_cosmetics); | |
bytes memory _svgDataUri = | |
abi.encodePacked("data:image/svg+xml;base64,", base64Encode(_svg)); | |
bytes memory _json = abi.encodePacked('{"image":"', _svgDataUri, '"}'); | |
bytes memory _jsonDataUri = | |
abi.encodePacked( | |
"data:application/json;base64,", | |
base64Encode(_json) | |
); | |
return _jsonDataUri; | |
} | |
function generateSvgImage(Cosmetics memory _cosmetics) | |
internal | |
pure | |
returns (bytes memory) | |
{ | |
string memory _width = spacePaddedDecimalString(_cosmetics.width); | |
string memory _height = spacePaddedDecimalString(_cosmetics.height); | |
return | |
abi.encodePacked( | |
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ', | |
_width, | |
" ", | |
_height, | |
'"><path fill="#', | |
hexColor(uint24(_cosmetics.rgbaColor >> 8)), | |
'" fill-opacity="', | |
spacePaddedDecimalString(uint16(_cosmetics.rgbaColor & 0xff)), | |
'" d="M0 0H', | |
_width, | |
"V", | |
_height, | |
'H0z"/></svg>' | |
); | |
} | |
function spacePaddedDecimalString(uint16 _z) | |
internal | |
pure | |
returns (string memory) | |
{ | |
bytes memory _buf = bytes(" "); // `type(uint16.max) == 65535` | |
uint8 _zero = 0x30; // `ord("0")` | |
if (_z == 0) { | |
_buf[4] = bytes1(_zero); | |
} else { | |
uint256 _i = 4; | |
while (_z > 0) { | |
_buf[_i--] = bytes1(_zero + uint8(_z % 10)); | |
_z /= 10; | |
} | |
} | |
return string(_buf); | |
} | |
function hexColor(uint24 _color) internal pure returns (string memory) { | |
bytes memory _buf = bytes("xxxxxx"); | |
bytes16 _alphabet = bytes16("0123456789abcdef"); | |
for (uint256 _i = 6; _i > 0; _i--) { | |
_buf[_i - 1] = _alphabet[_color % 16]; | |
_color >>= 4; | |
} | |
return string(_buf); | |
} | |
string internal constant BASE64_TABLE = | |
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
/// @notice Base64 | |
/// @notice Brecht Devos - <brecht@loopring.org> - MIT License | |
function base64Encode(bytes memory data) | |
internal | |
pure | |
returns (string memory) | |
{ | |
if (data.length == 0) return ""; | |
// load the table into memory | |
string memory table = BASE64_TABLE; | |
// multiply by 4/3 rounded up | |
uint256 encodedLen = 4 * ((data.length + 2) / 3); | |
// add some extra buffer at the end required for the writing | |
string memory result = new string(encodedLen + 32); | |
assembly { | |
// set the actual output length | |
mstore(result, encodedLen) | |
// prepare the lookup table | |
let tablePtr := add(table, 1) | |
// input ptr | |
let dataPtr := data | |
let endPtr := add(dataPtr, mload(data)) | |
// result ptr, jump over length | |
let resultPtr := add(result, 32) | |
// run over the input, 3 bytes at a time | |
for { | |
} lt(dataPtr, endPtr) { | |
} { | |
dataPtr := add(dataPtr, 3) | |
// read 3 bytes | |
let input := mload(dataPtr) | |
// write 4 characters | |
mstore( | |
resultPtr, | |
shl(248, mload(add(tablePtr, and(shr(18, input), 0x3F)))) | |
) | |
resultPtr := add(resultPtr, 1) | |
mstore( | |
resultPtr, | |
shl(248, mload(add(tablePtr, and(shr(12, input), 0x3F)))) | |
) | |
resultPtr := add(resultPtr, 1) | |
mstore( | |
resultPtr, | |
shl(248, mload(add(tablePtr, and(shr(6, input), 0x3F)))) | |
) | |
resultPtr := add(resultPtr, 1) | |
mstore( | |
resultPtr, | |
shl(248, mload(add(tablePtr, and(input, 0x3F)))) | |
) | |
resultPtr := add(resultPtr, 1) | |
} | |
// padding with '=' | |
switch mod(mload(data), 3) | |
case 1 { | |
mstore(sub(resultPtr, 2), shl(240, 0x3d3d)) | |
} | |
case 2 { | |
mstore(sub(resultPtr, 1), shl(248, 0x3d)) | |
} | |
} | |
return result; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment