Skip to content

Instantly share code, notes, and snippets.

@wchargin
Last active July 27, 2021 21:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wchargin/3d50a922febe5b290f0a268a6563c70f to your computer and use it in GitHub Desktop.
Save wchargin/3d50a922febe5b290f0a268a6563c70f to your computer and use it in GitHub Desktop.
SVG token with ownership and cosmetics embedded in token ID (100% untested)
// 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