Blockchains like Ethereum Virtual Machine “EVM” systems have two important library management elements.
Unique identifier of a record with hash pseudo-randomness; and
public-private keypair signatures execute record-keeping computation.
In a traditional client-server architecture, the server holds the private key secret, not the client:
+--------+ +----------+ +---------+
| client |------>| server |------>| database|
+--------+ +----------+ +---------+
| | |
| request | |
|------------------>| |
| | |
| | query |
| |--------------->|
| | |
| | |
| | |
| | result |
| |<---------------|
| | |
But in an EVM architecture, the Externally Owned Account “EOA” wallet within the client’s browser holds the private key secret:
+----------+ +-------------+ +----------+
| client | | signed-EOA | | RPC-URL |
+----------+ +-------------+ +----------+
| | |
| request | |
|------------------->| |
| | |
| | query |
| |------------------->|
| | |
| | |
| | |
| | result |
| |<-------------------|
| | |
EVM architecture runs on Von Neumann architecture:
+---------------------------------+
| Central Processing Unit |
| +-----------------------------+ |
| | Arithmetic Logic Unit (ALU)| |
| +-----------------------------+ |
| | Control Unit | |
| +-----------------------------+ |
+---------------------------------+
|
v
+---------------------------------+
| Memory |
| +-----------------------------+ |
| | Data Memory | |
| +-----------------------------+ |
| | Program Memory | |
| +-----------------------------+ |
+---------------------------------+
|
v
+---------------------------------+
| Input/Output Devices |
| |
| Keyboard, Mouse, Monitor, |
| Printer, etc. |
+---------------------------------+
|
v
+---------------------------------+
| Control Bus and Data Bus |
| +-----------------------------+ |
| | Control Bus | |
| +-----------------------------+ |
| | Data Bus | |
| +-----------------------------+ |
+---------------------------------+
In a client-sovereignty model, the client runs the program and the EVM is there as the persistent data-memory & program-memory systems.
Onchain JavaScript Object Notation “JSON” is the client-sovereign persistent data-memory; and
onchain HyperText Markup Language “HTML” is the client-sovereign persistent program-memory.
The GitHub repository for the data-memory, LIFO-JSON promises, program-memory and dev environment topics below:
The jsonState.sol
smart-contract is a JSON object with Admin-only write control.
/**
*Submitted for verification at sepolia-optimism.etherscan.io on 2023-12-27
*/
// SPDX-License-Identifier: MIT
// by besta.pe
pragma solidity ^0.8.19
;
contract jsonState {
function KeyValue(
string[] calldata key
, string[] calldata value
, bool newKeysOnly
) external {
ifAdmin()
; uint count
; Change = false
; Replace = true
;
if ( key.length != value.length ) {
revert KeyValueNotBijective()
;
}
if ( newKeysOnly ){
Replace = false
;
}
while( count < key.length ) {
addKeyValue(
key[ count ]
, value[ count ]
)
; count ++
;
}
if ( ! Change ) {
revert NoChange()
;
}
}
function JSON() public view returns (
string memory
) {
if ( KeyCountPlus1 == 0 ) {
revert NoKey()
;
}
uint count
; string memory json = "{"
;
while ( count < KeyCountPlus1 ) {
string memory key = keyIndex[ count ].key
; uint version = keyIndex[ count ].version
; string memory value = keyValue[
key ][ version
].value
; json = string.concat(
json
, "\""
, key
, "\":\""
, value
, "\""
)
;
if ( count < KeyCountPlus1 -1 ) {
json = string.concat(
json
, ","
)
;
}
count++
;
}
return string.concat(
json
, "}"
)
;
}
function LatestVersion(
string calldata key
) public view returns ( uint ) {
uint count
;
if ( KeyCountPlus1 == 0 ) {
revert NoKey()
;
}
while ( count < KeyCountPlus1 ) {
if (
keccak256( bytes( key ) )
== keccak256(
bytes( keyIndex[ count ].key )
)
) {
return keyIndex[ count ].version
;
}
count ++
;
}
revert NoKey()
;
}
function TotalKeys() public view returns (
uint
) {
if ( KeyCountPlus1 == 0 ) {
revert NoKey()
;
}
return KeyCountPlus1
;
}
function Value(
string calldata key
, uint version
) public view returns ( string memory ) {
string memory value = keyValue[
key ][ version
].value
;
if (
keccak256( bytes( value ) )
== keccak256( bytes( "" ) )
) {
revert NoValue()
;
}
return value
;
}
function addKey( string calldata key ) internal {
KeyCountPlus1 ++
; keyIndex[ KeyCountPlus1 -1 ] = keyMap(
key
, 0
)
;
}
function addKeyValue(
string calldata key
, string calldata value
) internal {
if (
keccak256( bytes( key ) )
== keccak256( bytes( "" ) )
) {
revert NoKey()
;
}
if (
keccak256( bytes( value ) )
== keccak256( bytes( "" ) )
) {
revert NoValue()
;
}
(
uint index
, uint version
) = newKeyTest( key )
;
if ( version > 0 ) {
if (
keccak256( bytes( value ) )
== keccak256( bytes(
keyValue[ key ][ version -1 ].value
) )
) {
return
;
}
keyIndex[ index ].version += 1
;
}
keyValue[ key ][ version ] = versionMap( value )
; Change = true
;
}
function ifAdmin() internal view {
if ( msg.sender == Admin ) {
return
;
}
revert NotAdmin()
;
}
function newKeyTest(
string calldata key
) internal returns (
uint
, uint
) {
uint count
;
while ( count < KeyCountPlus1 ) {
if (
keccak256( bytes( key ) )
== keccak256(
bytes( keyIndex[ count ].key )
)
) {
if ( ! Replace ) {
revert NewKeysOnly()
;
}
return (
count
, keyIndex[ count ].version +1
)
;
}
count ++
;
}
addKey( key )
; Change = true
; return ( 0, 0 )
;
}
mapping(
uint => keyMap
) private keyIndex
; mapping(
string => mapping(
uint => versionMap
)
) private keyValue
;
struct versionMap {
string value
;
}
struct keyMap {
string key
; uint version
;
}
error KeyValueNotBijective()
; error NewKeysOnly()
; error NoChange()
; error NoKey()
; error NotAdmin()
; error NoValue()
; address Admin
; bool Change
; uint KeyCountPlus1
; bool Replace
;
constructor() {
Admin = 0x0D89421D6eec0A4385F95f410732186A2Ab45077
;
}
}
Each JSON object is a standalone published smart-contract so that it has a unique top-level hash address. E.g. this smart-contract JSON object at hash address 0x8dcbc12efe584e24592d07a81bd6f6450def1052:
Using host.js
to run index.html
in the GitHub repository above:
The JSON object uses a Last-In First-Out “LIFO” value versioning array so that the key:value pairs are updatable and immutable.
There is a problem in fully-web3 invoicing where too much of the result needs to be known by the request.
The JSON object’s LIFO value versioning can be used for promises similar to JavaScript promises.
For instance, this schema’s version 0 is the promise and version 1 is the return:
In the GitHub repository above, the index.html
HTML is a Document Object Model “DOM” small enough to store directly onchain as an iFrame NFT “iNFT” application:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JSON State UX</title>
<script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
#config, #json, form {
max-width: 90%;
margin: 0 auto;
padding: 10px;
}
form label {
display: block;
margin-bottom: 5px;
}
form input[type="text"] {
width: 100%;
padding: 8px;
margin-bottom: 10px;
box-sizing: border-box;
}
button {
display: block;
width: 100%;
padding: 10px;
background-color: #007bff;
color: #fff;
border: none;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
pre {
white-space: pre-wrap;
}
</style>
</script>
</head>
<body>
<div id="config">
<div id="abi" style="display: none"></div>
</div>
<div id="json"></div>
<form>
<label for="key">contract
<input type="text" id="contract">
</label>
<br />
<label for="key">key
<input type="text" id="key" value='[ "string" ]'>
</label>
<br />
<label for="key">value
<input type="text" id="value" value='[ "string" ]'>
</label>
<br />
<label for="key">newKeysOnly
<input type="text" id="newKeysOnly" value='true'>
</label>
</form>
<button id="read">read</button>
<br />
<button id="write">write</button>
<script>
( function config() {
_0 = new Config
; _0.div.abi.innerHTML = JSON
.stringify(
_0.abi
)
; _0.input.contract.value = JSON
.stringify(
_0.jsonState
)
;
function Config() {
this.abi = [
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "KeyValueNotBijective",
"type": "error"
},
{
"inputs": [],
"name": "NewKeysOnly",
"type": "error"
},
{
"inputs": [],
"name": "NoChange",
"type": "error"
},
{
"inputs": [],
"name": "NoKey",
"type": "error"
},
{
"inputs": [],
"name": "NoValue",
"type": "error"
},
{
"inputs": [],
"name": "NotAdmin",
"type": "error"
},
{
"inputs": [],
"name": "JSON",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string[]",
"name": "key",
"type": "string[]"
},
{
"internalType": "string[]",
"name": "value",
"type": "string[]"
},
{
"internalType": "bool",
"name": "newKeysOnly",
"type": "bool"
}
],
"name": "KeyValue",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "key",
"type": "string"
}
],
"name": "LatestVersion",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TotalKeys",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "key",
"type": "string"
},
{
"internalType": "uint256",
"name": "version",
"type": "uint256"
}
],
"name": "Value",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
]
; this.div = {}
; this.div.abi = document
.getElementById( 'abi' )
; this.input = {}
; this.input.contract = document
.getElementById( 'contract' )
; this.jsonState = '0x0F2c3af6B686d9a8e67b17FD3888D48df57dFBD2'
;
}
} )()
</script>
<script>
( function read() {
async function getContract() {
await _0.ethereum
.request( {
method: 'eth_requestAcco'
+ 'unts'
} )
; _0.next()
;
}
function next() {
_0.contract = new _0.web3.eth
.Contract( _0.abi, _0.jsonState )
; _0.contract.methods.JSON().call()
.then( ( $0 ) => {
_0.json = $0.replace(
/"{/g
, "{"
)
; _0.json = _0.json.replace(
/}"/g
, "}"
)
; console.log( _0.json )
; _0.div.json.innerHTML
= 'JSON:<pre>'
+ JSON.stringify(
JSON.parse( _0.json )
, null
, 2
)
+ '</pre>'
} )
;
}
const _0 = new Read
; _0.button.addEventListener(
'click', () => {
_0.abi = JSON.parse(
_0.div.abi.innerHTML
)
; _0.jsonState = JSON.parse(
_0.input.contract.value
)
; _0.click ++
;
if ( _0.click < 2 ) {
_0.getContract()
; return
;
}
_0.next()
;
}
)
;
function Read() {
this.abi = null
; this.button = document
.getElementById(
'read'
)
; this.click = 0
; this.contract = null
; this.div = {}
; this.div.abi = document
.getElementById( 'abi' )
; this.div.json = document
.getElementById( 'json' )
; this.ethereum = window.ethereum
; this.getContract = getContract
; this.input = {}
; this.input.contract = document
.getElementById( 'contract' )
; this.json = null
; this.jsonState = null
; this.next = next
; this.web3 = new Web3(
window.ethereum
)
;
}
} )()
</script>
<script>
( function Write() {
async function getContract() {
_0.address = (
await _0.ethereum
.request( {
method: 'eth_requestAcco'
+ 'unts'
} )
)[ 0 ]
; _0.next()
;
}
function next() {
_0.contract = new _0.web3.eth
.Contract( _0.abi, _0.jsonState )
; _0.contract.methods.KeyValue(
JSON.parse( _0.input.key.value )
, JSON.parse(
_0.input.value.value
)
, _0.input.newKeysOnly.value
== "false" ? false : true
).send( { from: _0.address } )
.then( () => {
_0.read.click()
;
} )
;
}
const _0 = new Write
; _0.button.addEventListener(
'click', () => {
_0.abi = JSON.parse(
_0.div.abi.innerHTML
)
; _0.jsonState = JSON.parse(
_0.input.contract.value
)
; _0.click ++
;
if ( _0.click < 2 ) {
_0.getContract()
; return
;
}
_0.next()
;
}
)
;
function Write() {
this.abi = null
; this.address = null
; this.button = document
.getElementById(
'write'
)
; this.click = 0
; this.contract = null
; this.div = {}
; this.div.abi = document
.getElementById( 'abi' )
; this.ethereum = window.ethereum
; this.getContract = getContract
; this.input = {}
; this.input.contract = document
.getElementById( 'contract' )
; this.input.key = document
.getElementById(
'key'
)
; this.input.value = document
.getElementById(
'value'
)
; this.input.newKeysOnly = document
.getElementById(
'newKeysOnly'
)
; this.jsonState = null
; this.next = next
; this.read = document
.getElementById(
'read'
)
; this.web3 = new Web3(
window.ethereum
)
;
}
} )()
</script>
</body>
</html>
Note that on most platforms, iNFTs do not have full permissions because the iNFT has limited access to the platform’s DOM.
With the help of AI, it does not take long to draft simple iNFT reports from a jsonState.sol object:
KERNEL’s https://sign.kernel.community is the origin point.
https://ape.mirror.xyz has expanded KERNEL’s innovations by exploring legal seal and interface NFT concepts.