Skip to content

base64-svg solidity contract with helpful js utils

Notifications You must be signed in to change notification settings

sambacha/base64-svg

Repository files navigation

base64-svg

base64 author

Example Usage

see https://etherscan.io/address/0xff9c1b15b16263c61d017ee9f65c50e4ae0113d7#code#L1609

contract Loot is ERC721Enumerable, ReentrancyGuard, Ownable {
  string[] private weapons = [
    'Warhammer',
    'Quarterstaff',
    'Maul',
    'Mace',
    'Club',
    'Katana',
    'Falchion',
    'Scimitar',
    'Long Sword',
    'Short Sword',
    'Ghost Wand',
    'Grave Wand',
    'Bone Wand',
    'Wand',
    'Grimoire',
    'Chronicle',
    'Tome',
    'Book'
  ];

  string[] private chestArmor = [
    'Divine Robe',
    'Silk Robe',
    'Linen Robe',
    'Robe',
    'Shirt',
    'Demon Husk',
    'Dragonskin Armor',
    'Studded Leather Armor',
    'Hard Leather Armor',
    'Leather Armor',
    'Holy Chestplate',
    'Ornate Chestplate',
    'Plate Mail',
    'Chain Mail',
    'Ring Mail'
  ];

  string[] private headArmor = [
    'Ancient Helm',
    'Ornate Helm',
    'Great Helm',
    'Full Helm',
    'Helm',
    'Demon Crown',
    "Dragon's Crown",
    'War Cap',
    'Leather Cap',
    'Cap',
    'Crown',
    'Divine Hood',
    'Silk Hood',
    'Linen Hood',
    'Hood'
  ];

  string[] private waistArmor = [
    'Ornate Belt',
    'War Belt',
    'Plated Belt',
    'Mesh Belt',
    'Heavy Belt',
    'Demonhide Belt',
    'Dragonskin Belt',
    'Studded Leather Belt',
    'Hard Leather Belt',
    'Leather Belt',
    'Brightsilk Sash',
    'Silk Sash',
    'Wool Sash',
    'Linen Sash',
    'Sash'
  ];

  string[] private footArmor = [
    'Holy Greaves',
    'Ornate Greaves',
    'Greaves',
    'Chain Boots',
    'Heavy Boots',
    'Demonhide Boots',
    'Dragonskin Boots',
    'Studded Leather Boots',
    'Hard Leather Boots',
    'Leather Boots',
    'Divine Slippers',
    'Silk Slippers',
    'Wool Shoes',
    'Linen Shoes',
    'Shoes'
  ];

  string[] private handArmor = [
    'Holy Gauntlets',
    'Ornate Gauntlets',
    'Gauntlets',
    'Chain Gloves',
    'Heavy Gloves',
    "Demon's Hands",
    'Dragonskin Gloves',
    'Studded Leather Gloves',
    'Hard Leather Gloves',
    'Leather Gloves',
    'Divine Gloves',
    'Silk Gloves',
    'Wool Gloves',
    'Linen Gloves',
    'Gloves'
  ];

  string[] private necklaces = ['Necklace', 'Amulet', 'Pendant'];

  string[] private rings = [
    'Gold Ring',
    'Silver Ring',
    'Bronze Ring',
    'Platinum Ring',
    'Titanium Ring'
  ];

  string[] private suffixes = [
    'of Power',
    'of Giants',
    'of Titans',
    'of Skill',
    'of Perfection',
    'of Brilliance',
    'of Enlightenment',
    'of Protection',
    'of Anger',
    'of Rage',
    'of Fury',
    'of Vitriol',
    'of the Fox',
    'of Detection',
    'of Reflection',
    'of the Twins'
  ];

  string[] private namePrefixes = [
    'Agony',
    'Apocalypse',
    'Armageddon',
    'Beast',
    'Behemoth',
    'Blight',
    'Blood',
    'Bramble',
    'Brimstone',
    'Brood',
    'Carrion',
    'Cataclysm',
    'Chimeric',
    'Corpse',
    'Corruption',
    'Damnation',
    'Death',
    'Demon',
    'Dire',
    'Dragon',
    'Dread',
    'Doom',
    'Dusk',
    'Eagle',
    'Empyrean',
    'Fate',
    'Foe',
    'Gale',
    'Ghoul',
    'Gloom',
    'Glyph',
    'Golem',
    'Grim',
    'Hate',
    'Havoc',
    'Honour',
    'Horror',
    'Hypnotic',
    'Kraken',
    'Loath',
    'Maelstrom',
    'Mind',
    'Miracle',
    'Morbid',
    'Oblivion',
    'Onslaught',
    'Pain',
    'Pandemonium',
    'Phoenix',
    'Plague',
    'Rage',
    'Rapture',
    'Rune',
    'Skull',
    'Sol',
    'Soul',
    'Sorrow',
    'Spirit',
    'Storm',
    'Tempest',
    'Torment',
    'Vengeance',
    'Victory',
    'Viper',
    'Vortex',
    'Woe',
    'Wrath',
    "Light's",
    'Shimmering'
  ];

  string[] private nameSuffixes = [
    'Bane',
    'Root',
    'Bite',
    'Song',
    'Roar',
    'Grasp',
    'Instrument',
    'Glow',
    'Bender',
    'Shadow',
    'Whisper',
    'Shout',
    'Growl',
    'Tear',
    'Peak',
    'Form',
    'Sun',
    'Moon'
  ];

  function random(string memory input) internal pure returns (uint256) {
    return uint256(keccak256(abi.encodePacked(input)));
  }

  function getWeapon(uint256 tokenId) public view returns (string memory) {
    return pluck(tokenId, 'WEAPON', weapons);
  }

  function getChest(uint256 tokenId) public view returns (string memory) {
    return pluck(tokenId, 'CHEST', chestArmor);
  }

  function getHead(uint256 tokenId) public view returns (string memory) {
    return pluck(tokenId, 'HEAD', headArmor);
  }

  function getWaist(uint256 tokenId) public view returns (string memory) {
    return pluck(tokenId, 'WAIST', waistArmor);
  }

  function getFoot(uint256 tokenId) public view returns (string memory) {
    return pluck(tokenId, 'FOOT', footArmor);
  }

  function getHand(uint256 tokenId) public view returns (string memory) {
    return pluck(tokenId, 'HAND', handArmor);
  }

  function getNeck(uint256 tokenId) public view returns (string memory) {
    return pluck(tokenId, 'NECK', necklaces);
  }

  function getRing(uint256 tokenId) public view returns (string memory) {
    return pluck(tokenId, 'RING', rings);
  }

  function pluck(
    uint256 tokenId,
    string memory keyPrefix,
    string[] memory sourceArray
  ) internal view returns (string memory) {
    uint256 rand = random(
      string(abi.encodePacked(keyPrefix, toString(tokenId)))
    );
    string memory output = sourceArray[rand % sourceArray.length];
    uint256 greatness = rand % 21;
    if (greatness > 14) {
      output = string(
        abi.encodePacked(output, ' ', suffixes[rand % suffixes.length])
      );
    }
    if (greatness >= 19) {
      string[2] memory name;
      name[0] = namePrefixes[rand % namePrefixes.length];
      name[1] = nameSuffixes[rand % nameSuffixes.length];
      if (greatness == 19) {
        output = string(
          abi.encodePacked('"', name[0], ' ', name[1], '" ', output)
        );
      } else {
        output = string(
          abi.encodePacked('"', name[0], ' ', name[1], '" ', output, ' +1')
        );
      }
    }
    return output;
  }

  function tokenURI(uint256 tokenId)
    public
    view
    override
    returns (string memory)
  {
    string[17] memory parts;
    parts[
      0
    ] = '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"><style>.base { fill: white; font-family: serif; font-size: 14px; }</style><rect width="100%" height="100%" fill="black" /><text x="10" y="20" class="base">';

    parts[1] = getWeapon(tokenId);

    parts[2] = '</text><text x="10" y="40" class="base">';

    parts[3] = getChest(tokenId);

    parts[4] = '</text><text x="10" y="60" class="base">';

    parts[5] = getHead(tokenId);

    parts[6] = '</text><text x="10" y="80" class="base">';

    parts[7] = getWaist(tokenId);

    parts[8] = '</text><text x="10" y="100" class="base">';

    parts[9] = getFoot(tokenId);

    parts[10] = '</text><text x="10" y="120" class="base">';

    parts[11] = getHand(tokenId);

    parts[12] = '</text><text x="10" y="140" class="base">';

    parts[13] = getNeck(tokenId);

    parts[14] = '</text><text x="10" y="160" class="base">';

    parts[15] = getRing(tokenId);

    parts[16] = '</text></svg>';

    string memory output = string(
      abi.encodePacked(
        parts[0],
        parts[1],
        parts[2],
        parts[3],
        parts[4],
        parts[5],
        parts[6],
        parts[7],
        parts[8]
      )
    );
    output = string(
      abi.encodePacked(
        output,
        parts[9],
        parts[10],
        parts[11],
        parts[12],
        parts[13],
        parts[14],
        parts[15],
        parts[16]
      )
    );

    string memory json = Base64.encode(
      bytes(
        string(
          abi.encodePacked(
            '{"name": "Bag #',
            toString(tokenId),
            '", "description": "Loot is randomized adventurer gear generated and stored on chain. Stats, images, and other functionality are intentionally omitted for others to interpret. Feel free to use Loot in any way you want.", "image": "data:image/svg+xml;base64,',
            Base64.encode(bytes(output)),
            '"}'
          )
        )
      )
    );
    output = string(abi.encodePacked('data:application/json;base64,', json));

    return output;
  }

  function claim(uint256 tokenId) public nonReentrant {
    require(tokenId > 0 && tokenId < 7778, 'Token ID invalid');
    _safeMint(_msgSender(), tokenId);
  }

  function ownerClaim(uint256 tokenId) public nonReentrant onlyOwner {
    require(tokenId > 7777 && tokenId < 8001, 'Token ID invalid');
    _safeMint(owner(), tokenId);
  }

  function toString(uint256 value) internal pure returns (string memory) {
    // Inspired by OraclizeAPI's implementation - MIT license
    // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

    if (value == 0) {
      return '0';
    }
    uint256 temp = value;
    uint256 digits;
    while (temp != 0) {
      digits++;
      temp /= 10;
    }
    bytes memory buffer = new bytes(digits);
    while (value != 0) {
      digits -= 1;
      buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
      value /= 10;
    }
    return string(buffer);
  }

  constructor() ERC721('Loot', 'LOOT') Ownable() {}
}

/// [MIT License]
/// @title Base64
/// @notice Provides a function for encoding some bytes in base64
/// @author Brecht Devos <[email protected]>
library Base64 {
  bytes internal constant TABLE =
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

  /// @notice Encodes some bytes to the base64 representation
  function encode(bytes memory data) internal pure returns (string memory) {
    uint256 len = data.length;
    if (len == 0) return '';

    // multiply by 4/3 rounded up
    uint256 encodedLen = 4 * ((len + 2) / 3);

    // Add some extra buffer at the end
    bytes memory result = new bytes(encodedLen + 32);

    bytes memory table = TABLE;

    assembly {
      let tablePtr := add(table, 1)
      let resultPtr := add(result, 32)

      for {
        let i := 0
      } lt(i, len) {

      } {
        i := add(i, 3)
        let input := and(mload(add(data, i)), 0xffffff)

        let out := mload(add(tablePtr, and(shr(18, input), 0x3F)))
        out := shl(8, out)
        out := add(
          out,
          and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF)
        )
        out := shl(8, out)
        out := add(
          out,
          and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF)
        )
        out := shl(8, out)
        out := add(out, and(mload(add(tablePtr, and(input, 0x3F))), 0xFF))
        out := shl(224, out)

        mstore(resultPtr, out)

        resultPtr := add(resultPtr, 4)
      }

      switch mod(len, 3)
      case 1 {
        mstore(sub(resultPtr, 2), shl(240, 0x3d3d))
      }
      case 2 {
        mstore(sub(resultPtr, 1), shl(248, 0x3d))
      }

      mstore(result, encodedLen)
    }

    return string(result);
  }
}

License

MIT

About

base64-svg solidity contract with helpful js utils

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published