This is the final version of proof of concept 3.5. It shows a template driven HTML generator using all on chain assets, generated all onchain, and displayed directly from a call on this web page to an eth node.
The problem I am trying to solve is onchain composition of code libraries that can also be extended after the main template is deployed. I have used plain old javascript, a compiled dart library for the drunken bishop math that turns a hash into a unique sparse matrix, and an HTML template so as to prove the general case (ie. this is not a CNFT - it is plain HTML to show that this is a generally useful way to do onchain code deployment and composition. The assembler code is standard EVM assembler put through the 4th.energy EVM assembler tool). The original HTML project I used as the basis for this POC is explained here.
The ideas developed in this POC will be carried forward into work for onchain 4th.Energy code deployment, composition and library management.
What you are seeing here is an HTML page generated all onchain and displayed in an iframe. The iframe content is fetched directly from an "eth_call" from this web page as per POC3.
Press "Next" to see the next random hash. If you enter any plain old text into the edit box and press "View", we hash and display it for you and show you the resulting image. There are 7 draw algorithms - click Next a few times to see.
This POC will work on any device because it's all generated on chain and returned as an SVG inside an HTML page.
The proof of concept introduces some tools and techniques:
This was quite a complex project and it is beyond a simple code walk through, however the main components are shown below. The are:
{ "code-list": { "draw.e4ml": [ "0x...contract code...", "${user-hash}", "0x...contract code...", "${make-chip}", "0x...contract code...", "${draw-chip}", "0x...contract code..." ], "draw.js": [ "0x...contract code...[22k]", "0x...contract code...[22k]", "0x...contract code...[22k]", "0x...[remainder]" ], "gray.js": [ "0x...contract code..." ], "grid.js": [ "0x...contract code..." ], "text.js": [ "0x...contract code..." ], "cols.js": [ "0x...contract code..." ], "dots.js": [ "0x...contract code..." ], "line.js": [ "0x...contract code..." ], "spot.js": [ "0x...contract code..." ] }, "hash-list": { "draw.e4ml": [ "0x1aC902DfB6A2a12AB5E5f881c4c75C9D11FAe2A2", "0x8f509DA0F29690102A1652213B732b5aF39508d9", "0xECbB2B73a73cFd5C1f96A06c5c9b406929752Ddd", "0x25dB6AF57Bf91C20A8d400873eFd0BCe69333E86" ], "draw.js": [ "0x31447132A5c12f19e94525b4183986f2430a31bE", "0x0e3cD80a3F4b47035bE49a47df2155cc4182A08B", "0xA10ca7e46f486DdEEFa4ccb222db881e00F7b45C", "0x0AE8C3374BD9C09d7321a2b37281197B56390a2D" ], "gray.js": ["0xC28c48D72ab450b127e774306Ca08e9712e48F8c"], "grid.js": ["0x8dF9Db4C8880f074adDaB5C4D4EDBFB8a706FC55"], "text.js": ["0x8F4e3A23b6F7c7249F72e2d212a0d6281a5972E8"], "cols.js": ["0xd998627Fb834df02A8F9AA27b4680d5DD140400e"], "dots.js": ["0xd7eE9883240B26EC210fcB917eaBfA62D8d9b38E"], "line.js": ["0x89EbF11d92174269f2879362CBd720f8aA3CbdcB"], "spot.js": ["0x64EdCC7af6C16E903bbB33c0bc2d220b5e38DaE1"] } }
# # 4th.energy on-chain HTML POC # # A contract that takes the user hash and draw file selection contract hash # as call data and pulls it all together. # # Written using the 4th Energy EVM macro assembler. # PUSH2 &main JUMP # # function - make the call with what's on the stack and return the data into memory # :callCopy JUMPDEST # TOS -> arg count size and contract # Unknown ret size PUSH1 00 # Return buffer is current data size @ C0 + E0 address PUSH1 C0 MLOAD PUSH1 E0 ADD # Call # Arg size is arg count at 20 chars. Push on 00 to swap PUSH1 00 SWAP4 PUSH1 14 MUL # Arg mem offset address is 00 PUSH1 00 # Zero value send PUSH1 00 # Contract address arg is now at 6 - Push on 00 to swap PUSH1 00 SWAP6 # All our gas & call GAS CALL # Just assume it works so drop status code POP # First copy the first size word to the buffer # Return buffer is current data size @ 0x120 + 0x140 address PUSH1 C0 MLOAD PUSH1 E0 ADD DUP1 DUP1 # Load first word size PUSH1 00 PUSH1 20 SWAP2 RETURNDATACOPY # Load the size MLOAD DUP1 # Save it PUSH1 C0 MLOAD ADD PUSH1 C0 MSTORE # Copy the data SWAP1 PUSH1 20 SWAP1 RETURNDATACOPY # Pop of 00 swap placeholders and return to return address POP POP JUMP # # function - convert the word at 0 to unicode text to be used as our emitted script hash. # :hashText JUMPDEST PUSH1 00 :hashLoop JUMPDEST DUP1 PUSH1 20 EQ PUSH2 &doneLoop JUMPI DUP1 PUSH1 01 ADD SWAP1 # Last index value is now TOS - get byte at index PUSH1 00 MLOAD SWAP1 BYTE # The byte has to be made into two printable chars - 4 bytes. # first get first and second nibbles on stack DUP1 PUSH1 04 SHR SWAP1 PUSH1 0F AND # we basically add NIBBLE GT * 57 + NIBBLE LT 30 to find the right offset to add DUP1 PUSH1 09 SWAP1 GT PUSH1 57 MUL DUP1 PUSH1 09 SWAP1 LT PUSH1 30 MUL ADD ADD SWAP1 DUP1 PUSH1 09 SWAP1 GT PUSH1 57 MUL DUP1 PUSH1 09 SWAP1 LT PUSH1 30 MUL ADD ADD # put at current result memory location E0 + *C0 then add 2 to C0 PUSH1 C0 MLOAD PUSH1 E0 ADD MSTORE8 PUSH1 C0 MLOAD PUSH1 E1 ADD MSTORE8 PUSH1 C0 MLOAD PUSH1 02 ADD PUSH1 C0 MSTORE PUSH2 &hashLoop JUMP :doneLoop JUMPDEST # Pop index POP JUMP # # MAIN # :main JUMPDEST # Get the user hash call data into 00 PUSH1 14 PUSH1 00 PUSH1 00 CALLDATACOPY # Put the draw chips into memory for args from 20 # start accumulator PUSH1 14 # DUP1 PUSH20 ${cols.js.0} PUSH1 0C PUSH1 08 MUL SHL SWAP1 MSTORE PUSH1 14 ADD DUP1 PUSH20 ${dots.js.0} PUSH1 0C PUSH1 08 MUL SHL SWAP1 MSTORE PUSH1 14 ADD DUP1 PUSH20 ${gray.js.0} PUSH1 0C PUSH1 08 MUL SHL SWAP1 MSTORE PUSH1 14 ADD DUP1 PUSH20 ${grid.js.0} PUSH1 0C PUSH1 08 MUL SHL SWAP1 MSTORE PUSH1 14 ADD DUP1 PUSH20 ${line.js.0} PUSH1 0C PUSH1 08 MUL SHL SWAP1 MSTORE PUSH1 14 ADD DUP1 PUSH20 ${spot.js.0} PUSH1 0C PUSH1 08 MUL SHL SWAP1 MSTORE PUSH1 14 ADD DUP1 PUSH20 ${text.js.0} PUSH1 0C PUSH1 08 MUL SHL SWAP1 MSTORE PUSH1 14 ADD # remove accumulator POP # CO for output size accumlator, and E0 as start of output. # Set output size to 0 PUSH1 00 PUSH1 C0 MSTORE # 0. main html frag PUSH2 &draw.e4ml.0.done PUSH1 00 PUSH20 ${draw.e4ml.0} PUSH2 &callCopy JUMP :draw.e4ml.0.done JUMPDEST # Convert user hash to unicode, add to data PUSH2 &doneHash PUSH2 &hashText JUMP :doneHash JUMPDEST # 1. main html frag PUSH2 &draw.e4ml.1.done PUSH1 00 PUSH20 ${draw.e4ml.1} PUSH2 &callCopy JUMP :draw.e4ml.1.done JUMPDEST # Each of our js draw library file PUSH2 &draw.js.0.done PUSH1 00 PUSH20 ${draw.js.0} PUSH2 &callCopy JUMP :draw.js.0.done JUMPDEST PUSH2 &draw.js.1.done PUSH1 00 PUSH20 ${draw.js.1} PUSH2 &callCopy JUMP :draw.js.1.done JUMPDEST PUSH2 &draw.js.2.done PUSH1 00 PUSH20 ${draw.js.2} PUSH2 &callCopy JUMP :draw.js.2.done JUMPDEST PUSH2 &draw.js.3.done PUSH1 00 PUSH20 ${draw.js.3} PUSH2 &callCopy JUMP :draw.js.3.done JUMPDEST PUSH2 &draw.js.4.done PUSH1 00 PUSH20 ${draw.js.4} PUSH2 &callCopy JUMP :draw.js.4.done JUMPDEST PUSH2 &draw.js.5.done PUSH1 00 PUSH20 ${draw.js.5} PUSH2 &callCopy JUMP :draw.js.5.done JUMPDEST PUSH2 &draw.js.6.done PUSH1 00 PUSH20 ${draw.js.6} PUSH2 &callCopy JUMP :draw.js.6.done JUMPDEST PUSH2 &draw.js.7.done PUSH1 00 PUSH20 ${draw.js.7} PUSH2 &callCopy JUMP :draw.js.7.done JUMPDEST # 2. main html frag PUSH2 &draw.e4ml.2.done PUSH1 00 PUSH20 ${draw.e4ml.2} PUSH2 &callCopy JUMP :draw.e4ml.2.done JUMPDEST # Chip frag selector PUSH2 &frag.done # get the first word as our random number and mod by 7 PUSH1 07 PUSH1 00 MLOAD MOD # add 1 (user address) + mod * 20 (14) to get address PUSH1 01 ADD PUSH1 14 MUL MLOAD PUSH1 0C PUSH1 08 MUL SHR PUSH1 00 SWAP1 PUSH2 &callCopy JUMP :frag.done JUMPDEST # 3. main html frag PUSH2 &draw.e4ml.3.done PUSH1 00 PUSH20 ${draw.e4ml.3} PUSH2 &callCopy JUMP :draw.e4ml.3.done JUMPDEST # Main returns PUSH1 C0 MLOAD PUSH1 E0 RETURN
const spot = function(grid, rows, cols) { const hues = [ "#00db96", "#49297e", "#90dcff", "#e10086", "#fdfb76", ]; function set(node, name, text) { node.setAttributeNS(null, name, text); } var node = document.createElementNS("http://www.w3.org/2000/svg", "svg"); var size = 20; set(node, "viewBox", "0 0 " + (rows * size) + " " + (cols * size)); set(node, "width", (cols * size) + ""); set(node, "height", (rows * size) + ""); var r = 0; var s = 0; grid.forEach(row => { var c = 0; var x = r * size; r++; row.forEach(cell => { var y = c * size; c++; var ring = document.createElementNS("http://www.w3.org/2000/svg", "circle");; var rad = cell * size / 2; set(ring, 'cx', x); set(ring, 'cy', y); set(ring, 'r', rad); s += cell; ring.style.fill = hues[s % hues.length]; node.append(ring); }); }); return node; }; window['makeChip'] = spot;
async function showView() { var node = new ethers.providers.JsonRpcProvider('https://4th.energy/geth/test'); var data = await node.call({ to: '0x093F8F9e9D24Cde9e382b599dC364811cADAB256', data: ethers.utils.hashMessage(hash.value).substring(0, 42) }); chip.srcdoc = readByteText(data); }
A breather. The mathcastles testnet. An NFT mint process using the above. Xmas. And then some discussions on what to do next with the learning and 4th.energy