diff --git a/.gitignore b/.gitignore index 07ecc71..1ebc1df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.configs/ +.wallets/ +circom/ \*shell\* hardhat.config.js # Logs diff --git a/README.md b/README.md index 8a56aad..54afda5 100644 --- a/README.md +++ b/README.md @@ -1,353 +1,99 @@ -# WeaveDB Rollup Development Kit +# WeaveDB on AO -![](./assets/architecuture.png) +## Deployment -## How Rollup Works +You need to set up 5 components for local testing. -- A rollup node can have multiple DB instances. -- The admin user can add/update/remove DB instances. -- Once a DB is added, the gateway node dispatches rollup child process which contains an L2 DB, a WAL DB, and plugins DBs. All these DBs are offchain WeaveDB instances. -- If an L1 DB contract is deployed and rollup is enabled, the rollup process dispatches yet another child process for Warp SDK that keeps bundling L2 transactions and roll them up to the Warp sequencer in parallel. -- The L1 WeaveDB contract can receive bundle transactions in parallel in any order, but can resolve the correct order and compute the exact same state as the corresponding L2 contract. L2 transaction hashes are chaind in such a way that L1 contract can resolve L2 transaction order. -- When the WeaveDB Gateway receives an L2 query, it dispatches the query to the L2 DB on the rollup process, which triggers a WAL DB query, which in turn triggers offchain plugin DB queries. -- If the rollup node started without any local cache, it can recover the L2 DB state from the corresponding L1 contract. -- Users can query the DB directly on the L1 Warp contract, since L2 and L1 share the identical contract. The L1 delay is only a few seconds as the rollup flow takes less than 2 seconds. +- WAO Server +- WeaveDB on AO +- Rollup Node +- WeaveLayer on AO +- Frontend Demo -## Set up a Node - -### Prerequisites - -Install `docker` and `docker-compose` globally to your machine. And open port `8080` for anyone. - -### weavedb.config.js - -Create `weavedb.config.js` in `/grpc-node/node-server` directory. - -#### The minimum requirements - -- `admin` : an EVM private key for rollup node admin. The admin can add DBs to the node. -- `bundler` : Arweave RSA keys for rollup bundler. -The bundler will pay for rollup transactions and receive rewards from PoS in the future. -- `rollups` : this can be either empty or filled with pre-defined DB instances. Each key is the database name. - - `secure` : passed down to the contract initial-state, allways use `true` in production (default). - - `owner` : the DB contract owner EVM address. - - `tick` : time span in millisecond in which [tick](https://weavedb.vercel.app/docs/sdk/crons#tick) query will be periodically executed. - - `contractTxId` : Warp L1 contractTxId. - - `plugins` : add offchain plugins, plugin scripts have to be placed in `/grpc-node/node-server/plugins` with the same name. - - `rollup` : a bloolean value to enable rollup to Arweave/Warp - -```js title="/grpc-node/node-server/weavedb.config.js" -module.exports = { - admin: EVM_PRIVATE_KEY, - bundler: ARWEAVE_RSA_KEYS, - rollups: {}, -} -``` - -#### Other Parameters - -- `dir` : cache dirctory, default to `/grpc-node/node-server/cache`. -- `dbname` : cache database name, cache will be stored in `dir/dbname`. -- `sequencerUrl` : the Warp sequencer URL to use with SDK. -- `apiKey` : a Warp gateway API key to use with SDK. -- `arweave` : arweave network configuration -- `weavedb_srcTxId` : WeaveDB contract srcTxId -- `weavedb_version` : WeaveDB contract version -- `nostr` : enable WebSocket for Nostr, this turns the node into a Nostr relay. - - `db` : the database name for Nostr events, there can be only one DB instance to receive Nostr events. -- `snapshot` : config to store snapshots to GCP/S3. - - `count` : the number of bundles between snapshots (default to `100`). - - `gcp` : GCP configurations - - `bucket` : `[projectId].appspot.com` - - `keyFilename` : `gcs.json` - - `s3` : S3 configurations - -With everything included, - -```js title="/grpc-node/node-server/weavedb.config.js" -module.exports = { - dir: "/home/xyz/cache", - dbname: "mydb", - sequencerUrl: "https://gw.warp.cc/", - apiKey: "xxxxx", - snapshot:{ - count: 100, - gcs: { bucket: "xyz.appspot.com", keyFilename: "gcs.json" } - }, - admin: "privateky...", - arweave: { - host: "arweave.net", - port: 443, - protocol: "https", - }, - weavedb_srcTxId: "Ohr4AU6jRUCLoNSTTqu3bZ8GulKZ0V8gUm-vwrRbmS4", - weavedb_version: "0.37.2", - bundler: { - kty: "RSA", - ... - }, - nostr: { db: "nostr" }, - rollups: { - testdb: { - secure: true, - owner: "0xdef...", - tick: 1000 * 60 * 5, - contractTxId: "abcdef...", - rollup: true, - plugins: { notifications: {} }, - }, - nostr: { - owner: "0xdef...", - rollup: false, - } - }, -} -``` - -#### Auto Recovery - -If `contractTxId` is specified and the rollup node is re-initialized without cache, it will auto-recover the rollup DB state from Warp L1 transaction history. - -### Run docker-compose +### WAO Server (Local Arweave & AO Units) ```bash -yarn run-rollup -``` -### Admin Operations - -Anyone can access the rollup node stats, which returns the deployed DB information. - -```js -const DB = require("weavedb-node-client") -const db = new DB({ rpc: "localhost:8080", contractTxId: "testdb" }) -const stats = await db.node({op: "stats"}) -``` - -The admin EOA account can manage the rollup node and DBs from anywhere. - -#### Add DB - -```js -const tx = await db.admin( - { - op: "add_db", - key: "testdb2", - db: { - app: "http://localhost:3000", // this will be shown on the explorer - name: "Jots", // this will be shown on the explorer - rollup: true, - plugins: { notifications: {} }, - tick: 1000 * 60 * 5, - }, - }, - { privateKey: admin.privateKey } -) -``` -You can recover existing db with `contractTxId` after starting a new node. - -```js -const tx = await db.admin( - { - op: "add_db", - key: "testdb2", - db: { - app: "http://localhost:3000", // this will be shown on the explorer - name: "Jots", // this will be shown on the explorer - rollup: true, - plugins: { notifications: {} }, - tick: 1000 * 60 * 5, - contractTxId: "Warp_L1_contractTxId" - }, - }, - { privateKey: admin.privateKey } -) -``` - -#### Deploy Warp L1 Contract - -```js -const { contractTxId, srcTxId } = await db.admin( - { op: "deploy_contract", key: "testdb2" }, - { privateKey: admin.privateKey } -) -// you will need the "contractTxId" for regular DB queries +npx wao ``` -#### Update DB +Check that the following units are running. -```js -const tx = await db.admin( - { op: "deploy_contract", key: "testdb2" }, - { privateKey: admin.privateKey } -) -``` - -#### Remove DB - -```js -const tx = await db.admin( - { op: "remove_db", key: "testdb2" }, - { privateKey: admin.privateKey } -) -``` +- Arweave : [localhost:4000](http://localhost:4000) +- MU : [localhost:4002](http://localhost:4002) +- SU : [localhost:4003](http://localhost:4003) +- CU : [localhost:4004](http://localhost:4004) +### WeaveDB Processes on AO -#### Query DB +Clone the repo and install dependencies. -You will need the L1 `contractTxId` from the deployment operation to instantiate the DB client. -All L2 transactions will be signed with L1 `contractTxId` for L1/L2 verifiability. - -```js -const DB = require("weavedb-node-client") -const db = new DB({ rpc: "localhost:8080", contractTxId }) -const db_info = await db.getInfo() +```bash +git clone https://github.com/weavedb/weavedb-ao.git +cd weavedb-ao/lua && yarn +mkdir scripts/.wallets ``` +Under `scripts/.wallets`, prepare the following wallets. -### Plugins - -We currently have only one plugin for [Jots](https://weavedb.vercel.app/docs/get-started/jots#write-db-configurations) called `notifications` which generates personal notifications from onchain Jots activities. The notification DB will be an offchain WeaveDB instance, which won't be recorded onchain. Not every data should be onchain, and offchain plugins solve the problem. WeaveDB can seamlessly run in multiple environment such as blockchain, offchain (local), browser and centralized cloud. +- Owner ( `owner.json` | Arweave ) +- Bundler ( `bundler.json` | Arweave ) +- DB-Creator ( `db.json` | Arweave ) +- Validator1 ( `validator1.json` | Arweave ) +- Validator2 ( `validator2.json` | Arweave ) +- Delegator ( `delegator.json` | Arweave ) +- Rollup-Admin ( `admin.json` | EVM ) +- ZK-Committer ( `committer.json` | EVM ) -## Local Development Tips +The zk-committer must have a positive ETH balance. Alchemy has a faucet for [Sepolia](https://www.alchemy.com/faucets/ethereum-sepolia). -### Clone Repo +Or you can generate missing wallets. ```bash -git clone https://github.com/weavedb/rdk.git +node scripts/gen_wallets.js ``` -### Explorer +Create `.env` in `weavedbb-ao/lua` directory and specify [alchemy](https://alchemy.com) key. -If you are running the rollup node on `localhost:8080`, you can view blocks and transactions on our public [WeaveDB Scan](https://scan.weavedb.dev/node/localhost). +```text +ALCHEMY=XXXXXXXXXXXXXXXXXXXX +``` -However, the public explorer may not be up-to-date. To run the latest explorer, go to `explorer` folder. +Deploy contracts. ```bash -cd rdk/explorer -yarn -yarn dev +node scripts/deploy_all.js ``` -Now the explorer is running locally at [localhost:3000/node/localhost](http://localhost:3000/node/localhost). +### WeaveDB Rollup -### Run Envoy Separately - -You can run Envoy separately on your computer, and run the bare rollup file `index.js` without Docker. This way, you don't have to restart docker-compose every time you make changes in development. +In another terminal, run envoy so frontend apps can access via port 8080. ```bash +cd weavedb-ao yarn envoy ``` -Then you can run the rollup server without Docker. +Now start the rollup node in another terminal. ```bash -cd rdk/node/node-server -yarn +cd weavedbb-ao/node/node-server && yarn node index.js ``` -### Arweave Local Testnet - -To test rollup executions in your local environment, you can run [arlocal](https://github.com/textury/arlocal) (Arweave local testnet), and redirect WeaveDB SDK / Warp SDK to it. - -### Integration Tests with Mocha - -You don't need to run Envoy for local tests. Envoy is to access the node from web browsers. Also, stop arlocal if you are running test scripts with [mocha](https://mochajs.org/). The test scripts will start everything with a clean state. - -We have `Test` helper utility, to make testing easier. Here is some boilerplate for you to start writing tests. +### WeaveLayer Staking on AO -```javascript -const { expect } = require("chai") -const DB = require("weavedb-node-client") -const SDK = require("weavedb-sdk-node") -const { wait, Test } = require("./lib/utils") +Set up staking. -describe("rollup node", function () { - this.timeout(0) - let admin, network, bundler, test - - before(async () => { - // testing in insecure mode, never do that in production - test = new Test({ secure: false }) - ;({ network, bundler, admin } = await test.start()) - }) - - after(async () => { - await test.stop() - - // some processes linger, so force exit for now - process.exit() - }) - - it("should start server", async () => { - const db = new DB({ - rpc: "localhost:9090", - contractTxId: "testdb", - arweave: network, - }) - const stats = await db.node({ op: "stats" }) - expect(stats).to.eql({ dbs: [] }) - - // add a DB to node - const tx = await db.admin( - { - op: "add_db", - key: "testdb", - db: { - app: "http://localhost:3000", - name: "Jots", - rollup: true, - owner: admin.address, - }, - }, - { privateKey: admin.privateKey }, - ) - expect(tx.success).to.eql(true) - await wait(2000) - - // deploy L1 warp contract (via node) - const { contractTxId, srcTxId } = await db.admin( - { op: "deploy_contract", key: "testdb" }, - { privateKey: admin.privateKey }, - ) - expect((await db.node({ op: "stats" })).dbs[0].data.rollup).to.eql(true) - await wait(2000) - - // check L1 warp contract info directly with SDK (not via node) - const warp_db = new SDK({ - type: 3, - contractTxId, - arweave: network, - }) - await warp_db.init() - expect((await warp_db.getInfo()).version).to.eql("0.37.2") - - // update the DB (via node) - const db2 = new DB({ - rpc: "localhost:9090", - contractTxId, - }) - const Bob = { name: "Bob" } - const tx2 = await db2.set(Bob, "ppl", "Bob", { - privateKey: admin.privateKey, - }) - expect(tx2.success).to.eql(true) - expect(await db2.get("ppl", "Bob")).to.eql(Bob) - - // check rollup - await wait(5000) - expect( - (await warp_db.db.readState()).cachedValue.state.rollup.height, - ).to.eql(1) - - // check if L1 Warp state is the same as L2 DB state - expect(await warp_db.get("ppl", "Bob")).to.eql(Bob) - }) -}) +```bash +node scripts/setup_staking.js ``` -Run the tests. +### Frontend Demo + +In anotehr terminal, run the demo app. ```bash -cd rdk/node/node-server -yarn test +cd weavedb-ao/demo && yarn +yarn dev ``` + +Now, the demo is runnint at [localhost:3000](http://localhost:3000). diff --git a/assets/architecuture.png b/assets/architecuture.png deleted file mode 100644 index aace778..0000000 Binary files a/assets/architecuture.png and /dev/null differ diff --git a/demo/.eslintrc.json b/demo/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/demo/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/demo/.gitignore b/demo/.gitignore new file mode 100644 index 0000000..88bda13 --- /dev/null +++ b/demo/.gitignore @@ -0,0 +1,2 @@ +.env.* +.vercel diff --git a/demo/arweave.mjs b/demo/arweave.mjs new file mode 100644 index 0000000..37310e9 --- /dev/null +++ b/demo/arweave.mjs @@ -0,0 +1,87 @@ +import { + rmdirSync, + unlinkSync, + writeFileSync, + readFileSync, + readdirSync, +} from "fs" + +import { resolve, extname } from "path" +import * as cheerio from "cheerio" + +const out = resolve(import.meta.dirname, "out") +const dirs = readdirSync(out, { withFileTypes: true, recursive: true }) + +const prefixWithPath = url => { + const cleanedUrl = url.replace(/^\.\//, "").replace(/^\//, "") + const txt = "${path}" + cleanedUrl + return "`" + txt + "`" +} + +const isRelativeUrl = url => !/^https?:\/\//i.test(url) + +let _dirs = [] + +for (const v of dirs) { + const ext = extname(v.name) + const htmlPath = resolve(v.path, v.name) + if (v.isFile() && ext === ".html") { + if (v.name !== "index.html") { + unlinkSync(htmlPath) + } else { + const txt = readFileSync(htmlPath, "utf8") + const $ = cheerio.load(txt) + let _tags = [] + $("link[href]").each((i, elem) => { + const href = $(elem).attr("href") + if (isRelativeUrl(href)) { + const props = { ...elem.attribs } + props.href = prefixWithPath(href) + _tags.push({ elm: "link", props }) + $(elem).remove() + } + }) + + $("script[src]").each((i, elem) => { + const src = $(elem).attr("src") + if (isRelativeUrl(src)) { + const props = { ...elem.attribs } + props.src = prefixWithPath(src) + _tags.push({ elm: "script", props }) + $(elem).remove() + } + }) + + $("script").each((i, elem) => { + const scriptContent = $(elem).html() + if (scriptContent && scriptContent.includes("let tags = []")) { + const tagsString = JSON.stringify(_tags) + const updatedScriptContent = scriptContent + .replace(/let\s+tags\s*=\s*\[\s*\]/, `let tags = ${tagsString}`) + .replace(/"`/g, "`") + .replace(/`"/g, "`") + $(elem).html(updatedScriptContent) + } + }) + const modifiedHtml = $.root().html() + writeFileSync(htmlPath, modifiedHtml) + } + } else if (v.isFile() && ext === ".js" && /^webpack/.test(v.name)) { + const txt = readFileSync(htmlPath, "utf8") + const x = txt.match(/(.)\.p="\/_next\/"/)[1] + const mod = txt.replace( + new RegExp(`o=${x}\.p`, "g"), + `o=_assetPath()+${x}.p`, + ) + writeFileSync(htmlPath, mod) + } +} + +const dirs2 = readdirSync(out, { withFileTypes: true }) +for (const v of dirs2) { + const htmlPath = resolve(v.path, v.name) + console.log(htmlPath) + if (v.isDirectory() && v.name !== "_next") { + rmdirSync(htmlPath, { recursive: true, force: true }) + } +} diff --git a/demo/components/About.js b/demo/components/About.js new file mode 100644 index 0000000..d1af67f --- /dev/null +++ b/demo/components/About.js @@ -0,0 +1,118 @@ +import { Flex, Box } from "@chakra-ui/react" +import { map } from "ramda" + +export default function About({ setTab }) { + return ( + + + Decentralized NoSQL Database + + + Better Developer Experience than Web2 + + + {map(v => { + return map(v2 => { + return ( + + + + {v2.title} + + + {v2.desc} + + + + ) + })(v) + })([ + [ + { + title: "Smart Contract NoSQL Database", + desc: "WeaveDB is an AO process, which is a decentralized smartcontract and all data are permanently stored on the Arweave blockchain.", + }, + { + title: "Compatible with Firestore", + desc: "WeaveDB queries are mostly compatible with Google Firestore, but the query APIs are simpler and more powerful in JSON formats.", + }, + { + title: "Web2 Cloud Performance", + desc: "WeaveDB achieves the web2-like performance and latency by being a rollup to AO. Each DB instance is an app-specific rollup.", + }, + ], + [ + { + title: "No Infra Maintenance", + desc: "Once your DB instance is deployed onchain, there is no maintenance required, which is a far better dev experience than web2 cloud services.", + }, + { + title: "Hyper Optimized ZKP for JSON", + desc: "zkJSON is novel encoding and zk circuits optimized for JSON. It takes only 3 seconds to generate a zkp to prove any JSON data.", + }, + { + title: "Query from Other Blockchains", + desc: "WeaveDB is an optimistic zk-rollup to other blockchains allowing queries from any chain, which hyper-extends blockchains with off-chan data.", + }, + ], + [ + { + title: "Advanced DSL FPJSON", + desc: "FPJSON is a functional programming language in JSON format, which enables advanced access control rules for permissionless DBs.", + }, + { + title: "Social Logins & Passkeys", + desc: "WeaveDB allows crypto wallets using web2 social logins such as Google, Github, and Apple. It also integrates passkeys for biometric authentications.", + }, + { + title: "Extensive Developer Tools", + desc: "WeaveDB comes with an extensive set of dev tools such as No Code Web Console, CLI Testing Tools, WeaveDB Scan, ArNext React Framework.", + }, + ], + ])} + + + setTab("create")} + mx={6} + bg="#9C89F6" + w="250px" + py={2} + color="white" + justify="center" + sx={{ + borderRadius: "5px", + cursor: "pointer", + ":hover": { opacity: 0.75 }, + }} + > + Try Demo + + setTab("usecases")} + mx={6} + bg="#9C89F6" + w="250px" + py={2} + color="white" + justify="center" + sx={{ + borderRadius: "5px", + cursor: "pointer", + ":hover": { opacity: 0.75 }, + }} + > + Use Cases + + + + ) +} diff --git a/demo/components/Footer.js b/demo/components/Footer.js new file mode 100644 index 0000000..91dbefa --- /dev/null +++ b/demo/components/Footer.js @@ -0,0 +1,84 @@ +import { Image, Flex, Box } from "@chakra-ui/react" +import { Link } from "arnext" + +export default function Footer({ setTab }) { + let ml = [4, 6] + return ( + + + + + + + WeaveDB + + + + + Blog + Docs + + Scan + + + tDB Token + + Web Console + + + + + + + + + + + + + + + + + ) +} diff --git a/demo/components/Header.js b/demo/components/Header.js new file mode 100644 index 0000000..f8ed1ab --- /dev/null +++ b/demo/components/Header.js @@ -0,0 +1,409 @@ +import { startAuthentication, startRegistration } from "@simplewebauthn/browser" +import { map } from "ramda" +import forge from "node-forge" +import Arweave from "arweave" +import { + generateRegistrationOptions, + generateAuthenticationOptions, +} from "@simplewebauthn/server" +import { Image, Flex, Box } from "@chakra-ui/react" +import { Link } from "arnext" +import { useEffect, useState } from "react" +const { AO } = require("aonote") +import { opt } from "@/lib/utils" +import lf from "localforage" + +function to64(x) { + let modulus = Buffer.from(x.toByteArray()) + if (modulus[0] === 0) modulus = modulus.slice(1) + return modulus + .toString("base64") + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, "") +} + +function generateDeterministicRSAKey(entropy) { + const rng = forge.random.createInstance() + rng.seedFileSync = () => entropy.toString("hex") + const rsaKeyPair = forge.pki.rsa.generateKeyPair({ + bits: 4096, + e: 0x10001, + prng: rng, + }) + const { publicKey, privateKey } = rsaKeyPair + const { n } = publicKey + const { d, p, q, dP, dQ, qInv } = privateKey + const jwk = { + kty: "RSA", + e: "AQAB", + n: to64(n), + d: to64(d), + p: to64(p), + q: to64(q), + dp: to64(dP), + dq: to64(dQ), + qi: to64(qInv), + } + return jwk +} + +async function deriveEntropyForRSA(prfKey) { + const hkdfKeyMaterial = await crypto.subtle.importKey( + "raw", + prfKey, + "HKDF", + false, + ["deriveBits"], + ) + + const derivedEntropy = await crypto.subtle.deriveBits( + { + name: "HKDF", + hash: "SHA-256", + salt: new Uint8Array(32), + info: new Uint8Array(0), + }, + hkdfKeyMaterial, + 4096, + ) + return new Uint8Array(derivedEntropy) +} + +export default function Header({ + isWallet, + setIsWallet, + addr, + toast, + setBalance, + setDeposit, + setIsDashboard, + isDashboard, + setAddr, + jwk, + setJwk, +}) { + const [connecting, setConnecting] = useState(false) + const [wallet, setWallet] = useState(null) + useEffect(() => { + if (typeof arweaveWallet === "undefined") { + ;(async () => { + const wallet = (await lf.getItem("wallet")) ?? null + setWallet(wallet) + })() + } + }, []) + const page = isDashboard ? "Dashboard" : isWallet ? "Wallet" : "Demo" + return ( + + + + + + {isDashboard + ? "Dashboard" + : isWallet + ? "WeaveWallet" + : "WeaveDB Demos"} + + + + {!addr ? null : ( + + {map(v => { + return ( + { + if (v.name === "Dashboard") { + setIsWallet(false) + setIsDashboard(true) + } else if (v.name === "Wallet") { + setIsWallet(true) + setIsDashboard(false) + } else { + setIsWallet(false) + setIsDashboard(false) + } + }} + > + {v.name} + + ) + })([{ name: "Demo" }, { name: "Dashboard" }, { name: "Wallet" }])} + + )} + { + if (!connecting) { + let err = null + if (addr) { + if (confirm("Disconnect your wallet?")) { + setIsWallet(false) + setIsDashboard(false) + setAddr(null) + setJwk(null) + } + } else { + setConnecting(true) + try { + let __addr + if (typeof arweaveWallet === "undefined") { + const arweave = Arweave.init() + const createName = "WeaveDB" + const rpID = location.host.split(":")[0] + const rpName = "Weave Wallet" + const first = new Uint8Array(new Array(32).fill(1)).buffer + const user = { id: "weavedb", username: "weave_db" } + + if (wallet) { + const dec = new TextDecoder() + const opt = await generateAuthenticationOptions({ + rpID, + rpName, + allowCredentials: [ + { id: wallet.id, transport: wallet.transport }, + ], + extensions: { + largeBlob: { read: true }, + prf: { support: "preferred", eval: { first } }, + }, + }) + let res = null + try { + res = await startAuthentication({ optionsJSON: opt }) + } catch (e) { + res = await startAuthentication({ optionsJSON: opt }) + } + if (res.clientExtensionResults.prf?.results?.first) { + const key = new Uint8Array( + res.clientExtensionResults.prf.results.first, + ) + const rsaEntropy = await deriveEntropyForRSA(key) + const jwk = generateDeterministicRSAKey(rsaEntropy) + const addr2 = await arweave.wallets.jwkToAddress(jwk) + setJwk(jwk) + setWallet(wallet) + setAddr(addr2) + __addr = addr2 + } else { + const jwk = JSON.parse( + dec.decode(res.clientExtensionResults.largeBlob.blob), + ) + const addr2 = await arweave.wallets.jwkToAddress(jwk) + setJwk(jwk) + setWallet(wallet) + setAddr(addr2) + __addr = addr2 + } + toast({ + title: "Wallet Connected!", + status: "success", + duration: 5000, + isClosable: true, + }) + } else { + let optionsJSON = await generateRegistrationOptions({ + rpID, + rpName, + userName: user.username, + extensions: { + prf: { support: "preferred", eval: { first } }, + largeBlob: { support: "preferred" }, + }, + }) + const attResp = await startRegistration({ optionsJSON }) + if (attResp?.clientExtensionResults?.prf?.enabled) { + const opt2 = await generateAuthenticationOptions({ + rpID, + allowCredentials: [ + { id: attResp.id, transport: attResp.transport }, + ], + extensions: { prf: { eval: { first } } }, + }) + const attResp3 = await startAuthentication({ + optionsJSON: opt2, + }) + + const key = new Uint8Array( + attResp3?.clientExtensionResults.prf.results.first, + ) + const rsaEntropy = await deriveEntropyForRSA(key) + const jwk = generateDeterministicRSAKey(rsaEntropy) + const addr = await arweave.wallets.jwkToAddress(jwk) + __addr = addr + const wallet = { + addr, + id: attResp.id, + transport: attResp.transport, + } + setJwk(jwk) + setWallet(wallet) + setAddr(addr) + await lf.setItem("wallet", wallet) + toast({ + title: "Wallet Created!", + status: "success", + duration: 5000, + isClosable: true, + }) + } else if ( + attResp?.clientExtensionResults?.largeBlob?.supported + ) { + const jwk = await arweave.wallets.generate() + const addr = await arweave.wallets.jwkToAddress(jwk) + __addr = addr + const wallet = { + addr, + id: attResp.id, + transport: attResp.transport, + } + await lf.setItem("wallet", wallet) + const enc = new TextEncoder() + const opt2 = await generateAuthenticationOptions({ + rpID, + allowCredentials: [ + { id: attResp.id, transport: attResp.transport }, + ], + extensions: { + largeBlob: { + write: enc.encode(JSON.stringify(jwk)), + }, + }, + }) + const attResp3 = await startAuthentication({ + optionsJSON: opt2, + }) + if ( + JSON.stringify( + attResp3.clientExtensionResults?.largeBlob?.written, + ) + ) { + setJwk(jwk) + setWallet(wallet) + setAddr(addr) + toast({ + title: "Wallet Created!", + status: "success", + duration: 5000, + isClosable: true, + }) + } else { + toast({ + title: "Something Went Wrong!", + status: "error", + description: err, + duration: 5000, + isClosable: true, + }) + } + } else { + toast({ + title: "Something Went Wrong!", + status: "error", + description: "This device is not supported!", + duration: 5000, + isClosable: true, + }) + } + } + } else { + await arweaveWallet.connect([ + "ACCESS_ADDRESS", + "SIGN_TRANSACTION", + "ACCESS_PUBLIC_KEY", + ]) + const addr = await arweaveWallet.getActiveAddress() + __addr = addr + setAddr(addr) + setConnecting(false) + toast({ + title: "Wallet Connected!", + status: "success", + duration: 5000, + isClosable: true, + }) + } + try { + if (__addr) { + const ao = new AO(opt) + const { out } = await ao.dry({ + pid: process.env.NEXT_PUBLIC_TDB, + act: "Balance", + tags: { Target: __addr }, + get: "Balance", + }) + setBalance({ amount: out * 1, addr }) + const { out: out2 } = await ao.dry({ + pid: process.env.NEXT_PUBLIC_ADMIN_CONTRACT, + act: "Balance", + tags: { Target: __addr }, + get: true, + }) + setDeposit(out2 * 1) + } + } catch (e) { + console.log(e) + } + } catch (e) { + err = e.toString() + } + if (err) { + toast({ + title: "Something Went Wrong!", + status: "error", + description: err, + duration: 5000, + isClosable: true, + }) + } + } + setConnecting(false) + } + }} + > + {connecting ? ( + + ) : addr ? ( + addr.slice(0, 10) + ) : ( + "Connect Wallet" + )} + + + + ) +} diff --git a/demo/components/Masthead.js b/demo/components/Masthead.js new file mode 100644 index 0000000..18c9d62 --- /dev/null +++ b/demo/components/Masthead.js @@ -0,0 +1,165 @@ +import { Image, Flex, Box } from "@chakra-ui/react" +import { Link } from "arnext" + +export default function Header({}) { + return ( + + + + + + + Zero Knowledge Provable + NoSQL Database + + + + + + Hyper Extending Blockchains with zkJSON. + Web3 with Web2 UX is Finally Here. + + + + + + CEO + + + + + Tech + + + + + Github + + + + + X + + + + + + + + + + + + + + ) +} diff --git a/explorer/jsconfig.json b/demo/jsconfig.json similarity index 100% rename from explorer/jsconfig.json rename to demo/jsconfig.json diff --git a/demo/lib/a11y-dark.js b/demo/lib/a11y-dark.js new file mode 100644 index 0000000..6eb8922 --- /dev/null +++ b/demo/lib/a11y-dark.js @@ -0,0 +1,96 @@ +export default { + "hljs-comment": { + color: "#d4d0ab", + }, + "hljs-quote": { + color: "#d4d0ab", + }, + "hljs-variable": { + color: "#ffa07a", + }, + "hljs-template-variable": { + color: "#ffa07a", + }, + "hljs-tag": { + color: "#ffa07a", + }, + "hljs-name": { + color: "#ffa07a", + }, + "hljs-selector-id": { + color: "#ffa07a", + }, + "hljs-selector-class": { + color: "#ffa07a", + }, + "hljs-regexp": { + color: "#ffa07a", + }, + "hljs-deletion": { + color: "#ffa07a", + }, + "hljs-number": { + color: "#f5ab35", + }, + "hljs-built_in": { + color: "#f5ab35", + }, + "hljs-builtin-name": { + color: "#f5ab35", + }, + "hljs-literal": { + color: "#f5ab35", + }, + "hljs-type": { + color: "#f5ab35", + }, + "hljs-params": { + color: "#f5ab35", + }, + "hljs-meta": { + color: "#f5ab35", + }, + "hljs-link": { + color: "#f5ab35", + }, + "hljs-attribute": { + color: "#ffd700", + }, + "hljs-string": { + color: "#abe338", + }, + "hljs-symbol": { + color: "#abe338", + }, + "hljs-bullet": { + color: "#abe338", + }, + "hljs-addition": { + color: "#abe338", + }, + "hljs-title": { + color: "#00e0e0", + }, + "hljs-section": { + color: "#00e0e0", + }, + "hljs-keyword": { + color: "#dcc6e0", + }, + "hljs-selector-tag": { + color: "#dcc6e0", + }, + hljs: { + display: "block", + overflowX: "auto", + background: "#2b2b2b", + color: "#f8f8f2", + padding: "0.5em", + }, + "hljs-emphasis": { + fontStyle: "italic", + }, + "hljs-strong": { + fontWeight: "bold", + }, +} diff --git a/demo/lib/utils.js b/demo/lib/utils.js new file mode 100644 index 0000000..899fc50 --- /dev/null +++ b/demo/lib/utils.js @@ -0,0 +1,385 @@ +const abi = [ + { + inputs: [ + { + internalType: "address", + name: "_verifierRU", + type: "address", + }, + { + internalType: "address", + name: "_verifierDB", + type: "address", + }, + { + internalType: "address", + name: "_committer", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [ + { + internalType: "string", + name: "txid", + type: "string", + }, + { + internalType: "uint256[]", + name: "zkp", + type: "uint256[]", + }, + ], + name: "commit", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "txid", + type: "string", + }, + { + internalType: "uint256", + name: "_root", + type: "uint256", + }, + ], + name: "commitRoot", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "committer", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "txid", + type: "string", + }, + { + internalType: "uint256[]", + name: "path", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "zkp", + type: "uint256[]", + }, + ], + name: "qBool", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "txid", + type: "string", + }, + { + internalType: "uint256[]", + name: "path", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "cond", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "zkp", + type: "uint256[]", + }, + ], + name: "qCond", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "txid", + type: "string", + }, + { + internalType: "uint256[]", + name: "path", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "path2", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "zkp", + type: "uint256[]", + }, + ], + name: "qCustom", + outputs: [ + { + internalType: "int256", + name: "", + type: "int256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "txid", + type: "string", + }, + { + internalType: "uint256[]", + name: "path", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "zkp", + type: "uint256[]", + }, + ], + name: "qFloat", + outputs: [ + { + internalType: "uint256[3]", + name: "", + type: "uint256[3]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "txid", + type: "string", + }, + { + internalType: "uint256[]", + name: "path", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "zkp", + type: "uint256[]", + }, + ], + name: "qInt", + outputs: [ + { + internalType: "int256", + name: "", + type: "int256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "txid", + type: "string", + }, + { + internalType: "uint256[]", + name: "path", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "zkp", + type: "uint256[]", + }, + ], + name: "qNull", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "txid", + type: "string", + }, + { + internalType: "uint256[]", + name: "path", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "zkp", + type: "uint256[]", + }, + ], + name: "qRaw", + outputs: [ + { + internalType: "uint256[]", + name: "", + type: "uint256[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "txid", + type: "string", + }, + { + internalType: "uint256[]", + name: "path", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "zkp", + type: "uint256[]", + }, + ], + name: "qString", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + name: "roots", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "verifierDB", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "verifierRU", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, +] + +const opt = + process.env.NEXT_PUBLIC_NODE === "localhost" + ? { + ar: { port: 4000 }, + aoconnect: { + MU_URL: "http://localhost:4002", + CU_URL: "http://localhost:4004", + GATEWAY_URL: "http://localhost:4000", + }, + } + : {} + +module.exports = { abi, opt } diff --git a/demo/next.config.js b/demo/next.config.js new file mode 100644 index 0000000..58cce1d --- /dev/null +++ b/demo/next.config.js @@ -0,0 +1,17 @@ +const arnext = require("arnext/config") +const nextConfig = { + reactStrictMode: true, + webpack5: true, + webpack: config => { + config.resolve.fallback = { + fs: false, + tls: false, + net: false, + http2: false, + dns: false, + } + + return config + }, +} +module.exports = arnext(nextConfig) diff --git a/demo/package.json b/demo/package.json new file mode 100644 index 0000000..be3145e --- /dev/null +++ b/demo/package.json @@ -0,0 +1,55 @@ +{ + "name": "arnext-app", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "arweave": "npm run build:arweave && npx serve -s out", + "deploy": "node node_modules/arnext-arkb deploy out", + "deploy:turbo": "turbo upload-folder --folder-path out", + "build:arweave": "cross-env NEXT_PUBLIC_DEPLOY_TARGET='arweave' next build && node arweave.mjs", + "test": "mocha" + }, + "dependencies": { + "@chakra-ui/icons": "2.2.4", + "@chakra-ui/next-js": "2.4.1", + "@chakra-ui/react": "2.10.1", + "@emotion/react": "11.13.3", + "@emotion/styled": "11.13.0", + "@simplewebauthn/browser": "^11.0.0", + "@simplewebauthn/server": "^11.0.0", + "aonote": "^0.10.1", + "arnext": "^0.1.4", + "arweave": "^1.15.5", + "ethers": "^6.13.4", + "framer-motion": "^11.11.1", + "html5-qrcode": "^2.3.8", + "localforage": "^1.10.0", + "next": "14.2.13", + "node-forge": "^1.3.1", + "ramda": "^0.30.1", + "react": "^18", + "react-dom": "^18", + "react-qr-code": "^2.0.15", + "react-syntax-highlighter": "^15.6.1", + "usehooks-ts": "^3.1.0", + "wao": "^0.7.0", + "weavedb-client": "^0.45.2", + "weavedb-node-client": "^0.45.2", + "zkjson": "^0.3.2" + }, + "devDependencies": { + "@ardrive/turbo-sdk": "^1.19.0", + "arnext-arkb": "^0.0.1", + "cheerio": "^1.0.0", + "cross-env": "^7.0.3", + "eslint": "8.57.1", + "eslint-config-next": "14.2.13", + "express": "^4.21.0", + "http-proxy-middleware": "^3.0.2", + "starknet": "^6.11.0" + } +} diff --git a/demo/pages/404.js b/demo/pages/404.js new file mode 100644 index 0000000..5fd4e79 --- /dev/null +++ b/demo/pages/404.js @@ -0,0 +1,9 @@ +import { Link } from "arnext" + +export default function Home() { + return ( + <> + 404 | back + + ) +} diff --git a/demo/pages/_app.js b/demo/pages/_app.js new file mode 100644 index 0000000..390f69e --- /dev/null +++ b/demo/pages/_app.js @@ -0,0 +1,9 @@ +import { ArNext } from "arnext" +import { ChakraProvider } from "@chakra-ui/react" +export default function App(props) { + return ( + + + + ) +} diff --git a/demo/pages/_document.js b/demo/pages/_document.js new file mode 100644 index 0000000..9744d79 --- /dev/null +++ b/demo/pages/_document.js @@ -0,0 +1,22 @@ +import { Html, Main, NextScript } from "next/document" +import { Head } from "arnext" + +export default function Document() { + return ( + + + WeaveDB | Zero Knowledge Provable DB + + + + +
+ + + + ) +} diff --git a/demo/pages/api/commitRoot.js b/demo/pages/api/commitRoot.js new file mode 100644 index 0000000..82482f4 --- /dev/null +++ b/demo/pages/api/commitRoot.js @@ -0,0 +1,34 @@ +const { abi } = require("@/lib/utils") +const { Wallet, getDefaultProvider, Contract } = require("ethers") +const provider = getDefaultProvider("sepolia", { + alchemy: process.env.NEXT_PUBLIC_ALCHEMY_KEY, +}) +import DB from "weavedb-node-client" +const privateKey = process.env.PRIVATE_KEY +const wallet = new Wallet(privateKey, provider) + +export default async function handler(req, res) { + const { key, contractTxId } = JSON.parse(req.body) + let success = false + const db2 = new DB({ rpc: process.env.NEXT_PUBLIC_RPC_NODE, contractTxId }) + const hash = (await db2.node({ op: "hash", key })).hash + let updated = false + if (hash) { + const contract = new Contract(process.env.NEXT_PUBLIC_CONTRACT, abi, wallet) + const hash2 = await contract.roots(contractTxId) + console.log(hash2) + try { + if (BigInt(hash) === hash2) { + success = true + } else { + const tx = await contract.commitRoot(contractTxId, hash) + const res = await tx.wait() + if (res.status === 1) { + success = true + updated = true + } + } + } catch (e) {} + } + res.status(200).json({ updated, success, hash, contractTxId }) +} diff --git a/demo/pages/index.js b/demo/pages/index.js new file mode 100644 index 0000000..5850f5b --- /dev/null +++ b/demo/pages/index.js @@ -0,0 +1,4727 @@ +import { AO } from "wao" +import { Link, ssr } from "arnext" +import { useInterval } from "usehooks-ts" +import QRCode from "react-qr-code" +import { Html5QrcodeScanner } from "html5-qrcode" +import SyntaxHighlighter from "react-syntax-highlighter" +import a11yDark from "../lib/a11y-dark" +const { toIndex, path, encodeQuery } = require("zkjson") +import { useEffect, useState } from "react" +import DB from "weavedb-client" +import { opt, abi } from "@/lib/utils" +const network = + process.env.NEXT_PUBLIC_NODE === "localhost" + ? { host: "localhost", port: 4000, protocol: "http" } + : { host: "arweave.net", port: 443, protocol: "https" } +const { Contract, getDefaultProvider } = require("ethers") +import About from "@/components/About" +import Footer from "@/components/Footer" +import Header from "@/components/Header" +import Masthead from "@/components/Masthead" +import { + Image, + Flex, + Box, + Input, + Textarea, + Select, + useToast, +} from "@chakra-ui/react" +import { + without, + append, + range, + map, + pluck, + indexBy, + prop, + reverse, + isNil, + assoc, + includes, + is, + trim, + filter, +} from "ramda" +const n = (number, deci = 12) => + new Intl.NumberFormat("en-US").format( + (Math.floor(number ?? 0) / 10 ** deci).toFixed(0), + ) +const wait = ms => new Promise(res => setTimeout(() => res(), ms)) +let gwei = "" +for (let i = 0; i < 18; i++) gwei += "0" +const getP = async (pid, jwk) => { + let addr + if (jwk) { + addr = addr + } else { + await arweaveWallet.connect([ + "ACCESS_ADDRESS", + "SIGN_TRANSACTION", + "ACCESS_PUBLIC_KEY", + ]) + addr = await arweaveWallet.getActiveAddress() + } + const p = (await new AO(opt).init(jwk ?? arweaveWallet)).p(pid) + return { addr, p } +} +const provider = getDefaultProvider("sepolia", { + alchemy: process.env.NEXT_PUBLIC_ALCHEMY_KEY, +}) +let html5QrcodeScanner = null +const validAddress = addr => /^[a-zA-Z0-9_-]{43}$/.test(addr) +const getQ = ({ + search, + selectedCol, + limit, + order, + query, + operator, + value, + sort, +}) => { + if (search === "single") { + return [selectedCol, query] + } else { + let q = [selectedCol] + + if (!includes(operator, ["array-contains", "array-contains-any"])) + q.push([sort, order]) + if (operator !== "") { + let val = value + if (includes(operator, ["in", "not-in", "array-contains-any"])) { + val = map(v => { + let v2 = trim(v) + if (sort === "age") v2 *= 1 + if (sort === "married") v2 = v2 === "false" ? false : true + return v2 + })(val.split(",")) + } else { + if (sort === "age") val *= 1 + } + q.push([sort, operator, val]) + } + if (limit !== "") q.push(+limit) + return q + } +} +const commit = async ({ + _alert, + committing, + dbname2, + setCommitting, + dbs, + rpc, +}) => { + let updated = false + if (!committing[dbname2]) { + setCommitting(assoc(dbname2, true, committing)) + try { + const contractTxId = indexBy(prop("id"), dbs)[dbname2].data.contractTxId + const db = new DB({ rpc, contractTxId }) + const res = await fetch("/api/commitRoot", { + method: "POST", + body: JSON.stringify({ + rpc, + contractTxId, + key: dbname2, + }), + }).then(r => r.json()) + if (res.success) { + if (_alert) alert("hash committed!") + updated = res.updated + } else { + if (_alert) alert("something went wrong!") + } + } catch (e) { + if (_alert) alert("something went wrong!") + } + setCommitting(assoc(dbname2, false, committing)) + } + return updated +} +const codeDeploy = ({ dbname, owner }) => { + return `const owner = "${owner}" +await db.admin({ op: "add_db", key: "${dbname}", db: { owner }}) +await db.admin({ op: "deploy_contract", key: "${dbname}" }) +` +} + +const codeDeploy2 = ({ colName }) => { + return `const rules = [["write", [["allow()"]]]] +await db.setRules(rules, "${colName}") +const schema = { + type: "object", + required: ["name", "age", "married", "favorites"], + properties: { + name: { type: "string" }, + age: { type: "number" }, + married: { type: "boolean" }, + favorites: { type: "array", items: { type: "string" } } + }, +} +await db.setSchema(schema, "${colName}" }) +` +} + +const codeDeploy3 = ({ col, age, name, married, favorites }) => { + return `const profile = { + name: "${name}", + age: ${age}, + married: ${married}, + favorites: [${map(v => `"${v}"`)(favorites).join(", ")}] +} +await db.set(profile, "${col}", "${name}")` +} + +const codeDeploy4 = ({ + selectedCol, + limit, + order, + query, + search, + operator, + value, + sort, +}) => { + const q = getQ({ + order, + query, + search, + selectedCol, + limit, + operator, + value, + sort, + }) + return `await db.get(${JSON.stringify(q).replace(/^\[/, "").replace(/\]$/, "").replace(/,/g, ", ")}) +` +} + +const codeDeploy5 = ({ db, col, doc, tar }) => { + return `const zkp = await db.node({ + op: "zkp", + key: "${db}", + collection: "${col}", + doc: "${doc}", + path: "${tar}", +}) +` +} + +const codeDeploy6 = ({ txid, path, zkp, fn }) => { + return `await contract.${fn}( + "${txid}", + ${path}, + ${zkp}, +) +` +} + +export default function Home({ _date = null }) { + const [as, setAS] = useState("Validator") + const [delegatee, setDelegatee] = useState(null) + const [to, setTo] = useState("") + const [sendAmount, setSendAmount] = useState("0") + const [token, setToken] = useState(process.env.NEXT_PUBLIC_TDB) + const [walletTab, setWalletTab] = useState("Tokens") + const [jwk, setJwk] = useState(null) + const [depositing, setDepositing] = useState(false) + const [adding, setAdding] = useState(false) + const [withdrawing, setWithdrawing] = useState(false) + const [staking, setStaking] = useState(false) + const [sending, setSending] = useState(false) + const toast = useToast() + const [isDashboard, setIsDashboard] = useState(false) + const [isWallet, setIsWallet] = useState(false) + const [isSend, setIsSend] = useState(false) + const [isScan, setIsScan] = useState(false) + const [isReceive, setIsReceive] = useState(false) + const [op, setOp] = useState("Deposit") + const [amount, setAmount] = useState("100") + const [grpc, setGRPC] = useState("https://") + const [where, setWhere] = useState("") + const [qtype, setQType] = useState("disclosure") + const [operator, setOperator] = useState("") + const [value, setValue] = useState("") + const [limit, setLimit] = useState("") + const [sort, setSort] = useState("name") + const [order, setOrder] = useState("asc") + const [search, setSearch] = useState("single") + const [showCode, setShowCode] = useState(false) + const [showCode2, setShowCode2] = useState(false) + const [showCode3, setShowCode3] = useState(false) + const [showCode4, setShowCode4] = useState(false) + const [showCode5, setShowCode5] = useState(false) + const [showCode6, setShowCode6] = useState(false) + const [profiles, setProfiles] = useState([]) + const [favs, setFavs] = useState([]) + const [tab, setTab] = useState("about") + const [hash, setHash] = useState(null) + const [committing, setCommitting] = useState({}) + const [generating, setGenerating] = useState(false) + const [querying, setQuerying] = useState(false) + const [which, setWhich] = useState("WeaveDB Rollup") + const [zkp, setZKP] = useState(null) + const [name, setName] = useState("") + const [dbname, setDBName] = useState("") + const [dbname2, setDBName2] = useState("") + const [query, setQuery] = useState("") + const [tar, setTar] = useState("name") + const [age, setAge] = useState(5) + const [married, setMarried] = useState("true") + const [latency, setLatency] = useState(null) + const [latency2, setLatency2] = useState(null) + const [latency3, setLatency3] = useState(null) + const [latency4, setLatency4] = useState(null) + const [latency5, setLatency5] = useState(null) + const [latency6, setLatency6] = useState(null) + const [loading, setLoading] = useState(false) + const [deploying, setDeploying] = useState(false) + const [data, setData] = useState(null) + const [data2, setData2] = useState(null) + const [selectedData, setSelectedData] = useState(null) + const [dbs, setDBs] = useState([]) + const [cols, setCols] = useState([]) + const [colName, setColName] = useState("") + const [qvalue, setQValue] = useState("") + const [selectedCol, setSelectedCol] = useState(null) + const [addr, setAddr] = useState(null) + const [balance, setBalance] = useState(null) + const [balanceETH, setBalanceETH] = useState(null) + const [isStake, setIsStake] = useState(null) + const [deposit, setDeposit] = useState(0) + const [nodeDeposit, setNodeDeposit] = useState("1") + const [stats, setStats] = useState(null) + const [myStats, setMyStats] = useState(null) + const [count, setCount] = useState(0) + const [nodes, setNodes] = useState([]) + const [node, setNode] = useState(null) + const [stakeStats, setStakeStats] = useState(null) + const [allDBs, setAllDBs] = useState({}) + const [stakeAmount, setStakeAmount] = useState(1) + const _path = isNil(zkp) + ? null + : [Number(zkp.col_id).toString(), toIndex(zkp.data.name), ...path(zkp.tar)] + + const tabs = [ + { key: "about", name: "About" }, + { key: "create", name: "Create DB" }, + { key: "query", name: "Query" }, + { key: "zkjson", name: "zkJSON" }, + { key: "usecases", name: "Use Cases" }, + ] + useEffect(() => { + ;(async () => { + const p = new AO(opt).p(process.env.NEXT_PUBLIC_STAKING) + setStakeStats(await p.d("Info")) + setAllDBs(await p.d("Get-DBs")) + const _nodes = await p.d("Get-Nodes") + let __nodes = [] + let n = null + for (const k in _nodes) { + const _n = { id: k, info: _nodes[k] } + if (!n) n = _n + __nodes.push(_n) + } + setNode(n) + setNodes(__nodes) + })() + }, []) + useEffect(() => { + ;(async () => { + if (node) { + try { + const db = new DB({ + rpc: node.info.url, + contractTxId: dbname, + arweave: network, + }) + const stats = await db.node({ op: "stats" }) + setStats(stats) + const _dbs = filter( + v => !isNil(v.data.admin) && !isNil(v.data.contractTxId), + )(stats.dbs) + console.log(_dbs) + setDBs(_dbs) + if (_dbs[0]) { + setDBName2(_dbs[0].id ?? null) + const db2 = new DB({ + rpc: node.info.url, + contractTxId: _dbs[0].id, + arweave: network, + }) + const _cols = await db2.listCollections() + setSelectedCol(_cols[0] ?? null) + setCols(_cols) + if (!isNil(_cols[0])) setProfiles(await db2.get(_cols[0])) + } + } catch (e) { + console.log(e) + } + } + })() + }, [node]) + useInterval(async () => { + if (addr) { + const ao = new AO(opt) + const { err, out } = await ao.dry({ + pid: process.env.NEXT_PUBLIC_TDB, + act: "Balance", + tags: { Target: addr }, + get: "Balance", + }) + if (balance && balance.addr === addr && balance.amount < out * 1) { + toast({ + title: `Received ${Math.floor((out * 1 - balance.amount) / 10 ** 12)} tDB Token!`, + status: "success", + duration: 3000, + isClosable: true, + }) + } + if (out) setBalance({ addr, amount: out * 1 }) + const { err: err2, out: out2 } = await ao.dry({ + pid: process.env.NEXT_PUBLIC_ETH, + act: "Balance", + tags: { Target: addr }, + get: "Balance", + }) + if ( + balanceETH && + balanceETH.addr === addr && + balanceETH.amount < out2 * 1 + ) { + toast({ + title: `Received ${Math.floor((out2 * 1 - balanceETH.amount) / 10 ** 18)} taoETH Token!`, + status: "success", + duration: 3000, + isClosable: true, + }) + } + if (out2) setBalanceETH({ addr, amount: out2 * 1 }) + } + }, 5000) + useInterval(async () => { + if (addr) { + const ao = new AO(opt) + const { out: out3 } = await ao.dry({ + pid: process.env.NEXT_PUBLIC_STAKING, + act: "Get-Stats", + tags: { Address: addr, TS: Date.now() }, + get: true, + }) + if (out3) setMyStats(out3) + } + }, 10000) + + const _token = + token === process.env.NEXT_PUBLIC_TDB + ? { tick: "tDB", balance, decimal: 12 } + : { tick: "taoETH", balance: balanceETH, decimal: 18 } + + const processId = isNil(zkp) + ? "" + : (indexBy(prop("id"), dbs)[zkp.db]?.data?.contractTxId ?? "") + const deploy_ok = !/^\s*$/.test(dbname) && deposit * 1 >= 100 * 10 ** 12 + let add_node_ok = + nodeDeposit * 1 >= 1 && + balanceETH && + balanceETH.amount * 1 >= nodeDeposit * 10 ** 18 && + /^http(s){0,1}:\/\/.+$/.test(grpc) + let staking_ok = + stakeAmount * 1 >= 1 && + balanceETH && + balanceETH.amount * 1 >= stakeAmount * 10 ** 18 && + (as !== "Delegator" || delegatee) + let deposit_ok = + amount * 1 > 0 && balance && balance.amount * 1 >= amount * 10 ** 12 + if (op === "Withdraw") { + deposit_ok = amount * 1 > 0 && deposit * 1 >= amount * 10 ** 12 + } + const add_ok = !/^\s*$/.test(colName) + const save_ok = !/^\s*$/.test(name) + const query_ok = search === "multi" || !/^\s*$/.test(query) + const zkp_ok = + !isNil(selectedData) && (qtype === "disclosure" || !/^\s*$/.test(qvalue)) + const eth_ok = !committing[zkp?.db] + const to_ok = validAddress(to) + const send_amount_ok = + sendAmount * 1 > 0 && + _token.balance && + _token.balance.amount * 1 >= sendAmount * 10 ** _token.decimal + const send_ok = to_ok && send_amount_ok + const ops = includes(tar, ["name", "age", "married"]) + ? [ + { + val: "disclosure", + name: "Disclosure", + }, + { val: "gt", name: "$gt" }, + { val: "gte", name: "$gte" }, + { val: "lt", name: "$lt" }, + { val: "lte", name: "$lte" }, + { val: "eq", name: "$eq" }, + { val: "ne", name: "$ne" }, + { val: "in", name: "$in" }, + ] + : [ + { val: "contains", name: "$contains" }, + { + val: "contains_any", + name: "$contains_any", + }, + { + val: "contains_all", + name: "$contains_all", + }, + { + val: "contains_none", + name: "$contains_none", + }, + ] + const dbmap = indexBy(prop("id"))(dbs ?? []) + const pool = (stakeStats?.Rewards?.pool ?? 0) * 1 + const day = pool / 10 ** 12 / 365 + const mystake = (myStats?.stake ?? 0) * 1 + const allstake = (stakeStats?.TotalStake ?? 0) * 1 + const dayYield = Math.floor((day * mystake) / allstake) + const withdrawable = + (myStats?.["yield"] ?? 0) * 1 + (myStats?.["reward"] ?? 0) * 1 + const alltime_yield = + (myStats?.["yield"] ?? 0) * 1 + (myStats?.["withdraw"] ?? 0) * 1 + const alltime_reward = + (myStats?.["reward"] ?? 0) * 1 + (myStats?.["reward_withdraw"] ?? 0) * 1 + const adbs = [] + for (let k in allDBs) { + adbs.push({ id: k, info: allDBs[k] }) + } + const _validators = allDBs?.[isStake]?.stakes + + let validators = [] + if (_validators) { + for (let k in _validators ?? {}) { + const _delegators = allDBs?.[isStake]?.delegates?.[k] + let obj = { + addr: k, + amount: _validators[k] * 1, + delegated: 0, + delegates: [], + } + for (let k2 in _delegators ?? {}) { + obj.delegated += _delegators[k2] + obj.delegates.push(k2) + } + validators.push(obj) + } + } + return ( + <> +
+ {isDashboard ? ( + + + + + + + Your tDB + + + + {n(balance?.amount)} + + + = {((balance?.amount ?? 0) / 10 ** 13).toFixed(0)} USD + + + + + + + : + tDB ( Testnet Database ) Token + + + Address :{" "} + + + {process.env.NEXT_PUBLIC_TDB} + + + + + Total Supply : 10,000,000,000 + + + Reward Pool : {n(stakeStats?.Rewards?.pool)} tDB / year + + + Total Deposit : {n(stakeStats?.TotalDeposit, 18)} tDB + + + Total Stakes : {n(stakeStats?.TotalStake, 18)} taoETH + + + + + + + + + Staking Amount + + + + {n(myStats?.stake, 18)} + + + Yield : {n(dayYield, 0)} tDB / day + + + + + Staking Yields + + + + {n(myStats?.["yield"])} + + + All Time : {n(alltime_yield)} + + + + + Validator Rewards + + + + {n(myStats?.reward)} + + + All Time : {n(alltime_reward)} + + + + + Total Earnings + + + + {n(withdrawable)} + + 0 ? "#5137C5" : "#999"} + color="white" + py={1} + sx={{ + borderRadius: "3px", + cursor: withdrawable > 0 ? "pointer" : "default", + ":hover": { opacity: 0.75 }, + }} + onClick={async () => { + if (!withdrawing && withdrawable > 0) { + setWithdrawing(true) + const { addr, p } = await getP( + process.env.NEXT_PUBLIC_STAKING, + jwk, + ) + try { + await p.m("Withdraw-DB", null, { + check: /withdrew/, + jwk, + }) + toast({ + title: `Reward withdrawn!`, + status: "success", + duration: 3000, + isClosable: true, + }) + } catch (e) {} + setWithdrawing(false) + } + }} + > + {withdrawing ? ( + + ) : ( + Withdraw + )} + + + + + {map(_v => { + const v = _v.info + const node = nodes[v.node] + let mystake = 0 + if (!is(Array, v.stakes)) { + for (let k in v.stakes) { + if (k === addr) mystake += v.stakes[k] * 1 + } + } + if (!is(Array, v.delegates)) { + for (let k in v.delegates) { + for (let k2 in v.delegates[k]) { + if (k === addr) mystake += v.delegates[k][k2] * 1 + } + } + } + return ( + + + + {_v.id.slice(0, 7)} + + + {(node?.info.url ?? "").replace( + /^http(s){0,1}:\/\//, + "", + )} + + + + + {n(v.price)} + + tDB + + + + Price / Tx + + + + + {v.txs} + + txs + + + + Writes + + + + + {n(v.deposit)} + + tDB + + + + Deposit + + + + + {n(v.profit)} + + tDB + + + + Total Profit + + + + + {n(v.stake, 18)} + + taoETH + + + + Total Stake + + + + + {n(mystake, 18)} + + taoETH + + + + Your Stake + + + + setIsStake(_v.id)} + > + Stake + + + + ) + })(adbs)} + + + + + ) : isWallet ? ( + + + + {isReceive ? ( + <> + + + { + setIsReceive(false) + }} + sx={{ + borderRadius: "50%", + cursor: "pointer", + ":hover": { opacity: 0.75, bg: "#eee" }, + }} + > + + + + + Receive + + + + + + + {addr} + + + + ) : isSend ? ( + <> + {isScan ? ( + + + { + setIsScan(false) + try { + html5QrcodeScanner.clear() + } catch (e) {} + }} + sx={{ + borderRadius: "50%", + cursor: "pointer", + ":hover": { opacity: 0.75, bg: "#eee" }, + }} + > + + + + + Scan + + + + ) : ( + + + setIsSend(false)} + sx={{ + borderRadius: "50%", + cursor: "pointer", + ":hover": { opacity: 0.75, bg: "#eee" }, + }} + > + + + + + Send + + + + )} + + {isScan ? null : ( + + + From + + + + To + + { + setIsScan(true) + function onScanSuccess(decodedText, decodedResult) { + if (!validAddress(decodedText)) { + toast({ + title: `Not a Valid Arweave Address!`, + status: "error", + duration: 3000, + isClosable: true, + }) + } else { + setTo(decodedText) + html5QrcodeScanner.clear() + setIsScan(false) + toast({ + title: `Wallet Address Detected!`, + status: "success", + duration: 3000, + isClosable: true, + }) + } + } + html5QrcodeScanner = new Html5QrcodeScanner( + "reader", + { fps: 10, qrbox: { width: 250, height: 250 } }, + false, + ) + html5QrcodeScanner.render(onScanSuccess) + }} + > + Scan + + + setTo(e.target.value)} + color={to_ok ? "#222" : "crimson"} + sx={{ + border: to_ok + ? "#E2E8F0 1px solid" + : "crimson 1px solid", + }} + /> + + + Balance:{" "} + {Math.floor( + (_token.balance ? _token.balance.amount : 0) / + 10 ** _token.decimal, + )}{" "} + {_token.tick} + + + + setSendAmount( + Math.floor( + (_token.balance ? _token.balance.amount : 0) / + 10 ** _token.decimal, + ), + ) + } + > + Max + + + { + if ( + !Number.isNaN(+e.target.value) && + Math.round(e.target.value * 1) === +e.target.value + ) { + setSendAmount(e.target.value) + } + }} + /> + { + if (send_ok && !sending) { + let err + try { + setSending(true) + let _addr + if (jwk) { + _addr = addr + } else { + await arweaveWallet.connect([ + "ACCESS_ADDRESS", + "SIGN_TRANSACTION", + "ACCESS_PUBLIC_KEY", + ]) + _addr = await arweaveWallet.getActiveAddress() + } + + const ao = await new AO(opt).init( + jwk ?? arweaveWallet, + ) + let winston = "" + for (let i = 0; i < _token.decimal; i++) { + winston += "0" + } + const { err: _err, res } = await ao.msg({ + pid: token, + act: "Transfer", + tags: { + Recipient: to, + Quantity: `${sendAmount}${winston}`, + }, + }) + if (_err) { + err = _err.toString() + } else { + toast({ + title: `${sendAmount} ${_token.tick} Token Sent!`, + status: "success", + duration: 5000, + isClosable: true, + }) + setSendAmount("0") + setIsSend(false) + try { + const { out } = await ao.dry({ + pid: token, + act: "Balance", + tags: { Target: _addr }, + get: "Balance", + }) + if (_token.tick === "tDB") { + setBalance({ amount: out * 1, addr: _addr }) + } else { + setBalanceETH({ + amount: out * 1, + addr: _addr, + }) + } + } catch (e) {} + } + } catch (e) { + err = e.toString() + } + setSending(false) + if (err) { + toast({ + title: `Something Went Wrong!`, + status: "error", + description: err, + duration: 5000, + isClosable: true, + }) + } + } + }} + > + {sending ? ( + + ) : ( + "Send" + )} + + + )} + + ) : ( + <> + + { + navigator.clipboard.writeText(addr) + toast({ + title: `Copied!`, + status: "success", + duration: 3000, + isClosable: true, + }) + }} + > + {addr} + + + + + + {Math.floor( + (_token.balance ? _token.balance.amount : 0) / + 10 ** _token.decimal, + )}{" "} + {_token.tick} + + + + + setIsSend(true)} + bg="#5137C5" + color="white" + w="40px" + height="40px" + justify="center" + align="center" + sx={{ + borderRadius: "50%", + cursor: "pointer", + ":hover": { opacity: 0.75 }, + }} + > + + + + Send + + + + setIsReceive(true)} + bg="#5137C5" + color="white" + w="40px" + height="40px" + justify="center" + align="center" + sx={{ + borderRadius: "50%", + cursor: "pointer", + ":hover": { opacity: 0.75 }, + }} + > + + + + Receive + + + {!jwk ? null : ( + + { + const jsonString = JSON.stringify(jwk, null, 2) + const blob = new Blob([jsonString], { + type: "application/json", + }) + const url = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = `arweave-keyfile-${addr}.json` + a.click() + URL.revokeObjectURL(url) + }} + > + + + + Keyfile + + + )} + + + { + if (confirm("Disconnect your wallet?")) { + setIsWallet(false) + setIsDashboard(false) + setAddr(null) + setJwk(null) + } + }} + > + + + + Log out + + + + + {map(v => { + return ( + setWalletTab(v)} + > + {v} + + ) + })(["Tokens", "Activity"])} + + {walletTab === "Tokens" ? ( + <> + {map(v => { + return ( + setToken(v.addr)} + p={4} + bg={token === v.addr ? "#f7f7f7" : ""} + sx={{ + borderBottom: "1px solid #ddd", + cursor: "pointer", + ":hover": { opacity: 0.75 }, + }} + > + + + {v.tick} + + + {Math.floor( + (v.balance ? v.balance.amount : 0) / + 10 ** v.decimal, + )}{" "} + {v.tick} + + ) + })([ + { + tick: "tDB", + logo: "/logo.svg", + balance, + decimal: 12, + addr: process.env.NEXT_PUBLIC_TDB, + }, + { + tick: "taoETH", + logo: "/eth.png", + balance: balanceETH, + decimal: 18, + addr: process.env.NEXT_PUBLIC_ETH, + }, + ])} + + ) : ( + + No Activity Yet + + )} + + )} + + + + ) : ( + <> + + + + {map(v => { + return ( + { + if (v.soon) return alert("Coming Soon!") + setTab(v.key) + }} + > + {v.name} + + ) + })(tabs)} + + + + {tab === "about" ? ( + + ) : tab === "create" ? ( + + + + + + Node Info + + + + + + + Select Node + + + + + + + + gRPC + + + {node?.info.url} + + + + + Bundler + + + + {node?.info.admin} + + + + + + Subledger + + + + {process.env.NEXT_PUBLIC_ADMIN_CONTRACT} + + + + + + Security + + + {Math.floor( + (node?.info.deposit ?? 0) / 1000000000000000000, + )}{" "} + taoETH + + + + + + + DB Name + + + AO TxId + + + DB Admin + + + {map(v => { + return ( + + + + + {v.id} + + + + + + + {v.data.contractTxId.slice(0, 20)}... + + + + + {!v.data.admin ? null : ( + + + {v.data.admin.slice(0, 20)}... + + + )} + + + ) + })(reverse(dbs))} + + + + + + Deposit tDB + + + + Balance: + + {(balance ? balance.amount : 0) / 1000000000000} tDB + + + + Deposit: + {deposit / 1000000000000} tDB + + + {!addr ? ( + + Connect wallet. + + ) : ( + + + + Operation + + + + Amount (tDB) + { + if ( + !Number.isNaN(+e.target.value) && + Math.round(e.target.value * 1) === + +e.target.value + ) { + setAmount(e.target.value) + } + }} + /> + + + + { + if (!depositing && deposit_ok) { + setDepositing(true) + try { + let _addr + if (jwk) { + _addr = addr + } else { + await arweaveWallet.connect([ + "ACCESS_ADDRESS", + "SIGN_TRANSACTION", + "ACCESS_PUBLIC_KEY", + ]) + _addr = + await arweaveWallet.getActiveAddress() + } + const ao = await new AO(opt).init( + jwk ?? arweaveWallet, + ) + const winston = "000000000000" + if (op === "Deposit") { + const { err, res } = await ao.msg({ + pid: process.env.NEXT_PUBLIC_TDB, + act: "Transfer", + tags: { + Recipient: + process.env + .NEXT_PUBLIC_ADMIN_CONTRACT, + Quantity: `${amount}${winston}`, + }, + }) + } else { + const { err, res } = await ao.msg({ + pid: process.env + .NEXT_PUBLIC_ADMIN_CONTRACT, + act: "Withdraw", + tags: { + Quantity: `${amount}${winston}`, + }, + }) + } + await wait(3000) + const { out } = await ao.dry({ + pid: process.env.NEXT_PUBLIC_TDB, + act: "Balance", + tags: { Target: _addr }, + get: "Balance", + }) + setBalance({ amount: out * 1, addr: _addr }) + + const { out: out2 } = await ao.dry({ + pid: process.env + .NEXT_PUBLIC_ADMIN_CONTRACT, + act: "Balance", + tags: { Target: _addr }, + get: true, + }) + setDeposit(out2 * 1) + setAddr(_addr) + } catch (e) { + console.log(e) + } + setDepositing(false) + } + }} + > + {depositing ? ( + + ) : ( + op + )} + + + + + )} + + + Create DB + + + + Deposit: + {deposit / 1000000000000} tDB + + + Cost: + 100 tDB + + + {!addr ? ( + + Connect wallet. + + ) : ( + + + + New DB Name + setDBName(e.target.value)} + /> + + + { + if (!deploying && deploy_ok) { + await arweaveWallet.connect([ + "ACCESS_ADDRESS", + "SIGN_TRANSACTION", + "ACCESS_PUBLIC_KEY", + ]) + const addr = + await arweaveWallet.getActiveAddress() + const ao = new AO(opt) + const { out: out2 } = await ao.dry({ + pid: process.env.NEXT_PUBLIC_TDB, + act: "Balances", + get: { data: true, json: true }, + }) + const { out } = await ao.dry({ + pid: process.env.NEXT_PUBLIC_ADMIN_CONTRACT, + act: "Balances", + get: { data: true, json: true }, + }) + const deposit = out?.[addr] ?? 0 + if (deposit < 100000000000000) { + toast({ + title: "Something Went Wrong!", + status: "error", + description: + "Your tDB token deposit is not enough.", + duration: 5000, + isClosable: true, + }) + return + } + setDeploying(true) + const db = new DB({ + rpc: node.info.url, + contractTxId: dbname, + arweave: network, + }) + try { + const start = Date.now() + const tx = await db.admin( + { + op: "add_db", + key: dbname, + db: { + rollup: true, + owner: addr, + }, + }, + { ar2: jwk ?? arweaveWallet }, + ) + const { contractTxId, srcTxId } = + await db.admin( + { + op: "deploy_contract", + key: dbname, + type: "ao", + }, + { ar2: jwk ?? arweaveWallet }, + ) + const duration = Date.now() - start + setLatency4({ + dbname, + txid: contractTxId, + duration, + }) + const stats = await db.node({ op: "stats" }) + setStats(stats) + const _dbs = filter( + v => + !isNil(v.data.admin) && + !isNil(v.data.contractTxId), + )(stats.dbs) + setDBs(_dbs) + setDBName("") + toast({ + title: `DB Deployed in ${duration} ms!`, + description: contractTxId, + status: "success", + duration: 5000, + isClosable: true, + }) + const p = new AO(opt).p( + process.env.NEXT_PUBLIC_STAKING, + ) + setStakeStats(await p.d("Info")) + setAllDBs(await p.d("Get-DBs")) + if (dbname2 === "") setDBName2(dbname) + } catch (e) { + toast({ + title: "Something Went Wrong!", + status: "error", + description: e.toString(), + duration: 5000, + isClosable: true, + }) + } + setDeploying(false) + } + }} + > + {deploying ? ( + + ) : ( + "Create" + )} + + + + setShowCode(!showCode)} + > + {showCode ? "Hide JS Code" : "Show JS Code"} + + + {!showCode ? null : ( + + + {codeDeploy({ dbname, owner: addr ?? "" })} + + + )} + + )} + + {latency4 ? ( + <> + + + + WeaveDB + + + + + + AO + + + + + + Ethereum + + + + + DB deployed in{" "} + + {latency4.duration} ms + + + + + ) : null} + + + Add Node + + + + Balance: + + {(balanceETH ? balanceETH.amount : 0) / + 1000000000000000000}{" "} + taoETH + + + + Min Stake: + 1 taoETH + + + + + + Node gRPC URL + setGRPC(e.target.value)} + /> + + + Stake (taoETH) + { + if ( + !Number.isNaN(+e.target.value) && + Math.round(e.target.value * 1) === + +e.target.value + ) { + setNodeDeposit(e.target.value) + } + }} + /> + + + { + if (!adding && add_node_ok) { + let err + try { + setAdding(true) + const { addr, p } = await getP( + process.env.NEXT_PUBLIC_ETH, + jwk, + ) + const { err: _err, res } = await p.msg( + "Transfer", + { + Recipient: + process.env.NEXT_PUBLIC_STAKING, + Quantity: `${nodeDeposit}${gwei}`, + "X-Action": "Add-Node", + "X-URL": grpc, + }, + { check: /transferred/ }, + ) + if (_err) { + err = _err.toString() + } else { + toast({ + title: `Node Added!`, + status: "success", + description: grpc, + duration: 5000, + isClosable: true, + }) + setGRPC("https://") + try { + const out = await p.d( + "Balance", + { Target: addr }, + { get: "Balance" }, + ) + setBalanceETH({ amount: out * 1, addr }) + const { p: p2 } = await getP( + process.env.NEXT_PUBLIC_STAKING, + jwk, + ) + await wait(3000) + const _nodes = await p2.d("Get-Nodes") + let __nodes = [] + let n = null + for (const k in _nodes) { + const _n = { id: k, info: _nodes[k] } + if (!n) n = _n + __nodes.push(_n) + } + if (!node) setNode(n) + setNodes(__nodes) + } catch (e) { + console.log(e) + } + } + } catch (e) { + err = e.toString() + } + setAdding(false) + if (err) { + toast({ + title: `Something Went Wrong!`, + status: "error", + description: err, + duration: 5000, + isClosable: true, + }) + } + } + }} + > + {adding ? ( + + ) : ( + Add + )} + + + + + + + + ) : tab === "query" || tab === "zkjson" ? ( + + + + + + Database + + + + + + + Choose DB + + + + Collections + + + + + + + + + DB Name + + + + {dbname2} + + + + + + AO Process + + + + {dbmap[dbname2]?.data?.contractTxId} + + + + + + Owner + + + + {dbmap[dbname2]?.data?.admin} + + + + + + Collections + + + [ {cols.length} ] {cols.join(", ")} + + + + + + {dbname2}{" "} + + {!selectedCol ? ( + - + ) : ( + + {selectedCol} [ {profiles.length} items ] + + )} + + + + + + name + + + age + + + married + + + favorites + + + {map(v => { + return ( + setSelectedData(v)} + > + + {v.name} + + + {v.age} + + + {v.married ? "true" : "false"} + + + {(v.favorites ?? []).join(", ")} + + + ) + })(profiles)} + + + + {tab === "query" ? ( + <> + + + Add Collection + + + {!addr ? ( + + Connect wallet. + + ) : ( + + + + New Collection Name + setColName(e.target.value)} + /> + + + Data Schema + + + + { + if (add_ok) { + let err = null + let txid = null + let txid2 = null + try { + const contractTxId = indexBy( + prop("id"), + dbs, + )[dbname2].data.contractTxId + const db = new DB({ + rpc: node.info.url, + contractTxId, + arweave: network, + }) + const start = Date.now() + const rules = [["write", [["allow()"]]]] + const tx2 = await db.setRules( + rules, + colName, + { + ar2: jwk ?? arweaveWallet, + }, + ) + console.log(tx2) + if (!tx2.success) { + err = "error" + } else { + txid = tx2.originalTxId + const schema = { + type: "object", + required: [ + "name", + "age", + "married", + ], + properties: { + name: { type: "string" }, + age: { type: "number" }, + married: { type: "boolean" }, + favorites: { + type: "array", + items: { type: "string" }, + }, + }, + } + const tx3 = await db.setSchema( + schema, + colName, + { ar2: jwk ?? arweaveWallet }, + ) + if (tx3.success) { + txid2 = tx3.originalTxId + setCols(await db.listCollections()) + setLatency5({ + dbname: dbname2, + duration: Date.now() - start, + txid, + txid2, + }) + setSelectedCol(colName) + setProfiles([]) + } else { + err = tx3.error.toString() + } + } + } catch (e) { + console.log(e) + err = "error" + } + if (err) { + toast({ + title: "Something Went Wrong!", + status: "error", + description: err, + duration: 5000, + isClosable: true, + }) + } else { + toast({ + title: "Collection Added!", + status: "success", + description: `${colName}`, + duration: 5000, + isClosable: true, + }) + } + } + }} + > + Add + + + + setShowCode2(!showCode2)} + > + {showCode2 ? "Hide JS Code" : "Show JS Code"} + + + {!showCode2 ? null : ( + + + {codeDeploy2({ colName })} + + + )} + + )} + + {latency5 ? ( + <> + + + Rules + + + + + Schema + + + + + Collection added in{" "} + + {latency5.duration} ms + + + + ) : null} + + + + Store Data on WeaveDB + + + {!addr ? ( + + Connect wallet. + + ) : ( + + + + + + Name + setName(e.target.value)} + /> + + + Age + + + + Married + + + + + Favorites + + {map(v => { + return ( + { + if (includes(v, favs)) { + setFavs(without([v], favs)) + } else { + setFavs(append(v, favs)) + } + }} + > + + {v} + + ) + })([ + "apple", + "orange", + "grape", + "peach", + "lemon", + ])} + + + + + setShowCode3(!showCode3)} + > + {showCode3 ? "Hide JS Code" : "Show JS Code"} + + + {!showCode3 ? null : ( + + + {codeDeploy3({ + name, + age, + married, + favorites: favs, + col: selectedCol, + })} + + + )} + { + if (save_ok) { + let err = null + try { + const contractTxId = indexBy( + prop("id"), + dbs, + )[dbname2].data.contractTxId + const db = new DB({ + rpc: node.info.url, + contractTxId, + }) + const ppl = { + name, + age: +age, + married: + married === "true" ? true : false, + favorites: favs, + } + const start = Date.now() + const tx3 = await db.set( + ppl, + selectedCol, + name, + { + ar2: jwk ?? arweaveWallet, + }, + ) + if (tx3.success) { + setLatency({ + dbname: dbname2, + duration: Date.now() - start, + txid: tx3.originalTxId, + }) + setProfiles(await db.get(selectedCol)) + let updated = true + /* + setTimeout(async () => { + do { + updated = await commit({ + rpc: node.info.url, + committing, + dbname2, + dbs, + setCommitting, + _alert: false, + }) + } while (updated) + }, 5000)*/ + toast({ + title: "Doc Added!", + status: "success", + description: `${name}`, + duration: 5000, + isClosable: true, + }) + setSelectedData(ppl) + } else { + toast({ + title: "Something Went Wrong!", + status: "error", + description: "error", + duration: 5000, + isClosable: true, + }) + } + } catch (e) { + err = e.toString() + } + if (err) { + toast({ + title: "Something Went Wrong!", + status: "error", + description: err, + duration: 5000, + isClosable: true, + }) + } + } + }} + > + Save + + + )} + + {latency ? ( + <> + + + Doc + + + + + Doc stored in{" "} + + {latency.duration} ms + + + + ) : null} + + + + Query Data + + + + + + {search === "single" ? ( + + Doc ID + setQuery(e.target.value)} + /> + + ) : ( + + + + Sort + + + + Order + + + + Limit + { + if ( + !Number.isNaN(+e.target.value) && + Math.round(e.target.value * 1) === + +e.target.value + ) { + setLimit(e.target.value) + } + }} + /> + + + + + + Where + + + + + + Operator + + + + + + Value + {includes(operator, [ + "in", + "not-in", + "array-contains-any", + ]) + ? " (csv)" + : ""} + + { + setValue(e.target.value) + }} + /> + + + + )} + + setShowCode4(!showCode4)} + > + {showCode4 ? "Hide JS Code" : "Show JS Code"} + + + {!showCode4 ? null : ( + + + {codeDeploy4({ + selectedCol, + limit, + order, + query, + search, + operator, + value, + sort, + })} + + + )} + + { + if (query_ok) { + setLoading(true) + setData(null) + const contractTxId = indexBy(prop("id"), dbs)[ + dbname2 + ].data.contractTxId + const db = new DB({ + rpc: node.info.url, + contractTxId, + }) + const start = Date.now() + + const q = getQ({ + order, + query, + search, + selectedCol, + limit, + operator, + value, + sort, + }) + const tx3 = await db.get(...q) + setData(tx3) + if (search === "single") setSelectedData(tx3) + setLatency2({ + txid: contractTxId, + duration: Date.now() - start, + }) + setWhich("WeaveDB Rollup") + setLoading(false) + } + }} + > + Query Rollup + + { + if (query_ok) { + setLoading(true) + setData(null) + const contractTxId = indexBy(prop("id"), dbs)[ + dbname2 + ].data.contractTxId + const ao = new AO(opt) + const start = Date.now() + const q = getQ({ + order, + query, + search, + selectedCol, + limit, + operator, + value, + sort, + }) + const b = ( + await ao.dry({ + pid: contractTxId, + act: "Get", + tags: { + Query: JSON.stringify(q), + }, + get: true, + }) + ).out + + setLatency2({ + txid: contractTxId, + duration: Date.now() - start, + }) + setWhich("AO Process") + setData(b) + if (search === "single") setSelectedData(b) + setLoading(false) + } + }} + > + Query AO + + + + {loading ? ( + "querying..." + ) : !data ? ( + "data not found..." + ) : ( + + + + name + + + age + + + married + + + favorites + + + {map(v => { + return ( + + + {v.name} + + + {v.age} + + + {v.married ? "true" : "false"} + + + {(v.favorites ?? []).join(", ")} + + + ) + })(is(Array, data) ? data : [data])} + + )} + + + + {latency2 ? ( + <> + + + Queried on {which} in{" "} + + {latency2.duration} ms + + + + ) : null} + + + ) : ( + <> + + + Generate ZKP + + + + Deposit: + {deposit / 1000000000000} tDB + + + Cost: + 0 tDB + + + {!addr ? ( + + Connect wallet. + + ) : isNil(selectedData) ? ( + + Select a doc. + + ) : ( + <> + + + + Field + + + + Query Type + + + + + Value + {includes(qtype, [ + "in", + "contains_any", + "contains_all", + "contains_none", + ]) + ? " (csv)" + : ""} + + setQValue(e.target.value)} + /> + + + { + let err = null + if (zkp_ok && !generating) { + setGenerating(true) + const contractTxId = indexBy( + prop("id"), + dbs, + )[dbname2].data.contractTxId + const db = new DB({ + rpc: node.info.url, + contractTxId, + }) + const start = Date.now() + try { + let _zkp = { + data: selectedData, + col: selectedCol, + db: dbname2, + txid: contractTxId, + tar: tar, + qtype, + } + let params = { + op: "zkp", + key: dbname2, + collection: selectedCol, + doc: selectedData.name, + path: tar, + } + if ( + qtype !== "disclosure" && + !/^\s*$/.test(qvalue) + ) { + params.query = ["$" + qtype] + let qv = null + if ( + includes(qtype, [ + "in", + "contains_any", + "contains_all", + "contains_none", + ]) + ) { + qv = map(v => { + let v2 = trim(v) + if (tar === "age") { + return v2 * 1 + } else if (tar === "married") { + return v2 === "false" + ? false + : true + } else { + return v2 + } + })(qvalue.split(",")) + } else { + if (tar === "age") { + qv = qvalue * 1 + } else if (tar === "married") { + qv = + qvalue === "false" ? false : true + } else { + qv = qvalue + } + } + params.query.push(qv) + _zkp.qvalue = qv + } + + _zkp.query = params.query + const zkp = await db.node(params) + if (!isNil(zkp.zkp)) { + setLatency3(Date.now() - start) + setZKP({ + ..._zkp, + zkp: zkp.zkp, + col_id: zkp.col_id, + }) + setData2(null) + toast({ + title: "ZKP Generated!", + status: "success", + description: `${params.key} > ${params.collection} > ${params.doc} > ${params.path} (${qtype === "disclosure" ? "Selective Disclosure" : `${qvalue}`})`, + duration: 5000, + isClosable: true, + }) + } else { + console.log(zkp) + err = "error" + } + } catch (e) { + console.log(e) + err = e.toString() + } + setGenerating(false) + if (err) { + toast({ + title: "Something Went Wrong!", + status: "error", + description: err, + duration: 5000, + isClosable: true, + }) + } + } + }} + > + {generating ? ( + + ) : ( + "Generate ZKP" + )} + + {!zkp ? null : ( + <> + + AO Process TxID + + { + navigator.clipboard.writeText(processId) + }} + > + Copy + + + + {processId} + + + Data Path + + { + navigator.clipboard.writeText( + JSON.stringify(_path).replace( + /"/g, + "", + ), + ) + }} + > + Copy + + + + {JSON.stringify(_path).replace(/"/g, "")} + + + Zero Knowledge Proof + + { + navigator.clipboard.writeText( + JSON.stringify(zkp.zkp).replace( + /"/g, + "", + ), + ) + }} + > + Copy + + + + {JSON.stringify(zkp.zkp).replace(/"/g, "")} + + + )} + setShowCode5(!showCode5)} + > + {showCode5 ? "Hide JS Code" : "Show JS Code"} + + + {!showCode5 ? null : ( + + + {codeDeploy5({ + db: dbname2, + col: selectedCol, + doc: query, + tar, + })} + + + )} + + + {latency3 ? ( + <> + + Etherscan ({" "} + {!isNil(zkp.qvalue) + ? "qCond" + : zkp.tar === "name" + ? "qString" + : zkp.tar === "age" + ? "qInt" + : "qBool"}{" "} + ) + + + + ZKP generated in{" "} + + {latency3} ms + + + + ) : null} + + + )} + + + + Query from Ethereum with ZKP + + + {!zkp ? ( + + Generate a ZKP. + + ) : ( + <> + + + + + DB + + {zkp.db} + + + + Collection + + {zkp.col} + + + + Doc + + {zkp.data.name} + + + + Field + + {zkp.tar} + + + + Query Type + + {zkp.qtype === "disclosure" + ? "Selective Disclosure" + : "$" + zkp.qtype}{" "} + {isNil(zkp.qvalue) ? null : ( + <> + {is(Array, zkp.qvalue) + ? zkp.qvalue.join(", ") + : zkp.qvalue} + + )} + + + + + + {data2 ?? ""} + + { + if (eth_ok && !querying) { + let err = null + setQuerying(true) + try { + const contractTxId = indexBy( + prop("id"), + dbs, + )[zkp.db].data.contractTxId + const db = new DB({ + rpc: node.info.url, + contractTxId, + }) + const hash = ( + await db.node({ + op: "hash", + key: zkp.db, + }) + ).hash + const contract = new Contract( + process.env.NEXT_PUBLIC_CONTRACT, + abi, + provider, + ) + const start = Date.now() + let res = null + if (!isNil(zkp.qvalue)) { + const cond = zkp.zkp.slice(13, 18) + res = (await contract.qCond( + contractTxId, + _path, + cond, + zkp.zkp, + )) + ? "true" + : "false" + setData2(res) + } else if (zkp.tar === "age") { + res = ( + await contract.qInt( + contractTxId, + _path, + zkp.zkp, + ) + ).toString() + setData2(res) + } else if (zkp.tar === "married") { + res = (await contract.qBool( + contractTxId, + _path, + zkp.zkp, + )) + ? "true" + : "false" + setData2(res) + } else { + res = await contract.qString( + contractTxId, + _path, + zkp.zkp, + ) + setData2(res) + } + setLatency6(Date.now() - start) + toast({ + title: "Queried from Ethereum!", + status: "success", + description: res, + duration: 5000, + isClosable: true, + }) + } catch (e) { + console.log(e) + err = e.toString() + } + + setQuerying(false) + if (err) { + if (err.match(/mismatch/)) { + err = "Root Mismatch" + } else if (err.match(/match/)) { + err = "Network Error: Try again" + } else { + err = "Invalid ZKP" + } + toast({ + title: "Something Went Wrong!", + status: "error", + description: err, + duration: 5000, + isClosable: true, + }) + } + } + }} + > + {querying ? ( + + ) : ( + "Query from Ethereum" + )} + + + + setShowCode6(!showCode6)} + > + {showCode6 ? "Hide JS Code" : "Show JS Code"} + + {!showCode6 ? null : ( + + + {codeDeploy6({ + txid: zkp.txid, + path: `[${map(v => `"${v}"`)(_path)}]`, + zkp: "zkp", + fn: + zkp.tar === "name" + ? "qString" + : zkp.tar === "age" + ? "qInt" + : "qBool", + })} + + + )} + + + <> + {committing[zkp?.db] ? ( + + + Committing root... + + ) : ( + { + let updated = true + do { + updated = await commit({ + rpc: node.info.url, + committing, + dbname2: zkp.db, + dbs, + setCommitting, + _alert: updated, + }) + } while (updated) + }} + > + Commit Root + + )} + + {latency6 ? ( + + Queried in{" "} + + {latency6} ms + + + ) : null} + + + + )} + + )} + + + + ) : ( + + + + {map(v => { + return map(v2 => { + return ( + + alert("Coming Soon!")} + p={4} + color="#9C89F6" + sx={{ + cursor: "pointer", + ":hover": { opacity: 0.75 }, + borderRadius: "5px", + border: "#9C89F6 1px solid", + }} + > + + + {v2.title} + + + + ) + })(v) + })([ + [ + { + title: "Decentralized Social Apps", + }, + { + title: "zkOracles", + }, + { + title: "Token / Data Bridges", + }, + ], + [ + { + title: "zkNFT", + }, + { + title: "DeSci", + }, + { + title: "Blockchain History", + }, + ], + [ + { + title: "Private Databases", + }, + { + title: "Decentalized Point Systems", + }, + { + title: "AI Autonomous Databases", + }, + ], + ])} + + + + )} + + {tab === "about" ? ( + <> + + + + Backers + + + {`Supported Worldwide by Industry's Best`} + + + {map(v => { + return v.name ? ( + + + + {v.name} + + + {v.at} + + + + ) : ( + + + + + + ) + })([ + { + img: "permanent-ventures.webp", + href: "http://permanent.ventures", + }, + { img: "iosg.png", href: "https://iosg.vc" }, + { img: "mask.svg", href: "https://mask.io" }, + { + img: "forward-research.png", + href: "https://fwd.g8way.io", + }, + { + img: "hansa.svg", + py: 2, + href: "https://www.hansa.capital", + }, + { + img: "next-web-capital.webp", + height: "80px", + href: "https://nextweb.capital", + }, + { img: "cmtd.png", py: 2, href: "https://cmt.digital" }, + { + img: "formless-capital.webp", + href: "https://formless.capital", + }, + { + name: "Scott Moore", + at: "Gitcoin Founder", + href: "https://www.gitcoin.co", + }, + { + img: "cogitent.png", + href: "https://cogitent.ventures", + }, + { + name: "YY Lai", + at: "Signum Capital", + color: "#0082B9", + href: "https://signum.capital", + }, + { img: "hub71.svg", py: 2, href: "https://hub71.com" }, + ])} + + + + + + + Ecosystem + + + {`Who's Building on WeaveDB & zkJSON`} + + + + + ) : null} + + )} + {isWallet ? null :