ProtocolitesRendererV2_OpenSea

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/ProtocolitesRendererV2_OpenSea.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "solady/utils/Base64.sol";
import "solady/utils/LibString.sol";
import "solady/auth/Ownable.sol";

import "./interfaces/IProtocolitesRenderer.sol";

/// @title ProtocolitesRendererV2_OpenSea
/// @notice DOM-based renderer with OpenSea compatibility (includes static image)
contract ProtocolitesRendererV2_OpenSea is Ownable, IProtocolitesRenderer {
    string public renderScript;

    constructor() {
        _initializeOwner(msg.sender);
        renderScript = defaultScript();
    }

    function setRenderScript(string memory _script) external onlyOwner {
        renderScript = _script;
    }

    function tokenURI(uint256 tokenId, TokenData memory data) external view returns (string memory) {
        return string.concat("data:application/json;base64,", Base64.encode(bytes(metadata(tokenId, data))));
    }

    /// @notice Generate static ASCII preview as SVG for OpenSea image field
    function generateStaticImage(uint256 tokenId, TokenData memory data) public pure returns (string memory) {
        bool isKid = data.isKid;
        uint256 size = isKid ? 16 : 24;

        // Decode DNA to get family color
        uint256 familyBits = data.dna >> 17;
        uint256 familyIndex = familyBits % 6;
        string memory color;

        if (familyIndex == 0) color = "#cc0000"; // red
        else if (familyIndex == 1) color = "#008800"; // green
        else if (familyIndex == 2) color = "#0044cc"; // blue
        else if (familyIndex == 3) color = "#cc9900"; // yellow
        else if (familyIndex == 4) color = "#8800cc"; // purple
        else color = "#0088aa"; // cyan

        // Create simple SVG with creature info
        string memory svg = string.concat(
            '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600">',
            '<rect width="600" height="600" fill="#ffffff"/>',
            '<text x="300" y="280" font-family="monospace" font-size="120" fill="',
            color,
            '" text-anchor="middle">',
            isKid ? unicode"????" : unicode"????",
            '</text>',
            '<text x="300" y="340" font-family="monospace" font-size="24" fill="#666" text-anchor="middle">',
            'PROTOCOLITE #',
            LibString.toString(tokenId),
            '</text>',
            '<text x="300" y="370" font-family="monospace" font-size="18" fill="#999" text-anchor="middle">',
            isKid ? "Child" : "Spreader",
            ' - ',
            LibString.toString(size),
            'x',
            LibString.toString(size),
            '</text>',
            '<text x="300" y="400" font-family="monospace" font-size="14" fill="#999" text-anchor="middle">',
            'View animation_url for full experience',
            '</text>',
            '</svg>'
        );

        return string.concat("data:image/svg+xml;base64,", Base64.encode(bytes(svg)));
    }

    function metadata(uint256 tokenId, TokenData memory data) public view returns (string memory) {
        bool isKid = data.isKid;
        uint256 size = isKid ? 16 : 24;

        string memory animation = string.concat(
            '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">',
            "<title>Protocolite #",
            LibString.toString(tokenId),
            "</title>",
            "<style>",
            "*{margin:0;padding:0;box-sizing:border-box;}",
            "html,body{width:100%;height:100%;margin:0;padding:0;min-width:600px;min-height:600px;}",
            'body{font-family:"Courier New",monospace;background:#fff;color:#000;line-height:1;font-size:12px;font-weight:200;display:flex;align-items:center;justify-content:center;overflow:hidden;}',
            ".container{text-align:center;padding:",
            isKid ? "20" : "30",
            "px;position:relative;}",
            ".creature-container{position:relative;display:inline-block;font-family:'Courier New',monospace;line-height:1;-webkit-font-smoothing:none;-moz-osx-font-smoothing:unset;font-smooth:never;text-rendering:optimizeSpeed;}",
            ".creature-char{position:absolute;font-family:'Courier New',monospace;white-space:pre;user-select:none;pointer-events:none;will-change:transform;}",
            "</style></head><body>",
            '<div class="container"><div class="creature-container" id="creature"></div></div>',
            "<script>",
            "const tokenId=",
            LibString.toString(tokenId),
            ";",
            'const dna="',
            LibString.toHexString(data.dna),
            '";',
            "const isKid=",
            isKid ? "true" : "false",
            ";",
            'const parentDna="',
            LibString.toHexString(data.parentDna),
            '";',
            "const size=",
            LibString.toString(size),
            ";",
            renderScript,
            "</script>",
            "</body></html>"
        );

        string memory attributes = string.concat(
            '[{"trait_type":"Type","value":"',
            isKid ? "Child" : "Spreader",
            '"},',
            '{"trait_type":"Size","value":"',
            LibString.toString(size),
            "x",
            LibString.toString(size),
            '"},',
            '{"trait_type":"DNA","value":"',
            LibString.toHexString(data.dna),
            '"},',
            '{"trait_type":"Birth Block","value":',
            LibString.toString(data.birthBlock),
            "}",
            isKid
                ? string.concat(',{"trait_type":"Parent DNA","value":"', LibString.toHexString(data.parentDna), '"}')
                : "",
            "]"
        );

        // Generate static image for OpenSea
        string memory imageData = generateStaticImage(tokenId, data);

        string memory json = string.concat(
            '{"name":"Protocolite #',
            LibString.toString(tokenId),
            isKid ? " (Child)" : " (Spreader)",
            '",',
            '"description":"Fully on-chain generative ASCII art with DOM-based rendering and per-character animations.",',
            '"image":"',
            imageData,
            '",',
            '"animation_url":"data:text/html;base64,',
            Base64.encode(bytes(animation)),
            '",',
            '"attributes":',
            attributes,
            "}"
        );

        return json;
    }

    function getScript1() private pure returns (string memory) {
        return string.concat(
            "function hashCode(s){let h=0;for(let i=0;i<s.length;i++){h=((h<<5)-h)+s.charCodeAt(i);h&=h;}return Math.abs(h);}",
            'const families={red:"#cc0000",green:"#008800",blue:"#0044cc",yellow:"#cc9900",purple:"#8800cc",cyan:"#0088aa"};',
            "function getFamilyFromDNA(d){const b=BigInt(d);const f=b>>17n;const h=Number(f%16777216n);const k=Object.keys(families);return k[h%k.length];}",
            "const family=getFamilyFromDNA(dna);",
            "const familyColor=families[family];",
            "let seed=hashCode(dna);",
            "function random(){seed=(seed*9301+49297)%233280;return seed/233280;}"
        );
    }

    function getScript2() private pure returns (string memory) {
        return string.concat(
            "function decodeDNA(d){",
            "const n=BigInt(d);",
            'const bt=["square","round","diamond","mushroom","invader","ghost"];',
            'const bc=["\\u2588","\\u2593","\\u2592","\\u2591"];',
            'const ec=["\\u25cf","\\u25c9","\\u25ce","\\u25cb"];',
            'const at=["\\u25cf","\\u25c9","\\u25cb","\\u25ce","\\u2726","\\u2727","\\u2605"];',
            'const ht=["none","top","flat","double","fancy"];',
            "return{",
            "bodyType:bt[Number((n>>0n)&7n)%6],",
            "bodyChar:bc[Number((n>>3n)&3n)],",
            "eyeChar:ec[Number((n>>5n)&3n)],",
            'eyeSize:Number((n>>7n)&1n)===1?"mega":"normal",',
            "antennaTip:at[Number((n>>8n)&7n)%7],",
            'armStyle:Number((n>>11n)&1n)===1?"line":"block",',
            'legStyle:Number((n>>12n)&1n)===1?"line":"block",',
            "hatType:ht[Number((n>>13n)&7n)%5],",
            "hasCigarette:Number((n>>16n)&1n)===1};}",
            "const dnaObj=decodeDNA(dna);"
        );
    }

    function getScript3() private pure returns (string memory) {
        return string.concat(
            'const grid=Array(size).fill().map(()=>Array(size).fill(null).map(()=>({char:" ",type:"empty"})));',
            "const cx=Math.floor(size/2);",
            "const cy=Math.floor(size/2);",
            "const bodyType=dnaObj.bodyType;",
            "const bodyWidth=size===24?6:3;",
            "const bodyHeight=size===24?8:4;",
            "const bodyStartY=size===24?7:6;"
        );
    }

    function getScript4() private pure returns (string memory) {
        return string.concat(
            "for(let y=0;y<bodyHeight;y++){",
            "for(let x=-bodyWidth;x<=bodyWidth;x++){",
            "const posY=bodyStartY+y;",
            "const posX=cx+x;",
            "let inBody=false;",
            "const relX=x/bodyWidth;",
            "const relY=(y-bodyHeight/2)/(bodyHeight/2);",
            'if(bodyType==="square"){inBody=true;}',
            'else if(bodyType==="round"){const dist=Math.sqrt(relX*relX+relY*relY);inBody=dist<=1.0;}',
            'else if(bodyType==="diamond"){inBody=Math.abs(relX)+Math.abs(relY)<=1.0;}',
            'else if(bodyType==="mushroom"){',
            "if(isKid){if(relY<-0.2){inBody=true;}else{inBody=Math.abs(relX)<=0.7;}}",
            "else{if(relY<0){inBody=true;}else{inBody=Math.abs(relX)<=0.6;}}}",
            'else if(bodyType==="invader"){',
            "if(relY<-0.3){inBody=Math.abs(relX)<=0.7;}",
            "else if(relY<0.3){inBody=true;}",
            "else{inBody=Math.abs(relX)<=0.85;}}",
            'else if(bodyType==="ghost"){',
            "const distGhost=Math.sqrt(relX*relX+relY*relY);",
            "if(relY<0.5){inBody=distGhost<=1.0;}",
            "else{inBody=Math.abs(relX)<=0.9&&(Math.floor(x+bodyWidth)%2===0||relY<0.8);}}",
            "if(inBody&&posX>=0&&posX<size&&posY>=0&&posY<size){grid[posY][posX]={char:dnaObj.bodyChar,type:'body'};}}}",
            'const isBodyChar=c=>c&&c.type==="body";'
        );
    }

    function getScript5() private pure returns (string memory) {
        return string.concat(
            "const eyeY=bodyStartY+1;",
            "if(isKid){",
            "const eyeCount=1+Math.floor(random()*3);",
            "if(eyeCount===1){",
            "for(let dy=0;dy<2;dy++){for(let dx=-1;dx<=1;dx++){if(isBodyChar(grid[eyeY+dy][cx+dx])){grid[eyeY+dy][cx+dx]={char:' ',type:'empty'};}}}",
            "grid[eyeY][cx-1]={char:dnaObj.bodyChar,type:'body'};grid[eyeY][cx]={char:dnaObj.eyeChar,type:'eye'};grid[eyeY][cx+1]={char:dnaObj.bodyChar,type:'body'};grid[eyeY+1][cx]={char:dnaObj.bodyChar,type:'body'};}",
            "else if(eyeCount===2){",
            "const eyeSpacing=1;",
            "for(let dy=0;dy<2;dy++){for(let dx=0;dx<2;dx++){",
            "if(isBodyChar(grid[eyeY+dy][cx-eyeSpacing-1+dx])){grid[eyeY+dy][cx-eyeSpacing-1+dx]={char:' ',type:'empty'};}",
            "if(isBodyChar(grid[eyeY+dy][cx+eyeSpacing+dx])){grid[eyeY+dy][cx+eyeSpacing+dx]={char:' ',type:'empty'};}}}",
            "grid[eyeY][cx-eyeSpacing-1]={char:dnaObj.bodyChar,type:'body'};grid[eyeY][cx-eyeSpacing]={char:dnaObj.eyeChar,type:'eye'};",
            "grid[eyeY+1][cx-eyeSpacing-1]={char:dnaObj.bodyChar,type:'body'};grid[eyeY+1][cx-eyeSpacing]={char:dnaObj.bodyChar,type:'body'};",
            "grid[eyeY][cx+eyeSpacing]={char:dnaObj.eyeChar,type:'eye'};grid[eyeY][cx+eyeSpacing+1]={char:dnaObj.bodyChar,type:'body'};",
            "grid[eyeY+1][cx+eyeSpacing]={char:dnaObj.bodyChar,type:'body'};grid[eyeY+1][cx+eyeSpacing+1]={char:dnaObj.bodyChar,type:'body'};}",
            'else{for(let dx of[-2,0,2]){if(isBodyChar(grid[eyeY][cx+dx]))grid[eyeY][cx+dx]={char:\' \',type:\'empty\'};grid[eyeY][cx+dx]={char:dnaObj.eyeChar,type:\'eye\'};}}}',
            "else{"
        );
    }

    function getScript6() private pure returns (string memory) {
        return string.concat(
            "const eyeCount=1+Math.floor(random()*3);",
            "if(eyeCount===1){",
            "for(let dy=0;dy<3;dy++){for(let dx=-2;dx<=2;dx++){if(isBodyChar(grid[eyeY+dy][cx+dx])){grid[eyeY+dy][cx+dx]={char:' ',type:'empty'};}}}",
            "for(let dx=-2;dx<=2;dx++){grid[eyeY][cx+dx]={char:dnaObj.bodyChar,type:'body'};grid[eyeY+2][cx+dx]={char:dnaObj.bodyChar,type:'body'};}",
            "grid[eyeY+1][cx-2]={char:dnaObj.bodyChar,type:'body'};grid[eyeY+1][cx-1]={char:dnaObj.eyeChar,type:'eye'};",
            "grid[eyeY+1][cx]={char:dnaObj.eyeChar,type:'eye'};grid[eyeY+1][cx+1]={char:dnaObj.eyeChar,type:'eye'};grid[eyeY+1][cx+2]={char:dnaObj.bodyChar,type:'body'};}",
            "else if(eyeCount===2){",
            "const blockSpacing=2;",
            "for(let dy=0;dy<3;dy++){for(let dx=0;dx<3;dx++){",
            "if(isBodyChar(grid[eyeY+dy][cx-blockSpacing-2+dx])){grid[eyeY+dy][cx-blockSpacing-2+dx]={char:' ',type:'empty'};}",
            "if(isBodyChar(grid[eyeY+dy][cx+blockSpacing+dx])){grid[eyeY+dy][cx+blockSpacing+dx]={char:' ',type:'empty'};}}}",
            "for(let dy=0;dy<3;dy++){for(let dx=0;dx<3;dx++){",
            "const isCenter=dy===1&&dx===1;",
            "grid[eyeY+dy][cx-blockSpacing-2+dx]={char:isCenter?dnaObj.eyeChar:dnaObj.bodyChar,type:isCenter?'eye':'body'};",
            "grid[eyeY+dy][cx+blockSpacing+dx]={char:isCenter?dnaObj.eyeChar:dnaObj.bodyChar,type:isCenter?'eye':'body'};}}}",
            "else{for(let i=-3;i<=3;i+=3){for(let dy=0;dy<2;dy++){",
            "if(isBodyChar(grid[eyeY+dy][cx+i]))grid[eyeY+dy][cx+i]={char:' ',type:'empty'};",
            "if(isBodyChar(grid[eyeY+dy][cx+i-1]))grid[eyeY+dy][cx+i-1]={char:' ',type:'empty'};",
            "grid[eyeY+dy][cx+i]={char:dnaObj.eyeChar,type:'eye'};grid[eyeY+dy][cx+i-1]={char:dnaObj.eyeChar,type:'eye'};}}}}"
        );
    }

    function getScript7() private pure returns (string memory) {
        return string.concat(
            "if(random()>0.3){",
            "const mouthY=eyeY+(isKid?2:3);",
            "if(mouthY<size&&isBodyChar(grid[mouthY][cx])){",
            'grid[mouthY][cx]={char:"\\u2500",type:\'mouth\'};',
            'if(random()>0.5&&isBodyChar(grid[mouthY][cx-1])){grid[mouthY][cx-1]={char:"\\u2500",type:\'mouth\'};}',
            'if(random()>0.5&&isBodyChar(grid[mouthY][cx+1])){grid[mouthY][cx+1]={char:"\\u2500",type:\'mouth\'};}}}',
            "if(dnaObj.hasCigarette){",
            "const cigY=eyeY+(isKid?2:3);",
            'const cigChars=["\\u2248","\\u223c","~"];',
            "const cigChar=cigChars[Math.floor(random()*cigChars.length)];",
            "const cigX=cx+(random()>0.5?3:-3);",
            "if(cigX>=0&&cigX<size&&cigY>=0&&cigY<size){grid[cigY][cigX]={char:cigChar,type:'cigarette'};if(cigX+1<size)grid[cigY][cigX+1]={char:'\\u2219',type:'cigarette'};}}"
        );
    }

    function getScript8() private pure returns (string memory) {
        return string.concat(
            "const armCount=1+Math.floor(random()*4);",
            "const armLength=isKid?(1+Math.floor(random()*2)):(2+Math.floor(random()*4));",
            'const armChar=dnaObj.armStyle==="block"?"\\u2588":"\\u2500";',
            "for(let a=0;a<armCount;a++){",
            "const currentArmY=bodyStartY+2+a*(isKid?1:2);",
            "if(currentArmY>=bodyStartY+bodyHeight)break;",
            "let leftBodyEdge=cx,rightBodyEdge=cx;",
            "for(let x=cx;x>=0;x--){if(isBodyChar(grid[currentArmY][x])){leftBodyEdge=x;}else{break;}}",
            "for(let x=cx;x<size;x++){if(isBodyChar(grid[currentArmY][x])){rightBodyEdge=x;}else{break;}}",
            "for(let i=1;i<=armLength;i++){",
            "if(leftBodyEdge-i>=0){grid[currentArmY][leftBodyEdge-i]={char:armChar,type:'arm'};}",
            "if(rightBodyEdge+i<size){grid[currentArmY][rightBodyEdge+i]={char:armChar,type:'arm'};}}}"
        );
    }

    function getScript9() private pure returns (string memory) {
        return string.concat(
            "const legCount=1+Math.floor(random()*4);",
            "const legY=bodyStartY+bodyHeight;",
            "const legLength=isKid?(1+Math.floor(random()*2)):(2+Math.floor(random()*3));",
            'const legChar=dnaObj.legStyle==="block"?"\\u2588":"\\u2502";',
            "const bodyBottomPositions=[];",
            "for(let x=0;x<size;x++){if(grid[legY-1]&&isBodyChar(grid[legY-1][x])){bodyBottomPositions.push(x);}}",
            "const legPositions=[];",
            "if(bodyBottomPositions.length>0){",
            "if(legCount===1){legPositions.push(bodyBottomPositions[Math.floor(bodyBottomPositions.length/2)]);}",
            "else if(legCount===2){",
            "legPositions.push(bodyBottomPositions[Math.floor(bodyBottomPositions.length*0.25)]);",
            "legPositions.push(bodyBottomPositions[Math.floor(bodyBottomPositions.length*0.75)]);}",
            "else if(legCount===3){",
            "legPositions.push(bodyBottomPositions[0]);",
            "legPositions.push(bodyBottomPositions[Math.floor(bodyBottomPositions.length/2)]);",
            "legPositions.push(bodyBottomPositions[bodyBottomPositions.length-1]);}",
            "else{",
            "legPositions.push(bodyBottomPositions[0]);",
            "legPositions.push(bodyBottomPositions[Math.floor(bodyBottomPositions.length*0.33)]);",
            "legPositions.push(bodyBottomPositions[Math.floor(bodyBottomPositions.length*0.66)]);",
            "legPositions.push(bodyBottomPositions[bodyBottomPositions.length-1]);}}",
            "for(let legX of legPositions){if(legX>=0&&legX<size){for(let i=0;i<legLength;i++){if(legY+i<size){grid[legY+i][legX]={char:legChar,type:'leg'};}}}}"
        );
    }

    function getScript10() private pure returns (string memory) {
        return string.concat(
            "const antennaCount=1+Math.floor(random()*4);",
            "const antennaLength=isKid?1:(1+Math.floor(random()*2));",
            "const bodyTopPositions=[];",
            "for(let x=0;x<size;x++){if(grid[bodyStartY]&&isBodyChar(grid[bodyStartY][x])){bodyTopPositions.push(x);}}",
            "const antennaPositions=[];",
            "if(bodyTopPositions.length>0){",
            "if(antennaCount===1){antennaPositions.push(bodyTopPositions[Math.floor(bodyTopPositions.length/2)]);}",
            "else if(antennaCount===2){",
            "antennaPositions.push(bodyTopPositions[Math.floor(bodyTopPositions.length*0.25)]);",
            "antennaPositions.push(bodyTopPositions[Math.floor(bodyTopPositions.length*0.75)]);}",
            "else if(antennaCount===3){",
            "antennaPositions.push(bodyTopPositions[0]);",
            "antennaPositions.push(bodyTopPositions[Math.floor(bodyTopPositions.length/2)]);",
            "antennaPositions.push(bodyTopPositions[bodyTopPositions.length-1]);}",
            "else{",
            "antennaPositions.push(bodyTopPositions[0]);",
            "antennaPositions.push(bodyTopPositions[Math.floor(bodyTopPositions.length*0.33)]);",
            "antennaPositions.push(bodyTopPositions[Math.floor(bodyTopPositions.length*0.66)]);",
            "antennaPositions.push(bodyTopPositions[bodyTopPositions.length-1]);}}",
            "for(let antennaX of antennaPositions){for(let i=1;i<=antennaLength;i++){",
            "const antennaY=bodyStartY-i;",
            "if(antennaY>=0){grid[antennaY][antennaX]={char:i===antennaLength?dnaObj.antennaTip:'\\u2502',type:i===antennaLength?'antenna-tip':'antenna'};}}}"
        );
    }

    function getScript11() private pure returns (string memory) {
        return string.concat(
            'if(dnaObj.hatType&&dnaObj.hatType!=="none"){',
            "const hatY=bodyStartY-antennaLength-1;",
            "if(hatY>=0){",
            'if(dnaObj.hatType==="top"){',
            'for(let dx=-2;dx<=2;dx++){if(cx+dx>=0&&cx+dx<size){grid[hatY][cx+dx]={char:"\\u2580",type:\'hat\'};}}',
            'if(hatY+1<size){grid[hatY+1][cx]={char:"\\u2588",type:\'hat\'};}}',
            'else if(dnaObj.hatType==="flat"){for(let dx=-2;dx<=2;dx++){if(cx+dx>=0&&cx+dx<size){grid[hatY][cx+dx]={char:"\\u2550",type:\'hat\'};}}}',
            'else if(dnaObj.hatType==="double"){',
            'for(let dx=-2;dx<=2;dx++){if(cx+dx>=0&&cx+dx<size&&hatY-1>=0){grid[hatY-1][cx+dx]={char:"\\u2580",type:\'hat\'};grid[hatY][cx+dx]={char:"\\u2584",type:\'hat\'};}}}',
            'else if(dnaObj.hatType==="fancy"){',
            "if(cx-2>=0&&cx+2<size){",
            'grid[hatY][cx-2]={char:"\\u2554",type:\'hat\'};grid[hatY][cx-1]={char:"\\u2550",type:\'hat\'};grid[hatY][cx]={char:"\\u2550",type:\'hat\'};',
            'grid[hatY][cx+1]={char:"\\u2550",type:\'hat\'};grid[hatY][cx+2]={char:"\\u2557",type:\'hat\'};}}}}'
        );
    }

    function getScript12() private pure returns (string memory) {
        return string.concat(
            "const container=document.getElementById('creature');",
            "const fontSize=size===24?20:16;",
            "const charWidth=fontSize*0.6;",
            "const lineHeight=fontSize;",
            "container.style.width=size*charWidth+'px';",
            "container.style.height=size*lineHeight+'px';",
            "container.style.fontSize=fontSize+'px';",
            "const charElements=[];",
            "for(let y=0;y<size;y++){for(let x=0;x<size;x++){",
            "const cell=grid[y][x];",
            "if(!cell||cell.char===' ')continue;",
            "const span=document.createElement('span');",
            "span.className='creature-char';",
            "span.textContent=cell.char;",
            "span.style.left=x*charWidth+'px';",
            "span.style.top=y*lineHeight+'px';",
            "span.style.color=familyColor;",
            "container.appendChild(span);",
            "charElements.push({element:span,originalChar:cell.char,type:cell.type,x:x,y:y,baseX:x*charWidth,baseY:y*lineHeight});}}"
        );
    }

    function getScript13() private pure returns (string memory) {
        return string.concat(
            "let time=0;",
            "const tempNames=['calm','balanced','energetic','chaotic','glitchy','unstable'];",
            "const tempWeights=[35,30,20,10,4,1];",
            "const tempSpeeds=[1.5,2.5,3.5,5.0,6.0,7.0];",
            "const tempGlitch=[0.02,0.05,0.12,0.25,0.40,0.60];",
            "const tempCorrupt=[0.0,0.0,0.03,0.08,0.15,0.25];",
            "const totalWeight=tempWeights.reduce((s,w)=>s+w,0);",
            "let r=random()*totalWeight;",
            "let tempIndex=0;",
            "for(let i=0;i<tempWeights.length;i++){r-=tempWeights[i];if(r<=0){tempIndex=i;break;}}",
            "const temperament=tempNames[tempIndex];",
            "const speed=tempSpeeds[tempIndex];",
            "const glitchChance=tempGlitch[tempIndex];",
            "const charCorrupt=tempCorrupt[tempIndex];",
            'const corruptChars=["\\u2588","\\u2593","\\u2592","\\u2591","\\u2580","\\u2584","\\u25a0","\\u25a1","\\u25aa","\\u25ab"];',
            "const energetic=temperament==='energetic';",
            "const chaotic=temperament==='chaotic';",
            "const glitchy=temperament==='glitchy';",
            "const unstable=temperament==='unstable';"
        );
    }

    function getScript14() private pure returns (string memory) {
        return string.concat(
            "function animate(){",
            "time+=speed*0.016;",
            "const isGlitching=Math.random()<glitchChance;",
            "const rowOffsets=new Array(size).fill(0);",
            "if(isGlitching&&(chaotic||glitchy||unstable)){for(let y=0;y<size;y++){if(Math.random()<0.1){rowOffsets[y]=(Math.random()-0.5)*6;}}}",
            "for(const cd of charElements){",
            "const{element,originalChar,type,x,y,baseX,baseY}=cd;",
            "let char=originalChar;",
            "let offsetX=0;",
            "let offsetY=0;",
            "let rotation=0;",
            "let scale=1;",
            "if(isGlitching&&Math.random()<charCorrupt){char=corruptChars[Math.floor(Math.random()*corruptChars.length)];}",
            "if(type==='body'){offsetY+=Math.sin(time+x*0.1)*2;}",
            "else if(type==='eye'){if(Math.sin(time*0.5)<-0.95){char='\\u2500';}if(energetic||chaotic||glitchy||unstable){offsetX+=Math.sin(time*2+y)*1.5;}}",
            "else if(type==='arm'){offsetY+=Math.sin(time*1.5+y*0.5)*3;if(chaotic||glitchy||unstable){offsetX+=Math.cos(time*2+x)*2;}}",
            "else if(type==='leg'){offsetY+=Math.sin(time*2+x*1.5)*2;}",
            "else if(type==='antenna'||type==='antenna-tip'){offsetX+=Math.sin(time+x*0.3)*2;if(type==='antenna-tip'){rotation=Math.sin(time*0.8)*0.3;}}",
            "else if(type==='mouth'){if(Math.sin(time*0.7)>0.7&&(energetic||chaotic||glitchy||unstable)){char='\\u25cb';}}"
        );
    }

    function getScript15() private pure returns (string memory) {
        return string.concat(
            "if(isGlitching){const glitchMult=unstable?4:glitchy?3:chaotic?2:energetic?1.5:1;offsetX+=(Math.random()-0.5)*glitchMult;offsetY+=(Math.random()-0.5)*glitchMult;}",
            "offsetX+=rowOffsets[y];",
            "if(isGlitching&&unstable&&Math.random()<0.1){scale=0.8+Math.random()*0.4;rotation+=(Math.random()-0.5)*0.3;}",
            "let color=familyColor;",
            "if(isGlitching&&(chaotic||glitchy||unstable)){const colors=['#ff0000','#00ff00','#0000ff',familyColor];color=colors[Math.floor(Math.random()*colors.length)];}",
            "element.textContent=char;",
            "element.style.color=color;",
            "const transforms=[];",
            "if(offsetX!==0||offsetY!==0){transforms.push('translate('+offsetX.toFixed(2)+'px,'+offsetY.toFixed(2)+'px)');}",
            "if(rotation!==0){transforms.push('rotate('+rotation+'rad)');}",
            "if(scale!==1){transforms.push('scale('+scale+')');}",
            "element.style.transform=transforms.length>0?transforms.join(' '):'none';}",
            "requestAnimationFrame(animate);}",
            "animate();"
        );
    }

    function defaultScript() private pure returns (string memory) {
        return string.concat(
            getScript1(),
            getScript2(),
            getScript3(),
            getScript4(),
            getScript5(),
            getScript6(),
            getScript7(),
            getScript8(),
            getScript9(),
            getScript10(),
            getScript11(),
            getScript12(),
            getScript13(),
            getScript14(),
            getScript15()
        );
    }
}
"
    },
    "lib/solady/src/utils/Base64.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Library to encode strings in Base64.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Base64.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/Base64.sol)
/// @author Modified from (https://github.com/Brechtpd/base64/blob/main/base64.sol) by Brecht Devos - <brecht@loopring.org>.
library Base64 {
    /// @dev Encodes `data` using the base64 encoding described in RFC 4648.
    /// See: https://datatracker.ietf.org/doc/html/rfc4648
    /// @param fileSafe  Whether to replace '+' with '-' and '/' with '_'.
    /// @param noPadding Whether to strip away the padding.
    function encode(bytes memory data, bool fileSafe, bool noPadding)
        internal
        pure
        returns (string memory result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let dataLength := mload(data)

            if dataLength {
                // Multiply by 4/3 rounded up.
                // The `shl(2, ...)` is equivalent to multiplying by 4.
                let encodedLength := shl(2, div(add(dataLength, 2), 3))

                // Set `result` to point to the start of the free memory.
                result := mload(0x40)

                // Store the table into the scratch space.
                // Offsetted by -1 byte so that the `mload` will load the character.
                // We will rewrite the free memory pointer at `0x40` later with
                // the allocated size.
                // The magic constant 0x0670 will turn "-_" into "+/".
                mstore(0x1f, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef")
                mstore(0x3f, xor("ghijklmnopqrstuvwxyz0123456789-_", mul(iszero(fileSafe), 0x0670)))

                // Skip the first slot, which stores the length.
                let ptr := add(result, 0x20)
                let end := add(ptr, encodedLength)

                let dataEnd := add(add(0x20, data), dataLength)
                let dataEndValue := mload(dataEnd) // Cache the value at the `dataEnd` slot.
                mstore(dataEnd, 0x00) // Zeroize the `dataEnd` slot to clear dirty bits.

                // Run over the input, 3 bytes at a time.
                for {} 1 {} {
                    data := add(data, 3) // Advance 3 bytes.
                    let input := mload(data)

                    // Write 4 bytes. Optimized for fewer stack operations.
                    mstore8(0, mload(and(shr(18, input), 0x3F)))
                    mstore8(1, mload(and(shr(12, input), 0x3F)))
                    mstore8(2, mload(and(shr(6, input), 0x3F)))
                    mstore8(3, mload(and(input, 0x3F)))
                    mstore(ptr, mload(0x00))

                    ptr := add(ptr, 4) // Advance 4 bytes.
                    if iszero(lt(ptr, end)) { break }
                }
                mstore(dataEnd, dataEndValue) // Restore the cached value at `dataEnd`.
                mstore(0x40, add(end, 0x20)) // Allocate the memory.
                // Equivalent to `o = [0, 2, 1][dataLength % 3]`.
                let o := div(2, mod(dataLength, 3))
                // Offset `ptr` and pad with '='. We can simply write over the end.
                mstore(sub(ptr, o), shl(240, 0x3d3d))
                // Set `o` to zero if there is padding.
                o := mul(iszero(iszero(noPadding)), o)
                mstore(sub(ptr, o), 0) // Zeroize the slot after the string.
                mstore(result, sub(encodedLength, o)) // Store the length.
            }
        }
    }

    /// @dev Encodes `data` using the base64 encoding described in RFC 4648.
    /// Equivalent to `encode(data, false, false)`.
    function encode(bytes memory data) internal pure returns (string memory result) {
        result = encode(data, false, false);
    }

    /// @dev Encodes `data` using the base64 encoding described in RFC 4648.
    /// Equivalent to `encode(data, fileSafe, false)`.
    function encode(bytes memory data, bool fileSafe)
        internal
        pure
        returns (string memory result)
    {
        result = encode(data, fileSafe, false);
    }

    /// @dev Decodes base64 encoded `data`.
    ///
    /// Supports:
    /// - RFC 4648 (both standard and file-safe mode).
    /// - RFC 3501 (63: ',').
    ///
    /// Does not support:
    /// - Line breaks.
    ///
    /// Note: For performance reasons,
    /// this function will NOT revert on invalid `data` inputs.
    /// Outputs for invalid inputs will simply be undefined behaviour.
    /// It is the user's responsibility to ensure that the `data`
    /// is a valid base64 encoded string.
    function decode(string memory data) internal pure returns (bytes memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            let dataLength := mload(data)

            if dataLength {
                let decodedLength := mul(shr(2, dataLength), 3)

                for {} 1 {} {
                    // If padded.
                    if iszero(and(dataLength, 3)) {
                        let t := xor(mload(add(data, dataLength)), 0x3d3d)
                        // forgefmt: disable-next-item
                        decodedLength := sub(
                            decodedLength,
                            add(iszero(byte(30, t)), iszero(byte(31, t)))
                        )
                        break
                    }
                    // If non-padded.
                    decodedLength := add(decodedLength, sub(and(dataLength, 3), 1))
                    break
                }
                result := mload(0x40)

                // Write the length of the bytes.
                mstore(result, decodedLength)

                // Skip the first slot, which stores the length.
                let ptr := add(result, 0x20)
                let end := add(ptr, decodedLength)

                // Load the table into the scratch space.
                // Constants are optimized for smaller bytecode with zero gas overhead.
                // `m` also doubles as the mask of the upper 6 bits.
                let m := 0xfc000000fc00686c7074787c8084888c9094989ca0a4a8acb0b4b8bcc0c4c8cc
                mstore(0x5b, m)
                mstore(0x3b, 0x04080c1014181c2024282c3034383c4044484c5054585c6064)
                mstore(0x1a, 0xf8fcf800fcd0d4d8dce0e4e8ecf0f4)

                for {} 1 {} {
                    // Read 4 bytes.
                    data := add(data, 4)
                    let input := mload(data)

                    // Write 3 bytes.
                    // forgefmt: disable-next-item
                    mstore(ptr, or(
                        and(m, mload(byte(28, input))),
                        shr(6, or(
                            and(m, mload(byte(29, input))),
                            shr(6, or(
                                and(m, mload(byte(30, input))),
                                shr(6, mload(byte(31, input)))
                            ))
                        ))
                    ))
                    ptr := add(ptr, 3)
                    if iszero(lt(ptr, end)) { break }
                }
                mstore(0x40, add(end, 0x20)) // Allocate the memory.
                mstore(end, 0) // Zeroize the slot after the bytes.
                mstore(0x60, 0) // Restore the zero slot.
            }
        }
    }
}
"
    },
    "lib/solady/src/utils/LibString.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import {LibBytes} from "./LibBytes.sol";

/// @notice Library for converting numbers into strings and other string operations.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibString.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol)
///
/// @dev Note:
/// For performance and bytecode compactness, most of the string operations are restricted to
/// byte strings (7-bit ASCII), except where otherwise specified.
/// Usage of byte string operations on charsets with runes spanning two or more bytes
/// can lead to undefined behavior.
library LibString {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          STRUCTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Goated string storage struct that totally MOGs, no cap, fr.
    /// Uses less gas and bytecode than Solidity's native string storage. It's meta af.
    /// Packs length with the first 31 bytes if <255 bytes, so it’s mad tight.
    struct StringStorage {
        bytes32 _spacer;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                        CUSTOM ERRORS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The length of the output is too small to contain all the hex digits.
    error HexLengthInsufficient();

    /// @dev The length of the string is more than 32 bytes.
    error TooBigForSmallString();

    /// @dev The input string must be a 7-bit ASCII.
    error StringNot7BitASCII();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The constant returned when the `search` is not found in the string.
    uint256 internal constant NOT_FOUND = type(uint256).max;

    /// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.
    uint128 internal constant ALPHANUMERIC_7_BIT_ASCII = 0x7fffffe07fffffe03ff000000000000;

    /// @dev Lookup for 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.
    uint128 internal constant LETTERS_7_BIT_ASCII = 0x7fffffe07fffffe0000000000000000;

    /// @dev Lookup for 'abcdefghijklmnopqrstuvwxyz'.
    uint128 internal constant LOWERCASE_7_BIT_ASCII = 0x7fffffe000000000000000000000000;

    /// @dev Lookup for 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
    uint128 internal constant UPPERCASE_7_BIT_ASCII = 0x7fffffe0000000000000000;

    /// @dev Lookup for '0123456789'.
    uint128 internal constant DIGITS_7_BIT_ASCII = 0x3ff000000000000;

    /// @dev Lookup for '0123456789abcdefABCDEF'.
    uint128 internal constant HEXDIGITS_7_BIT_ASCII = 0x7e0000007e03ff000000000000;

    /// @dev Lookup for '01234567'.
    uint128 internal constant OCTDIGITS_7_BIT_ASCII = 0xff000000000000;

    /// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \	\
\r\x0b\x0c'.
    uint128 internal constant PRINTABLE_7_BIT_ASCII = 0x7fffffffffffffffffffffff00003e00;

    /// @dev Lookup for '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'.
    uint128 internal constant PUNCTUATION_7_BIT_ASCII = 0x78000001f8000001fc00fffe00000000;

    /// @dev Lookup for ' \	\
\r\x0b\x0c'.
    uint128 internal constant WHITESPACE_7_BIT_ASCII = 0x100003e00;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                 STRING STORAGE OPERATIONS                  */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sets the value of the string storage `$` to `s`.
    function set(StringStorage storage $, string memory s) internal {
        LibBytes.set(bytesStorage($), bytes(s));
    }

    /// @dev Sets the value of the string storage `$` to `s`.
    function setCalldata(StringStorage storage $, string calldata s) internal {
        LibBytes.setCalldata(bytesStorage($), bytes(s));
    }

    /// @dev Sets the value of the string storage `$` to the empty string.
    function clear(StringStorage storage $) internal {
        delete $._spacer;
    }

    /// @dev Returns whether the value stored is `$` is the empty string "".
    function isEmpty(StringStorage storage $) internal view returns (bool) {
        return uint256($._spacer) & 0xff == uint256(0);
    }

    /// @dev Returns the length of the value stored in `$`.
    function length(StringStorage storage $) internal view returns (uint256) {
        return LibBytes.length(bytesStorage($));
    }

    /// @dev Returns the value stored in `$`.
    function get(StringStorage storage $) internal view returns (string memory) {
        return string(LibBytes.get(bytesStorage($)));
    }

    /// @dev Helper to cast `$` to a `BytesStorage`.
    function bytesStorage(StringStorage storage $)
        internal
        pure
        returns (LibBytes.BytesStorage storage casted)
    {
        /// @solidity memory-safe-assembly
        assembly {
            casted.slot := $.slot
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     DECIMAL OPERATIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the base 10 decimal representation of `value`.
    function toString(uint256 value) internal pure returns (string memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            // The maximum value of a uint256 contains 78 digits (1 byte per digit), but
            // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned.
            // We will need 1 word for the trailing zeros padding, 1 word for the length,
            // and 3 words for a maximum of 78 digits.
            result := add(mload(0x40), 0x80)
            mstore(0x40, add(result, 0x20)) // Allocate memory.
            mstore(result, 0) // Zeroize the slot after the string.

            let end := result // Cache the end of the memory to calculate the length later.
            let w := not(0) // Tsk.
            // We write the string from rightmost digit to leftmost digit.
            // The following is essentially a do-while loop that also handles the zero case.
            for { let temp := value } 1 {} {
                result := add(result, w) // `sub(result, 1)`.
                // Store the character to the pointer.
                // The ASCII index of the '0' character is 48.
                mstore8(result, add(48, mod(temp, 10)))
                temp := div(temp, 10) // Keep dividing `temp` until zero.
                if iszero(temp) { break }
            }
            let n := sub(end, result)
            result := sub(result, 0x20) // Move the pointer 32 bytes back to make room for the length.
            mstore(result, n) // Store the length.
        }
    }

    /// @dev Returns the base 10 decimal representation of `value`.
    function toString(int256 value) internal pure returns (string memory result) {
        if (value >= 0) return toString(uint256(value));
        unchecked {
            result = toString(~uint256(value) + 1);
        }
        /// @solidity memory-safe-assembly
        assembly {
            // We still have some spare memory space on the left,
            // as we have allocated 3 words (96 bytes) for up to 78 digits.
            let n := mload(result) // Load the string length.
            mstore(result, 0x2d) // Store the '-' character.
            result := sub(result, 1) // Move back the string pointer by a byte.
            mstore(result, add(n, 1)) // Update the string length.
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   HEXADECIMAL OPERATIONS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the hexadecimal representation of `value`,
    /// left-padded to an input length of `byteCount` bytes.
    /// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte,
    /// giving a total length of `byteCount * 2 + 2` bytes.
    /// Reverts if `byteCount` is too small for the output to contain all the digits.
    function toHexString(uint256 value, uint256 byteCount)
        internal
        pure
        returns (string memory result)
    {
        result = toHexStringNoPrefix(value, byteCount);
        /// @solidity memory-safe-assembly
        assembly {
            let n := add(mload(result), 2) // Compute the length.
            mstore(result, 0x3078) // Store the "0x" prefix.
            result := sub(result, 2) // Move the pointer.
            mstore(result, n) // Store the length.
        }
    }

    /// @dev Returns the hexadecimal representation of `value`,
    /// left-padded to an input length of `byteCount` bytes.
    /// The output is not prefixed with "0x" and is encoded using 2 hexadecimal digits per byte,
    /// giving a total length of `byteCount * 2` bytes.
    /// Reverts if `byteCount` is too small for the output to contain all the digits.
    function toHexStringNoPrefix(uint256 value, uint256 byteCount)
        internal
        pure
        returns (string memory result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // We need 0x20 bytes for the trailing zeros padding, `byteCount * 2` bytes
            // for the digits, 0x02 bytes for the prefix, and 0x20 bytes for the length.
            // We add 0x20 to the total and round down to a multiple of 0x20.
            // (0x20 + 0x20 + 0x02 + 0x20) = 0x62.
            result := add(mload(0x40), and(add(shl(1, byteCount), 0x42), not(0x1f)))
            mstore(0x40, add(result, 0x20)) // Allocate memory.
            mstore(result, 0) // Zeroize the slot after the string.

            let end := result // Cache the end to calculate the length later.
            // Store "0123456789abcdef" in scratch space.
            mstore(0x0f, 0x30313233343536373839616263646566)

            let start := sub(result, add(byteCount, byteCount))
            let w := not(1) // Tsk.
            let temp := value
            // We write the string from rightmost digit to leftmost digit.
            // The following is essentially a do-while loop that also handles the zero case.
            for {} 1 {} {
                result := add(result, w) // `sub(result, 2)`.
                mstore8(add(result, 1), mload(and(temp, 15)))
                mstore8(result, mload(and(shr(4, temp), 15)))
                temp := shr(8, temp)
                if iszero(xor(result, start)) { break }
            }
            if temp {
                mstore(0x00, 0x2194895a) // `HexLengthInsufficient()`.
                revert(0x1c, 0x04)
            }
            let n := sub(end, result)
            result := sub(result, 0x20)
            mstore(result, n) // Store the length.
        }
    }

    /// @dev Returns the hexadecimal representation of `value`.
    /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte.
    /// As address are 20 bytes long, the output will left-padded to have
    /// a length of `20 * 2 + 2` bytes.
    function toHexString(uint256 value) internal pure returns (string memory result) {
        result = toHexStringNoPrefix(value);
        /// @solidity memory-safe-assembly
        assembly {
            let n := add(mload(result), 2) // Compute the length.
            mstore(result, 0x3078) // Store the "0x" prefix.
            result := sub(result, 2) // Move the pointer.
            mstore(result, n) // Store the length.
        }
    }

    /// @dev Returns the hexadecimal representation of `value`.
    /// The output is prefixed with "0x".
    /// The output excludes leading "0" from the `toHexString` output.
    /// `0x00: "0x0", 0x01: "0x1", 0x12: "0x12", 0x123: "0x123"`.
    function toMinimalHexString(uint256 value) internal pure returns (string memory result) {
        result = toHexStringNoPrefix(value);
        /// @solidity memory-safe-assembly
        assembly {
            let o := eq(byte(0, mload(add(result, 0x20))), 0x30) // Whether leading zero is present.
            let n := add(mload(result), 2) // Compute the length.
            mstore(add(result, o), 0x3078) // Store the "0x" prefix, accounting for leading zero.
            result := sub(add(result, o), 2) // Move the pointer, accounting for leading zero.
            mstore(result, sub(n, o)) // Store the length, accounting for leading zero.
        }
    }

    /// @dev Returns the hexadecimal representation of `value`.
    /// The output excludes leading "0" from the `toHexStringNoPrefix` output.
    /// `0x00: "0", 0x01: "1", 0x12: "12", 0x123: "123"`.
    function toMinimalHexStringNoPrefix(uint256 value)
        internal
        pure
        returns (string memory result)
    {
        result = toHexStringNoPrefix(value);
        /// @solidity memory-safe-assembly
        assembly {
            let o := eq(byte(0, mload(add(result, 0x20))), 0x30) // Whether leading zero is present.
            let n := mload(result) // Get the length.
            result := add(result, o) // Move the pointer, accounting for leading zero.
            mstore(result, sub(n, o)) // Store the length, accounting for leading zero.
        }
    }

    /// @dev Returns the hexadecimal representation of `value`.
    /// The output is encoded using 2 hexadecimal digits per byte.
    /// As address are 20 bytes long, the output will left-padded to have
    /// a length of `20 * 2` bytes.
    function toHexStringNoPrefix(uint256 value) internal pure returns (string memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length,
            // 0x02 bytes for the prefix, and 0x40 bytes for the digits.
            // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x40) is 0xa0.
            result := add(mload(0x40), 0x80)
            mstore(0x40, add(result, 0x20)) // Allocate memory.
            mstore(result, 0) // Zeroize the slot after the string.

            let end := result // Cache the end to calculate the length later.
            mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup.

            let w := not(1) // Tsk.
            // We write the string from rightmost digit to leftmost digit.
            // The following is essentially a do-while loop that also handles the zero case.
            for { let temp := value } 1 {} {
                result := add(result, w) // `sub(result, 2)`.
                mstore8(add(result, 1), mload(and(temp, 15)))
                mstore8(result, mload(and(shr(4, temp), 15)))
                temp := shr(8, temp)
                if iszero(temp) { break }
            }
            let n := sub(end, result)
            result := sub(result, 0x20)
            mstore(result, n) // Store the length.
        }
    }

    /// @dev Returns the hexadecimal representation of `value`.
    /// The output is prefixed with "0x", encoded using 2 hexadecimal digits per byte,
    /// and the alphabets are capitalized conditionally according to
    /// https://eips.ethereum.org/EIPS/eip-55
    function toHexStringChecksummed(address value) internal pure returns (string memory result) {
        result = toHexString(value);
        /// @solidity memory-safe-assembly
        assembly {
            let mask := shl(6, div(not(0), 255)) // `0b010000000100000000 ...`
            let o := add(result, 0x22)
            let hashed := and(keccak256(o, 40), mul(34, mask)) // `0b10001000 ... `
            let t := shl(240, 136) // `0b10001000 << 240`
            for { let i := 0 } 1 {} {
                mstore(add(i, i), mul(t, byte(i, hashed)))
                i := add(i, 1)
                if eq(i, 20) { break }
            }
            mstore(o, xor(mload(o), shr(1, and(mload(0x00), and(mload(o), mask)))))
            o := add(o, 0x20)
            mstore(o, xor(mload(o), shr(1, and(mload(0x20), and(mload(o), mask)))))
        }
    }

    /// @dev Returns the hexadecimal representation of `value`.
    /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte.
    function toHexString(address value) internal pure returns (string memory result) {
        result = toHexStringNoPrefix(value);
        /// @solidity memory-safe-assembly
        assembly {
            let n := add(mload(result), 2) // Compute the length.
            mstore(result, 0x3078) // Store the "0x" prefix.
            result := sub(result, 2) // Move the pointer.
            mstore(result, n) // Store the length.
        }
    }

    /// @dev Returns the hexadecimal representation of `value`.
    /// The output is encoded using 2 hexadecimal digits per byte.
    function toHexStringNoPrefix(address value) internal pure returns (string memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := mload(0x40)
            // Allocate memory.
            // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length,
            // 0x02 bytes for the prefix, and 0x28 bytes for the digits.
            // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x28) is 0x80.
            mstore(0x40, add(result, 0x80))
            mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup.

            result := add(result, 2)
            mstore(result, 40) // Store the length.
            let o := add(result, 0x20)
            mstore(add(o, 40), 0) // Zeroize the slot after the string.
            value := shl(96, value)
            // We write the string from rightmost digit to leftmost digit.
            // The following is essentially a do-while loop that also handles the zero case.
            for { let i := 0 } 1 {} {
                let p := add(o, add(i, i))
                let temp := byte(i, value)
                mstore8(add(p, 1), mload(and(temp, 15)))
                mstore8(p, mload(shr(4, temp)))
                i := add(i, 1)
                if eq(i, 20) { break }
            }
        }
    }

    /// @dev Returns the hex encoded string from the raw bytes.
    /// The output is encoded using 2 hexadecimal digits per byte.
    function toHexString(bytes memory raw) internal pure returns (string memory result) {
        result = toHexStringNoPrefix(raw);
        /// @solidity memory-safe-assembly
        assembly {
            let n := add(mload(result), 2) // Compute the length.
            mstore(result, 0x3078) // Store the "0x" prefix.
            result := sub(result, 2) // Move the pointer.
            mstore(result, n) // Store the length.
        }
    }

    /// @dev Returns the hex encoded string from the raw bytes.
    /// The output is encoded using 2 hexadecimal digits per byte.
    function toHexStringNoPrefix(bytes memory raw) internal pure returns (string memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(raw)
            result := add(mload(0x40), 2) // Skip 2 bytes for the optional prefix.
            mstore(result, add(n, n)) // Store the length of the output.

            mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup.
            let o := add(result, 0x20)
            let end := add(raw, n)
            for {} iszero(eq(raw, end)) {} {
                raw := add(raw, 1)
                mstore8(add(o, 1), mload(and(mload(raw), 15)))
                mstore8(o, mload(and(shr(4, mload(raw)), 15)))
                o := add(o, 2)
            }
            mstore(o, 0) // Zeroize the slot after the string.
            mstore(0x40, add(o, 0x20)) // Allocate memory.
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   RUNE STRING OPERATIONS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the number of UTF characters in the string.
    function runeCount(string memory s) internal pure returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            if mload(s) {
                mstore(0x00, div(not(0), 255))
                mstore(0x20, 0x0202020202020202020202020202020202020202020202020303030304040506)
                let o := add(s, 0x20)
                let end := add(o, mload(s))
                for { result := 1 } 1 { result := add(result, 1) } {
                    o := add(o, byte(0, mload(shr(250, mload(o)))))
                    if iszero(lt(o, end)) { break }
                }
            }
        }
    }

    /// @dev Returns if this string is a 7-bit ASCII string.
    /// (i.e. all characters codes are in [0..127])
    function is7BitASCII(string memory s) internal pure returns (bool result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := 1
            let mask := shl(7, div(not(0), 255))
            let n := mload(s)
            if n {
                let o := add(s, 0x20)
                let end := add(o, n)
                let last := mload(end)
                mstore(end, 0)
                for {} 1 {} {
                    if and(mask, mload(o)) {
                        result := 0
                        break
                    }
                    o := add(o, 0x20)
                    if iszero(lt(o, end)) { break }
                }
                mstore(end, last)
            }
        }
    }

    /// @dev Returns if this string is a 7-bit ASCII string,
    /// AND all characters are in the `allowed` lookup.
    /// Note: If `s` is empty, returns true regardless of `allowed`.
    function is7BitASCII(string memory s, uint128 allowed) internal pure returns (bool result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := 1
            if mload(s) {
                let allowed_ := shr(128, shl(128, allowed))
                let o := add(s, 0x20)
                for { let end := add(o, mload(s)) } 1 {} {
                    result := and(result, shr(byte(0, mload(o)), allowed_))
                    o := add(o, 1)
                    if iszero(and(result, lt(o, end))) { break }
                }
            }
        }
    }

    /// @dev Converts the bytes in the 7-bit ASCII string `s` to
    /// an allowed lookup for use in `is7BitASCII(s, allowed)`.
    /// To save runtime gas, you can cache the result in an immutable variable.
    function to7BitASCIIAllowedLookup(string memory s) internal pure returns (uint128 result) {
        /// @solidity memory-safe-assembly
        assembly {
            if mload(s) {
                let o := add(s, 0x20)
                for { let end := add(o, mload(s)) } 1 {} {
                    result := or(result, shl(byte(0, mload(o)), 1))
                    o := add(o, 1)
                    if iszero(lt(o, end)) { break }
                }
                if shr(128, result) {
                    mstore(0x00, 0xc9807e0d) // `StringNot7BitASCII()`.
                    revert(0x1c, 0x04)
                }
            }
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   BYTE STRING OPERATIONS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    // For performance and bytecode compactness, byte string operations are restricted
    // to 7-bit ASCII strings. All offsets are byte offsets, not UTF character offsets.
    // Usage of byte string operations on charsets with runes spanning two or more bytes
    // can lead to undefined behavior.

    /// @dev Returns `subject` all occurrences of `needle` replaced with `replacement`.
    function replace(string memory subject, string memory needle, string memory replacement)
        internal
        pure
        returns (string memory)
    {
        return string(LibBytes.replace(bytes(subject), bytes(needle), bytes(replacement)));
    }

    /// @dev Returns the byte index of the first location of `needle` in `subject`,
    /// needleing from left to right, starting from `from`.
    /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
    function indexOf(string memory subject, string memory needle, uint256 from)
        internal
        pure
        returns (uint256)
    {
        return LibBytes.indexOf(bytes(subject), bytes(needle), from);
    }

    /// @dev Returns the byte index of the first location of `needle` in `subject`,
    /// needleing from left to right.
    /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
    function indexOf(string memory subject, string memory needle) internal pure returns (uint256) {
        return LibBytes.indexOf(bytes(subject), bytes(needle), 0);
    }

    /// @dev Returns the byte index of the first location of `needle` in `subject`,
    /// needleing from right to left, starting from `from`.
    /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
    function lastIndexOf(string memory subject, string memory needle, uint256 from)
        internal
        pure
        returns (uint256)
    {
        return LibBytes.lastIndexOf(bytes(subject), bytes(needle), from);
    }

    /// @dev Returns the byte index of the first location of `needle` in `subject`,
    /// needleing from right to left.
    /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
    function lastIndexOf(string memory subject, string memory needle)
        internal
        pure
        returns (uint256)
    {
        return LibBytes.lastIndexOf(bytes(subject), bytes(needle), type(uint256).max);
    }

    /// @dev Returns true if `needle` is found in `subject`, false otherwise.
    function contains(string memory subject, string memory needle) internal pure returns (bool) {
        return LibBytes.contains(bytes(subject), bytes(needle));
    }

    /// @dev Returns whether `subject` starts with `needle`.
    function startsWith(string memory subject, string memory needle) internal pure returns (bool) {
        return LibBytes.startsWith(bytes(subject), bytes(needle));
    }

    /// @dev Returns whether `subject` ends with `needle`.
    function endsWith(string memory subject, string memory needle) internal pure returns (bool) {
        return LibBytes.endsWith(bytes(subject), bytes(needle));
    }

    /// @dev Returns `subject` repeated `times`.
    function repeat(string memory subject, uint256 times) internal pure returns (string memory) {
        return string(LibBytes.repeat(bytes(subject), times));
    }

    /// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive).
    /// `start` and `end` are byte offsets.
    function slice(string memory subject, uint256 start, uint256 end)
        internal
        pure
        returns (string memory)
    {
        return string(LibBytes.slice(bytes(subject), start, end));
    }

    /// @dev Returns a copy of `subject` sliced from `start` to the end of the string.
    /// `start` is a byte offset.
    function slice(string memory subject, uint256 start) internal pure returns (string memory) {
        return string(LibBytes.slice(bytes(subject), start, type(uint256).max));
    }

    /// @dev Returns all the indices of `needle` in `subject`.
    /// The indices are byte offsets.
    function indicesOf(string memory subject, string memory needle)
        internal
        pure
        returns (uint256[] memory)
    {
        return LibBytes.indicesOf(bytes(subject), bytes(needle));
    }

    /// @dev Returns a arrays of strings based on the `delimiter` inside of the `subject` string.
    function split(string memory subject, string memory delimiter)
        internal
        pure
        returns (string[] memory result)
    {
        bytes[] memory a = LibBytes.split(bytes(subject), bytes(delimiter));
        /// @solidity memory-safe-assembly
        assembly {
            result := a
        }
    }
\

Tags:
Proxy, Upgradeable, Factory|addr:0xb24be3bc4f479d5fc3c193f72781927306d3a83d|verified:true|block:23694738|tx:0xc6ac3bac5573ffd068ed666aa731b5df29c3ccf999d22026509f2fa9bc5a3d51|first_check:1761908647

Submitted on: 2025-10-31 12:04:07

Comments

Log in to comment.

No comments yet.