ProtocolitesRendererAnimated

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/ProtocolitesRendererAnimated.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 ProtocolitesRendererAnimated
 * @notice Advanced renderer with canvas-based animations and temperament system
 * @dev Based on the V_ANIMATED HTML template with per-attribute animations
 */
contract ProtocolitesRendererAnimated 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))));
    }

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

        // HTML with canvas animation
        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>',
            '@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@100;200;300;400&display=swap");',
            '*{margin:0;padding:0;box-sizing:border-box;}',
            'body{font-family:"JetBrains Mono","Courier New",monospace;background:#fff;color:#000;',
            'display:flex;align-items:center;justify-content:center;min-height:100vh;overflow:hidden;}',
            'canvas{image-rendering:crisp-edges;image-rendering:pixelated;transform:scale(3.5);transform-origin:center;}',
            '</style></head><body>',
            '<canvas id="c"></canvas>',
            '<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), '"}') : '',
            ']'
        );

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

        return json;
    }

    // Core DNA parsing and family selection
    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 n=BigInt(d);const h=Number((n>>17n)%16777216n);',
            'const fams=Object.keys(families);return fams[h%fams.length];}',
            'const family=getFamilyFromDNA(dna);const familyColor=families[family];',
            'function random(){seed=(seed*9301+49297)%233280;return seed/233280;}let seed=hashCode(dna);'
        );
    }

    // DNA decoding with temperament
    function getScript2() private pure returns (string memory) {
        return string.concat(
            'function decodeDNA(d){const n=BigInt(d);',
            'const temps=["calm","balanced","energetic","chaotic","glitchy","unstable"];',
            'const tempWeights=[35,30,20,10,4,1];let totalW=100;',
            'const tempIdx=Number((n>>20n)&7n)%6;',
            'return{',
            'bodyType:["square","round","diamond","mushroom","invader","ghost"][Number((n>>0n)&7n)%6],',
            'bodyChar:["\\u2588","\\u2593","\\u2592","\\u2591"][Number((n>>3n)&3n)],',
            'eyeChar:["\\u25cf","\\u25c9","\\u25ce","\\u25cb"][Number((n>>5n)&3n)],',
            'eyeSize:Number((n>>7n)&1n)===1?"mega":"normal",',
            'antennaTip:["\\u25cf","\\u25c9","\\u25cb","\\u25ce","\\u2726","\\u2727","\\u2605"][Number((n>>8n)&7n)%7],',
            'armStyle:Number((n>>11n)&1n)===1?"line":"block",',
            'legStyle:Number((n>>12n)&1n)===1?"line":"block",',
            'hatType:["none","top","flat","double","fancy"][Number((n>>13n)&7n)%5],',
            'hasCig:Number((n>>16n)&1n)===1,',
            'temperament:temps[tempIdx]};}',
            'const dnaObj=decodeDNA(dna);'
        );
    }

    // Grid generation with type tracking
    function getScript3() private pure returns (string memory) {
        return string.concat(
            'const grid=Array(size).fill().map(()=>Array(size).fill().map(()=>({char:" ",type:"empty"})));',
            'const cx=Math.floor(size/2);const cy=Math.floor(size/2);',
            'const bodyWidth=size===24?6:3;const bodyHeight=size===24?8:4;const bodyStartY=size===24?7:6;',
            'const bodyType=dnaObj.bodyType;',
            '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 d=Math.sqrt(relX*relX+relY*relY);inBody=d<=1.0;}',
            'else if(bodyType==="diamond")inBody=Math.abs(relX)+Math.abs(relY)<=1.0;',
            'else if(bodyType==="mushroom"){if(isKid){inBody=relY<-0.2?true:Math.abs(relX)<=0.7;}',
            'else{inBody=relY<0?true: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 dg=Math.sqrt(relX*relX+relY*relY);',
            'if(relY<0.5)inBody=dg<=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"};}}'
        );
    }

    // Eyes with type marking
    function getScript4() private pure returns (string memory) {
        return string.concat(
            'const isBodyChar=c=>c&&c.type==="body";const eyeY=bodyStartY+1;',
            'if(isKid){const ec=1+Math.floor(random()*3);',
            'if(ec===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(ec===2){const es=1;for(let dy=0;dy<2;dy++)for(let dx=0;dx<2;dx++){',
            'if(isBodyChar(grid[eyeY+dy][cx-es-1+dx]))grid[eyeY+dy][cx-es-1+dx]={char:" ",type:"empty"};',
            'if(isBodyChar(grid[eyeY+dy][cx+es+dx]))grid[eyeY+dy][cx+es+dx]={char:" ",type:"empty"};}',
            'grid[eyeY][cx-es-1]={char:dnaObj.bodyChar,type:"body"};grid[eyeY][cx-es]={char:dnaObj.eyeChar,type:"eye"};',
            'grid[eyeY+1][cx-es-1]={char:dnaObj.bodyChar,type:"body"};grid[eyeY+1][cx-es]={char:dnaObj.bodyChar,type:"body"};',
            'grid[eyeY][cx+es]={char:dnaObj.eyeChar,type:"eye"};grid[eyeY][cx+es+1]={char:dnaObj.bodyChar,type:"body"};',
            'grid[eyeY+1][cx+es]={char:dnaObj.bodyChar,type:"body"};grid[eyeY+1][cx+es+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 getScript5() private pure returns (string memory) {
        return string.concat(
            'const ec=1+Math.floor(random()*3);if(ec===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(ec===2){const bs=2;',
            'for(let dy=0;dy<3;dy++)for(let dx=0;dx<3;dx++){',
            'if(isBodyChar(grid[eyeY+dy][cx-bs-2+dx]))grid[eyeY+dy][cx-bs-2+dx]={char:" ",type:"empty"};',
            'if(isBodyChar(grid[eyeY+dy][cx+bs+dx]))grid[eyeY+dy][cx+bs+dx]={char:" ",type:"empty"};}',
            'for(let dy=0;dy<3;dy++)for(let dx=0;dx<3;dx++){const ic=dy===1&&dx===1;',
            'grid[eyeY+dy][cx-bs-2+dx]={char:ic?dnaObj.eyeChar:dnaObj.bodyChar,type:ic?"eye":"body"};',
            'grid[eyeY+dy][cx+bs+dx]={char:ic?dnaObj.eyeChar:dnaObj.bodyChar,type:ic?"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"};}}}',
            'if(random()>0.3){const my=eyeY+(isKid?2:3);if(my<size&&isBodyChar(grid[my][cx])){',
            'grid[my][cx]={char:"\\u2500",type:"mouth"};',
            'if(random()>0.5&&isBodyChar(grid[my][cx-1]))grid[my][cx-1]={char:"\\u2500",type:"mouth"};',
            'if(random()>0.5&&isBodyChar(grid[my][cx+1]))grid[my][cx+1]={char:"\\u2500",type:"mouth"};}}'
        );
    }

    // Cigarette, arms, legs
    function getScript6() private pure returns (string memory) {
        return string.concat(
            'if(dnaObj.hasCig){const cy=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&&cy>=0&&cy<size){grid[cy][cigX]={char:cigChar,type:"cig"};',
            'if(cigX+1<size)grid[cy][cigX+1]={char:"\\u2219",type:"cig"};}}',
            'const armCnt=1+Math.floor(random()*4);const armLen=isKid?(1+Math.floor(random()*2)):(2+Math.floor(random()*4));',
            'const armChar=dnaObj.armStyle==="block"?"\\u2588":"\\u2500";',
            'for(let a=0;a<armCnt;a++){const ay=bodyStartY+2+a*(isKid?1:2);if(ay>=bodyStartY+bodyHeight)break;',
            'let lbe=cx,rbe=cx;for(let x=cx;x>=0;x--)if(isBodyChar(grid[ay][x]))lbe=x;else break;',
            'for(let x=cx;x<size;x++)if(isBodyChar(grid[ay][x]))rbe=x;else break;',
            'for(let i=1;i<=armLen;i++){if(lbe-i>=0)grid[ay][lbe-i]={char:armChar,type:"arm"};',
            'if(rbe+i<size)grid[ay][rbe+i]={char:armChar,type:"arm"};}}',
            'const legCnt=1+Math.floor(random()*4);const legY=bodyStartY+bodyHeight;',
            'const legLen=isKid?(1+Math.floor(random()*2)):(2+Math.floor(random()*3));',
            'const legChar=dnaObj.legStyle==="block"?"\\u2588":"\\u2502";const bps=[];',
            'for(let x=0;x<size;x++)if(grid[legY-1]&&isBodyChar(grid[legY-1][x]))bps.push(x);',
            'const lps=[];if(bps.length>0){if(legCnt===1)lps.push(bps[Math.floor(bps.length/2)]);',
            'else if(legCnt===2){lps.push(bps[Math.floor(bps.length*0.25)]);lps.push(bps[Math.floor(bps.length*0.75)]);}',
            'else if(legCnt===3){lps.push(bps[0]);lps.push(bps[Math.floor(bps.length/2)]);lps.push(bps[bps.length-1]);}',
            'else{lps.push(bps[0]);lps.push(bps[Math.floor(bps.length*0.33)]);',
            'lps.push(bps[Math.floor(bps.length*0.66)]);lps.push(bps[bps.length-1]);}}',
            'for(let lx of lps)if(lx>=0&&lx<size)for(let i=0;i<legLen;i++)if(legY+i<size)grid[legY+i][lx]={char:legChar,type:"leg"};'
        );
    }

    // Antennas and hat
    function getScript7() private pure returns (string memory) {
        return string.concat(
            'const antCnt=1+Math.floor(random()*4);const antLen=isKid?1:(1+Math.floor(random()*2));const btp=[];',
            'for(let x=0;x<size;x++)if(grid[bodyStartY]&&isBodyChar(grid[bodyStartY][x]))btp.push(x);',
            'const aps=[];if(btp.length>0){if(antCnt===1)aps.push(btp[Math.floor(btp.length/2)]);',
            'else if(antCnt===2){aps.push(btp[Math.floor(btp.length*0.25)]);aps.push(btp[Math.floor(btp.length*0.75)]);}',
            'else if(antCnt===3){aps.push(btp[0]);aps.push(btp[Math.floor(btp.length/2)]);aps.push(btp[btp.length-1]);}',
            'else{aps.push(btp[0]);aps.push(btp[Math.floor(btp.length*0.33)]);',
            'aps.push(btp[Math.floor(btp.length*0.66)]);aps.push(btp[btp.length-1]);}}',
            'for(let ax of aps)for(let i=1;i<=antLen;i++){const ay=bodyStartY-i;',
            'if(ay>=0)grid[ay][ax]={char:i===antLen?dnaObj.antennaTip:"\\u2502",type:i===antLen?"ant-tip":"ant"};}',
            'if(dnaObj.hatType&&dnaObj.hatType!=="none"){const hy=bodyStartY-antLen-1;if(hy>=0){',
            'if(dnaObj.hatType==="top"){for(let dx=-2;dx<=2;dx++)if(cx+dx>=0&&cx+dx<size)grid[hy][cx+dx]={char:"\\u2580",type:"hat"};',
            'if(hy+1<size)grid[hy+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[hy][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&&hy-1>=0){',
            'grid[hy-1][cx+dx]={char:"\\u2580",type:"hat"};grid[hy][cx+dx]={char:"\\u2584",type:"hat"};}}',
            'else if(dnaObj.hatType==="fancy"&&cx-2>=0&&cx+2<size){grid[hy][cx-2]={char:"\\u2554",type:"hat"};',
            'grid[hy][cx-1]={char:"\\u2550",type:"hat"};grid[hy][cx]={char:"\\u2550",type:"hat"};',
            'grid[hy][cx+1]={char:"\\u2550",type:"hat"};grid[hy][cx+2]={char:"\\u2557",type:"hat"};}}}'
        );
    }

    // Canvas animation engine
    function getScript8() private pure returns (string memory) {
        return string.concat(
            'const canvas=document.getElementById("c");const ctx=canvas.getContext("2d");',
            'const fontSize=size===24?9:8;ctx.font=fontSize+"px \\"Courier New\\",monospace";',
            'ctx.textBaseline="top";ctx.textAlign="left";const metrics=ctx.measureText("\\u2588");',
            'const charW=metrics.width;const lineH=fontSize;canvas.width=size*charW;canvas.height=size*lineH;',
            'const tempCfg={calm:{spd:1.5,glitch:0.02,corrupt:0},balanced:{spd:2.5,glitch:0.05,corrupt:0},',
            'energetic:{spd:3.5,glitch:0.12,corrupt:0.03},chaotic:{spd:5,glitch:0.25,corrupt:0.08},',
            'glitchy:{spd:6,glitch:0.4,corrupt:0.15},unstable:{spd:7,glitch:0.6,corrupt:0.25}}[dnaObj.temperament]||{spd:2,glitch:0.05,corrupt:0};',
            'let time=0;const corruptChars=["\\u2588","\\u2593","\\u2592","\\u2591","\\u2580","\\u2584"];',
            'function animate(){ctx.clearRect(0,0,canvas.width,canvas.height);time+=tempCfg.spd*0.016;',
            'const isGlitch=Math.random()<tempCfg.glitch;',
            'for(let y=0;y<size;y++){let rowOff=0;',
            'if(isGlitch&&(dnaObj.temperament==="chaotic"||dnaObj.temperament==="glitchy"||dnaObj.temperament==="unstable")){',
            'if(Math.random()<0.1)rowOff=(Math.random()-0.5)*6;}',
            'for(let x=0;x<size;x++){const cell=grid[y][x];if(!cell||cell.char===" ")continue;',
            'let char=cell.char;const type=cell.type;',
            'if(isGlitch&&Math.random()<tempCfg.corrupt)char=corruptChars[Math.floor(Math.random()*corruptChars.length)];',
            'let posX=x*charW;let posY=y*lineH;'
        );
    }

    function getScript9() private pure returns (string memory) {
        return string.concat(
            'if(type==="body"){const breathe=Math.sin(time+x*0.1)*2;posY+=breathe;}',
            'else if(type==="eye"){if(Math.sin(time*0.5)<-0.95)char="\\u2500";',
            'if(dnaObj.temperament==="energetic"||dnaObj.temperament==="chaotic"||dnaObj.temperament==="glitchy"||dnaObj.temperament==="unstable")posX+=Math.sin(time*2+y)*1.5;}',
            'else if(type==="arm"){const wave=Math.sin(time*1.5+y*0.5)*3;posY+=wave;',
            'if(dnaObj.temperament==="chaotic"||dnaObj.temperament==="glitchy"||dnaObj.temperament==="unstable")posX+=Math.cos(time*2+x)*2;}',
            'else if(type==="leg"){const march=Math.sin(time*2+x*1.5)*2;posY+=march;}',
            'else if(type==="ant"||type==="ant-tip"){const wobble=Math.sin(time+x*0.3)*2;posX+=wobble;}',
            'else if(type==="mouth"){if(Math.sin(time*0.7)>0.7&&(dnaObj.temperament==="energetic"||dnaObj.temperament==="chaotic"||dnaObj.temperament==="glitchy"||dnaObj.temperament==="unstable"))char="\\u25cb";}',
            'if(isGlitch){const glitchMult=dnaObj.temperament==="unstable"?4:dnaObj.temperament==="glitchy"?3:dnaObj.temperament==="chaotic"?2:dnaObj.temperament==="energetic"?1.5:1;',
            'posX+=(Math.random()-0.5)*glitchMult;posY+=(Math.random()-0.5)*glitchMult;}',
            'posX+=rowOff;let color=familyColor;',
            'if(isGlitch&&(dnaObj.temperament==="chaotic"||dnaObj.temperament==="glitchy"||dnaObj.temperament==="unstable")){',
            'const colors=["#ff0000","#00ff00","#0000ff",familyColor];color=colors[Math.floor(Math.random()*colors.length)];}',
            'ctx.fillStyle=color;ctx.fillText(char,posX,posY);}}',
            'requestAnimationFrame(animate);}animate();'
        );
    }

    function defaultScript() private pure returns (string memory) {
        return string.concat(
            getScript1(),
            getScript2(),
            getScript3(),
            getScript4(),
            getScript5(),
            getScript6(),
            getScript7(),
            getScript8(),
            getScript9()
        );
    }
}
"
    },
    "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
        }
    }

    /// @dev Returns a concatenated string of `a` and `b`.
    /// Cheaper than `string.concat()` and does not de-align the free memory pointer.
    function concat(string memory a, string memory b) internal pure returns (string memory) {
        return string(LibBytes.concat(bytes(a), bytes(b)));
    }

    /// @dev Returns a copy of the string in either lowercase or UPPERCASE.
    /// WARNING! This function is only compatible with 7-bit ASCII strings.
    function toCase(string memory subject, bool toUpper)
        internal
        pure
        returns (string memory result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(subject)
            if n {
                result := mload(0x40)
                let o := add(result, 0x20)
                let d := sub(subject, result)
                let flags := shl(add(70, shl(5, toUpper)), 0x3ffffff)
                for { let end := add(o, n) } 1 {} {
                    let b := byte(0, mload(add(d, o)))
                    mstore8(o, xor(and(shr(b, flags), 0x20), b))
                    o := add(o, 1)
                    if eq(o, end) { break }
                }
                mstore(result, n) // Store the length.
                mstore(o, 0) // Zeroize the slot after the string.
                mstore(0x40, add(o, 0x20)) // Allocate memory.
            }
        }
    }

    /// @dev Returns a string from a small bytes32 string.
    /// `s` must be null-terminated, or behavior will be undefined.
    function fromSmallString(bytes32 s) internal pure returns (string memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := mload(0x40)
            let n := 0
            for {} byte(n, s) { n := add(n, 1) } {} // Scan for '\0'.
            mstore(result, n) // Store the length.
            let o := add(result, 0x20)
            mstore(o, s) // Store the bytes of the string.
            mstore(add(o, n), 0) // Zeroize the slot after the string.
            mstore(0x40, add(result, 0x40)) // Allocate memory.
        }
    }

    /// @dev Returns the small string, with all bytes after the first null byte zeroized.
    function normalizeSmallString(bytes32 s) internal pure returns (bytes32 result) {
        /// @solidity memory-safe-assembly
        assembly {
            for {} byte(result, s) { result := add(result, 1) } {} // Scan for '\0'.
            mstore(0x00, s)
            mstore(result, 0x00)
            result := mload(0x00)
        }
    }

    /// @dev Returns the string as a normalized null-terminated small string.
    function toSmallString(string memory s) internal pure returns (bytes32 result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := mload(s)
            if iszero(lt(result, 33)) {
                mstore(0x00, 0xec92f9a3) // `TooBigForSmallString()`.
                revert(0x1c, 0x04)
            }
            result := shl(shl(3, sub(32, result)), mload(add(s, result)))
        }
    }

    /// @dev Returns a lowercased copy of the string.
    /// WARNING! This function is only compatible with 7-bit ASCII strings.
    function lower(string memory subject) internal pure returns (string memory result) {
        result = toCase(subject, false);
    }

    /// @dev Returns an UPPERCASED copy of the string.
    /// WARNING! This function is only compatible with 7-bit ASCII strings.
    function upper(string memory subject) internal pure returns (string memory result) {
        result = toCase(subject, true);
    }

    /// @dev Escapes the string to be used within HTML tags.
    function escapeHTML(string memory s) internal pure returns (string memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := mload(0x40)
            let end := add(s, mload(s))
            let o := add(result, 0x20)
            // Store the bytes of the packed offsets and strides into the scratch space.
            // `packed = (stride << 5) | offset`. Max offset is 20. Max stride is 6.
            mstore(0x1f, 0x900094)
            mstore(0x08, 0xc0000000a6ab)
            // Store "&quot;&amp;&#39;&lt;&gt;" into the scratch space.
            mstore(0x00, shl(64, 0x2671756f743b26616d703b262333393b266c743b2667743b))
            for {} iszero(eq(s, end)) {} {
                s := add(s, 1)
                let c := and(mload(s), 0xff)
                // Not in `["\"","'","&","<",">"]`.
                if iszero(and(shl(c, 1), 0x500000c400000000)) {
                    mstore8(o, c)
                    o := add(o, 1)
                    continue
                }
                let t := shr(248, mload(c))
                mstore(o, mload(and(t, 0x1f)))
                o := add(o, shr(5, t))
            }
            mstore(o, 0) // Zeroize the slot after the string.
            mstore(result, sub(o, add(result, 0x20))) // Store the length.
            mstore(0x40, add(o, 0x20)) // Allocate memory.
        }
    }

    /// @dev Escapes the string to be used within double-quotes in a JSON.
    /// If `addDoubleQuotes` is true, the result will be enclosed in double-quotes.
    function escapeJSON(string memory s, bool addDoubleQuotes)
        internal
        pure
        returns (string memory result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            result := mload(0x40)
            let o := add(result, 0x20)
            if addDoubleQuotes {
                mstore8(o, 34)
                o := add(1, o)
            }
            // Store "\\u0000" in scratch space.
            // Store "0123456789abcdef" in scratch space.
            // Also, store `{0x08:"b", 0x09:"t", 0x0a:"n", 0x0c:"f", 0x0d:"r"}`.
            // into the scratch space.
            mstore(0x15, 0x5c75303030303031323334353637383961626364656662746e006672)
            // Bitmask for detecting `["\"","\\"]`.
            let e := or(shl(0x22, 1), shl(0x5c, 1))
            for { let end := add(s, mload(s)) } iszero(eq(s, end)) {} {
                s := add(s, 1)
                let c := and(mload(s), 0xff)
                if iszero(lt(c, 0x20)) {
                    if iszero(and(shl(c, 1), e)) {
                        // Not in `["\"","\\"]`.
                        mstore8(o, c)
                        o := add(o, 1)
                        continue
                    }
                    mstore8(o, 0x5c) // "\\".
                    mstore8(add(o, 1), c)
                    o := add(o, 2)
                    continue
                }
                if iszero(and(shl(c, 1), 0x3700)) {
                    // Not in `["\b","\	","\
","\f","\d"]`.
                    mstore8(0x1d, mload(shr(4, c))) // Hex value.
                    mstore8(0x1e, mload(and(c, 15))) // Hex value.
                    mstore(o, mload(0x19)) // "\\u00XX".
                    o := add(o, 6)
                    continue
                }
                mstore8(o, 0x5c) // "\\".
                mstore8(add(o, 1), mload(add(c, 8)))
                o := add(o, 2)
            }
            if addDoubleQuotes {
                mstore8(o, 34)
                o := add(1, o)
            }
            mstore(o, 0) // Zeroize the slot after the string.
            mstore(result, sub(o, add(result, 0x20))) // Store the length.
            mstore(0x40, add(o, 0x20)) // Allocate memory.
        }
    }

    /// @dev Escapes the string to be used within double-quotes in a JSON.
    function escapeJSON(string memory s) internal pure returns (string memory result) {
        result = escapeJSON(s, false);
    }

    /// @dev Encodes `s` so that it can be safely used in a URI,
    /// just like `encodeURIComponent` in JavaScript.
    /// See: https://developer.m

Tags:
Proxy, Upgradeable, Factory|addr:0xbf0e58cc63b3ab2feac7301532c936ef172d7057|verified:true|block:23617971|tx:0xc0680ad8fc2aa5baf92bbd7bf317b70ee4e266fc5dbe739ac35743d600df9750|first_check:1760964826

Submitted on: 2025-10-20 14:53:47

Comments

Log in to comment.

No comments yet.