From c4c057569791b37dfc1b7a1224c678d9175a6b7a Mon Sep 17 00:00:00 2001
From: erhant
Date: Thu, 16 May 2024 20:05:23 +0300
Subject: [PATCH 1/9] moved client and prover
---
.github/workflows/publish-client.yml | 71 +
.../{publish.yml => publish-core.yml} | 0
.github/workflows/publish-prover.yml | 71 +
.github/workflows/test-client.yml | 37 +
.github/workflows/{test.yml => test-core.yml} | 0
.github/workflows/test-prover.yml | 52 +
.prettierignore | 13 -
.prettierrc.cjs | 7 -
README.md | 7 -
docs/QUICK-START.md | 178 ++
docs/README.md | 194 ++
packages/client/.eslintignore | 5 +
packages/client/.eslintrc.json | 3 +
packages/client/.gitignore | 6 +
.parcelrc => packages/client/.parcelrc | 0
packages/client/.prettierignore | 5 +
packages/client/.prettierrc.cjs | 3 +
packages/client/LICENSE | 21 +
packages/client/README.md | 86 +
packages/client/jest.config.cjs | 8 +
packages/client/package.json | 87 +
packages/client/src/client.ts | 295 +++
packages/client/src/index.ts | 2 +
packages/client/src/utilities/index.ts | 32 +
.../hollow-authz-groth16/hollow-authz.wasm | Bin
.../hollow-authz-groth16/prover_key.zkey | Bin
.../verification_key.json | 0
.../hollow-authz-plonk/hollow-authz.wasm | Bin
.../hollow-authz-plonk/prover_key.zkey | Bin
.../hollow-authz-plonk/verification_key.json | 0
packages/client/tests/client.test.ts | 78 +
packages/client/tests/mocks/index.ts | 191 ++
packages/client/tests/refresh.test.ts | 53 +
packages/client/tests/utilities/index.ts | 40 +
packages/client/tsconfig.json | 17 +
.eslintignore => packages/core/.eslintignore | 0
.../core/.eslintrc.json | 0
packages/core/.gitignore | 27 +
packages/core/.parcelrc | 8 +
packages/core/LICENSE | 21 +
packages/core/README.md | 81 +
.../core/config}/circuits/README.md | 0
.../hollow-authz-groth16/hollow-authz.wasm | Bin 0 -> 1627954 bytes
.../hollow-authz-groth16/prover_key.zkey | Bin 0 -> 115376 bytes
.../verification_key.json | 104 +
.../hollow-authz-plonk/hollow-authz.wasm | Bin 0 -> 1627950 bytes
.../hollow-authz-plonk/prover_key.zkey | Bin 0 -> 7631048 bytes
.../hollow-authz-plonk/verification_key.json | 63 +
.../core/config}/wallets/.gitignore | 0
.../core/jest.config.ts | 0
packages/core/package.json | 114 +
{src => packages/core/src}/base/admin.ts | 0
{src => packages/core/src}/base/base.ts | 0
{src => packages/core/src}/base/index.ts | 0
{src => packages/core/src}/base/sdk.ts | 0
{src => packages/core/src}/bin/build.ts | 0
{src => packages/core/src}/bin/cli.ts | 0
{src => packages/core/src}/bin/deploy.ts | 0
{src => packages/core/src}/bin/evolve.ts | 0
{src => packages/core/src}/bin/transfer.ts | 0
{src => packages/core/src}/bin/utils/index.ts | 0
.../core/src}/contracts/README.md | 0
.../build/hollowdb-batch.contract.js | 0
.../contracts/build/hollowdb-htx.contract.js | 0
.../contracts/build/hollowdb-set.contract.js | 0
.../src}/contracts/build/hollowdb.contract.js | 0
.../contracts/build/hollowdb.old.contract.js | 0
.../core/src}/contracts/errors/index.ts | 0
.../src}/contracts/hollowdb-htx.contract.ts | 0
.../src}/contracts/hollowdb-set.contract.ts | 0
.../core/src}/contracts/hollowdb.contract.ts | 0
.../core/src}/contracts/modifiers/apply.ts | 0
.../core/src}/contracts/modifiers/index.ts | 0
.../contracts/states/hollowdb-htx.state.json | 0
.../contracts/states/hollowdb-set.state.json | 0
.../states/hollowdb.groth16.state.json | 0
.../src}/contracts/states/hollowdb.state.json | 0
.../core/src}/contracts/states/index.ts | 0
.../core/src}/contracts/types/contract.ts | 0
.../core/src}/contracts/types/index.ts | 0
.../core/src}/contracts/types/inputs.ts | 0
.../core/src}/contracts/utils/index.ts | 0
{src => packages/core/src}/globals.d.ts | 0
{src => packages/core/src}/hollowdb-set.ts | 0
{src => packages/core/src}/hollowdb.ts | 0
{src => packages/core/src}/index.ts | 0
{tests => packages/core/tests}/README.md | 0
{tests => packages/core/tests}/batch.test.ts | 0
.../core/tests}/constants/index.ts | 0
{tests => packages/core/tests}/crud.test.ts | 0
.../core/tests}/environment/setup.ts | 0
.../core/tests}/environment/teardown.ts | 0
{tests => packages/core/tests}/evolve.test.ts | 0
{tests => packages/core/tests}/hooks/index.ts | 0
{tests => packages/core/tests}/htx.test.ts | 0
{tests => packages/core/tests}/mock/bundlr.ts | 0
{tests => packages/core/tests}/multi.test.ts | 0
{tests => packages/core/tests}/null.test.ts | 0
{tests => packages/core/tests}/proofs.test.ts | 0
{tests => packages/core/tests}/set.test.ts | 0
{tests => packages/core/tests}/utils/cache.ts | 0
{tests => packages/core/tests}/utils/index.ts | 0
.../core/tests}/whitelists.test.ts | 0
.../core/tsconfig.base.json | 0
tsconfig.json => packages/core/tsconfig.json | 0
packages/prover/.eslintignore | 5 +
packages/prover/.eslintrc.json | 3 +
packages/prover/.gitignore | 6 +
packages/prover/.parcelrc | 8 +
packages/prover/.prettierignore | 5 +
packages/prover/.prettierrc.cjs | 4 +
packages/prover/LICENSE | 21 +
packages/prover/README.md | 55 +
.../prover/circuits/groth16/hollow-authz.wasm | Bin 0 -> 1627954 bytes
.../prover/circuits/groth16/prover_key.zkey | Bin 0 -> 115376 bytes
.../circuits/groth16/verification_key.json | 104 +
.../prover/circuits/plonk/hollow-authz.wasm | Bin 0 -> 1627950 bytes
.../prover/circuits/plonk/prover_key.zkey | Bin 0 -> 7631048 bytes
.../circuits/plonk/verification_key.json | 63 +
packages/prover/jest.config.cjs | 7 +
packages/prover/package.json | 94 +
packages/prover/src/index.ts | 100 +
packages/prover/tests/constants/index.ts | 12 +
packages/prover/tests/prover.test.ts | 84 +
packages/prover/tsconfig.json | 17 +
pnpm-lock.yaml | 2029 +++++++++++++++--
pnpm-workspace.yaml | 2 +
127 files changed, 4368 insertions(+), 231 deletions(-)
create mode 100644 .github/workflows/publish-client.yml
rename .github/workflows/{publish.yml => publish-core.yml} (100%)
create mode 100644 .github/workflows/publish-prover.yml
create mode 100644 .github/workflows/test-client.yml
rename .github/workflows/{test.yml => test-core.yml} (100%)
create mode 100644 .github/workflows/test-prover.yml
delete mode 100644 .prettierignore
delete mode 100644 .prettierrc.cjs
create mode 100644 docs/QUICK-START.md
create mode 100644 docs/README.md
create mode 100644 packages/client/.eslintignore
create mode 100644 packages/client/.eslintrc.json
create mode 100644 packages/client/.gitignore
rename .parcelrc => packages/client/.parcelrc (100%)
create mode 100644 packages/client/.prettierignore
create mode 100644 packages/client/.prettierrc.cjs
create mode 100644 packages/client/LICENSE
create mode 100644 packages/client/README.md
create mode 100644 packages/client/jest.config.cjs
create mode 100644 packages/client/package.json
create mode 100644 packages/client/src/client.ts
create mode 100644 packages/client/src/index.ts
create mode 100644 packages/client/src/utilities/index.ts
rename {config => packages/client/tests}/circuits/hollow-authz-groth16/hollow-authz.wasm (100%)
rename {config => packages/client/tests}/circuits/hollow-authz-groth16/prover_key.zkey (100%)
rename {config => packages/client/tests}/circuits/hollow-authz-groth16/verification_key.json (100%)
rename {config => packages/client/tests}/circuits/hollow-authz-plonk/hollow-authz.wasm (100%)
rename {config => packages/client/tests}/circuits/hollow-authz-plonk/prover_key.zkey (100%)
rename {config => packages/client/tests}/circuits/hollow-authz-plonk/verification_key.json (100%)
create mode 100644 packages/client/tests/client.test.ts
create mode 100644 packages/client/tests/mocks/index.ts
create mode 100644 packages/client/tests/refresh.test.ts
create mode 100644 packages/client/tests/utilities/index.ts
create mode 100644 packages/client/tsconfig.json
rename .eslintignore => packages/core/.eslintignore (100%)
rename .eslintrc.json => packages/core/.eslintrc.json (100%)
create mode 100644 packages/core/.gitignore
create mode 100644 packages/core/.parcelrc
create mode 100644 packages/core/LICENSE
create mode 100644 packages/core/README.md
rename {config => packages/core/config}/circuits/README.md (100%)
create mode 100644 packages/core/config/circuits/hollow-authz-groth16/hollow-authz.wasm
create mode 100644 packages/core/config/circuits/hollow-authz-groth16/prover_key.zkey
create mode 100644 packages/core/config/circuits/hollow-authz-groth16/verification_key.json
create mode 100644 packages/core/config/circuits/hollow-authz-plonk/hollow-authz.wasm
create mode 100644 packages/core/config/circuits/hollow-authz-plonk/prover_key.zkey
create mode 100644 packages/core/config/circuits/hollow-authz-plonk/verification_key.json
rename {config => packages/core/config}/wallets/.gitignore (100%)
rename jest.config.ts => packages/core/jest.config.ts (100%)
create mode 100644 packages/core/package.json
rename {src => packages/core/src}/base/admin.ts (100%)
rename {src => packages/core/src}/base/base.ts (100%)
rename {src => packages/core/src}/base/index.ts (100%)
rename {src => packages/core/src}/base/sdk.ts (100%)
rename {src => packages/core/src}/bin/build.ts (100%)
rename {src => packages/core/src}/bin/cli.ts (100%)
rename {src => packages/core/src}/bin/deploy.ts (100%)
rename {src => packages/core/src}/bin/evolve.ts (100%)
rename {src => packages/core/src}/bin/transfer.ts (100%)
rename {src => packages/core/src}/bin/utils/index.ts (100%)
rename {src => packages/core/src}/contracts/README.md (100%)
rename {src => packages/core/src}/contracts/build/hollowdb-batch.contract.js (100%)
rename {src => packages/core/src}/contracts/build/hollowdb-htx.contract.js (100%)
rename {src => packages/core/src}/contracts/build/hollowdb-set.contract.js (100%)
rename {src => packages/core/src}/contracts/build/hollowdb.contract.js (100%)
rename {src => packages/core/src}/contracts/build/hollowdb.old.contract.js (100%)
rename {src => packages/core/src}/contracts/errors/index.ts (100%)
rename {src => packages/core/src}/contracts/hollowdb-htx.contract.ts (100%)
rename {src => packages/core/src}/contracts/hollowdb-set.contract.ts (100%)
rename {src => packages/core/src}/contracts/hollowdb.contract.ts (100%)
rename {src => packages/core/src}/contracts/modifiers/apply.ts (100%)
rename {src => packages/core/src}/contracts/modifiers/index.ts (100%)
rename {src => packages/core/src}/contracts/states/hollowdb-htx.state.json (100%)
rename {src => packages/core/src}/contracts/states/hollowdb-set.state.json (100%)
rename {src => packages/core/src}/contracts/states/hollowdb.groth16.state.json (100%)
rename {src => packages/core/src}/contracts/states/hollowdb.state.json (100%)
rename {src => packages/core/src}/contracts/states/index.ts (100%)
rename {src => packages/core/src}/contracts/types/contract.ts (100%)
rename {src => packages/core/src}/contracts/types/index.ts (100%)
rename {src => packages/core/src}/contracts/types/inputs.ts (100%)
rename {src => packages/core/src}/contracts/utils/index.ts (100%)
rename {src => packages/core/src}/globals.d.ts (100%)
rename {src => packages/core/src}/hollowdb-set.ts (100%)
rename {src => packages/core/src}/hollowdb.ts (100%)
rename {src => packages/core/src}/index.ts (100%)
rename {tests => packages/core/tests}/README.md (100%)
rename {tests => packages/core/tests}/batch.test.ts (100%)
rename {tests => packages/core/tests}/constants/index.ts (100%)
rename {tests => packages/core/tests}/crud.test.ts (100%)
rename {tests => packages/core/tests}/environment/setup.ts (100%)
rename {tests => packages/core/tests}/environment/teardown.ts (100%)
rename {tests => packages/core/tests}/evolve.test.ts (100%)
rename {tests => packages/core/tests}/hooks/index.ts (100%)
rename {tests => packages/core/tests}/htx.test.ts (100%)
rename {tests => packages/core/tests}/mock/bundlr.ts (100%)
rename {tests => packages/core/tests}/multi.test.ts (100%)
rename {tests => packages/core/tests}/null.test.ts (100%)
rename {tests => packages/core/tests}/proofs.test.ts (100%)
rename {tests => packages/core/tests}/set.test.ts (100%)
rename {tests => packages/core/tests}/utils/cache.ts (100%)
rename {tests => packages/core/tests}/utils/index.ts (100%)
rename {tests => packages/core/tests}/whitelists.test.ts (100%)
rename tsconfig.base.json => packages/core/tsconfig.base.json (100%)
rename tsconfig.json => packages/core/tsconfig.json (100%)
create mode 100644 packages/prover/.eslintignore
create mode 100644 packages/prover/.eslintrc.json
create mode 100644 packages/prover/.gitignore
create mode 100644 packages/prover/.parcelrc
create mode 100644 packages/prover/.prettierignore
create mode 100644 packages/prover/.prettierrc.cjs
create mode 100644 packages/prover/LICENSE
create mode 100644 packages/prover/README.md
create mode 100644 packages/prover/circuits/groth16/hollow-authz.wasm
create mode 100644 packages/prover/circuits/groth16/prover_key.zkey
create mode 100644 packages/prover/circuits/groth16/verification_key.json
create mode 100644 packages/prover/circuits/plonk/hollow-authz.wasm
create mode 100644 packages/prover/circuits/plonk/prover_key.zkey
create mode 100644 packages/prover/circuits/plonk/verification_key.json
create mode 100644 packages/prover/jest.config.cjs
create mode 100644 packages/prover/package.json
create mode 100644 packages/prover/src/index.ts
create mode 100644 packages/prover/tests/constants/index.ts
create mode 100644 packages/prover/tests/prover.test.ts
create mode 100644 packages/prover/tsconfig.json
create mode 100644 pnpm-workspace.yaml
diff --git a/.github/workflows/publish-client.yml b/.github/workflows/publish-client.yml
new file mode 100644
index 0000000..725ff38
--- /dev/null
+++ b/.github/workflows/publish-client.yml
@@ -0,0 +1,71 @@
+name: Publish Package to npmjs
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install Node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: 19.x
+
+ - name: Get yarn cache directory path
+ id: yarn-cache-dir-path
+ run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
+
+ - name: Restore yarn cache
+ uses: actions/cache@v3
+ id: yarn-cache
+ with:
+ path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
+ key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-yarn-
+
+ - name: Install dependencies
+ run: yarn
+
+ - name: Formatting
+ run: yarn format
+
+ - name: Linting
+ run: yarn lint
+
+ - name: Build
+ run: yarn build
+
+ - name: Run tests
+ run: yarn test
+
+ publish:
+ needs: test
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-node@v3
+ with:
+ node-version: '20'
+ registry-url: 'https://registry.npmjs.org'
+
+ - run: yarn
+ - run: yarn build
+ - uses: JS-DevTools/npm-publish@v3
+ id: publish
+ with:
+ token: ${{ secrets.NPM_TOKEN }}
+
+ - if: ${{ steps.publish.outputs.type }}
+ run: echo "$PCKG_ID published to $PCKG_TAG, old version was [$OLD_VER]"
+ env:
+ OLD_VER: ${{ steps.publish.outputs.old-version }}
+ PCKG_ID: ${{ steps.publish.outputs.id }}
+ PCKG_TAG: ${{ steps.publish.outputs.tag }}
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish-core.yml
similarity index 100%
rename from .github/workflows/publish.yml
rename to .github/workflows/publish-core.yml
diff --git a/.github/workflows/publish-prover.yml b/.github/workflows/publish-prover.yml
new file mode 100644
index 0000000..9a67b66
--- /dev/null
+++ b/.github/workflows/publish-prover.yml
@@ -0,0 +1,71 @@
+name: Publish Package to npmjs
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: '20'
+
+ - name: Get yarn cache directory path
+ id: yarn-cache-dir-path
+ run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
+
+ - name: Restore yarn cache
+ uses: actions/cache@v3
+ id: yarn-cache
+ with:
+ path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
+ key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-yarn-
+
+ - name: Install dependencies
+ run: yarn
+
+ - name: Formatting
+ run: yarn format
+
+ - name: Linting
+ run: yarn lint
+
+ - name: Build
+ run: yarn build
+
+ - name: Run tests
+ run: yarn test
+
+ publish:
+ needs: test
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-node@v3
+ with:
+ node-version: '20'
+ registry-url: 'https://registry.npmjs.org'
+
+ - run: yarn
+ - run: yarn build
+ - uses: JS-DevTools/npm-publish@v3
+ id: publish
+ with:
+ token: ${{ secrets.NPM_TOKEN }}
+
+ - if: ${{ steps.publish.outputs.type }}
+ run: echo "$PCKG_ID published to $PCKG_TAG, old version was [$OLD_VER]"
+ env:
+ OLD_VER: ${{ steps.publish.outputs.old-version }}
+ PCKG_ID: ${{ steps.publish.outputs.id }}
+ PCKG_TAG: ${{ steps.publish.outputs.tag }}
diff --git a/.github/workflows/test-client.yml b/.github/workflows/test-client.yml
new file mode 100644
index 0000000..445a36a
--- /dev/null
+++ b/.github/workflows/test-client.yml
@@ -0,0 +1,37 @@
+name: test
+
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install Node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: 19.x
+
+ - name: Get yarn cache directory path
+ id: yarn-cache-dir-path
+ run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
+
+ - name: Restore yarn cache
+ uses: actions/cache@v3
+ id: yarn-cache
+ with:
+ path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
+ key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-yarn-
+
+ - name: Install dependencies
+ run: yarn
+
+ - name: Run tests
+ run: yarn test
diff --git a/.github/workflows/test.yml b/.github/workflows/test-core.yml
similarity index 100%
rename from .github/workflows/test.yml
rename to .github/workflows/test-core.yml
diff --git a/.github/workflows/test-prover.yml b/.github/workflows/test-prover.yml
new file mode 100644
index 0000000..301fd37
--- /dev/null
+++ b/.github/workflows/test-prover.yml
@@ -0,0 +1,52 @@
+name: test
+
+on:
+ pull_request:
+ types:
+ - opened
+ branches:
+ - dev
+ push:
+ branches:
+ - master
+ - dev
+
+jobs:
+ style:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install Node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: 19.x
+
+ - name: Get yarn cache directory path
+ id: yarn-cache-dir-path
+ run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
+
+ - name: Restore yarn cache
+ uses: actions/cache@v3
+ id: yarn-cache
+ with:
+ path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
+ key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-yarn-
+
+ - name: Install dependencies
+ run: yarn
+
+ - name: Formatting
+ run: yarn format
+
+ - name: Linting
+ run: yarn lint
+
+ - name: Build
+ run: yarn build
+
+ - name: Run tests
+ run: yarn test
diff --git a/.prettierignore b/.prettierignore
deleted file mode 100644
index 86d49b1..0000000
--- a/.prettierignore
+++ /dev/null
@@ -1,13 +0,0 @@
-# js output
-build
-dist
-lib
-
-# modules
-node_modules
-
-# ignore configs
-config
-
-# declarations
-*.d.ts
diff --git a/.prettierrc.cjs b/.prettierrc.cjs
deleted file mode 100644
index 6e6775a..0000000
--- a/.prettierrc.cjs
+++ /dev/null
@@ -1,7 +0,0 @@
-module.exports = {
- bracketSpacing: false,
- singleQuote: true,
- trailingComma: 'es5',
- arrowParens: 'avoid',
- printWidth: 120,
-};
diff --git a/README.md b/README.md
index 09ca34f..a535c0e 100644
--- a/README.md
+++ b/README.md
@@ -54,13 +54,6 @@ You can read the full documentation of HollowDB at .
>
> If you are interested in customizing the smart contract of HollowDB and extending its SDKs, refer to this [README](./src/contracts/README.md).
-## Examples
-
-Check out the [examples](./examples/) folder for a few examples of HollowDB usage:
-
-- **Simple**: a single JS file that demonstrates getting & setting a key.
-- **Micro**: a Vercel Micro backend that can serves HollowDB as API endpoints, useful when you want to use HollowDB from another language.
-
## Testing
You can run all tests via:
diff --git a/docs/QUICK-START.md b/docs/QUICK-START.md
new file mode 100644
index 0000000..0f2405b
--- /dev/null
+++ b/docs/QUICK-START.md
@@ -0,0 +1,178 @@
+# Quick Start
+
+To get started with HollowDB, simply install the package:
+
+```sh
+yarn add hollowdb
+pnpm add hollowdb
+npm install hollowdb
+```
+
+Let's quickly demonstrate with a NodeJS project to see HollowDB in action.
+
+## Setup
+
+Open up your favorite terminal and/or IDE and create a new directory for our fresh project:
+
+```sh
+mkdir hollowdb-example
+cd hollowdb-example
+```
+
+Install HollowDB & Warp Contracts packages:
+
+```sh
+yarn add hollowdb warp-contracts
+```
+
+Finally, create yourself an Arweave wallet at or use an existing one if you have. Download your wallet (which will be a JSON file) and put it as `wallet.json` in the current directory.
+
+Create a new file called `index.js`. Prepare the code as follows:
+
+```js
+const { SDK } = require("hollowdb");
+const { WarpFactory } = require("warp-contracts");
+const fs = require("fs");
+
+async function main() {
+ // we will write our code here...
+}
+
+main();
+```
+
+## Instantiating HollowDB
+
+To instantiate HollowDB SDK, we need to provide a signer object (that will be our wallet), a Warp instance and a contract transaction id to specify which smart contract we are connecting to. Let's do just that!
+
+Write the following code within your main function:
+
+```js
+// read your wallet
+const wallet = JSON.parse(fs.readFileSync("./wallet.json").toString());
+
+// use an existing contract
+// to deploy your own, read "Contract Operations" section :)
+const contractTxId = "_eVfQYDOLpd-0QX0yt7RV2CXE5D9U0otCz9BNBiJMYY";
+
+// we will use Mainnet
+const warp = WarpFactory.forMainnet();
+
+// create the SDK!
+const sdk = new SDK(wallet, contractTxId, warp);
+```
+
+### Generating proofs & computing the key
+
+The contract we are connecting to in this example uses zero-knowledge proofs to update keys. So, we can very much get a value at some key, or put a new key-value pair; but when it comes to updating them we will need to generate proofs!
+
+We can use **HollowDB Prover** for this:
+
+```js
+const { Prover, computeKey } = require("hollowdb-prover");
+```
+
+## Getting & Setting keys
+
+It's time to put everything to use! Here is what we will do:
+
+- define a secret that is related to our key
+- put some value to that key
+- update that value using a zero-knowledge proof
+
+First, we will define our secret, which can be anything that you'd like. We can say `secret=2023` for this example; although, if you really care about the secret staying that way, you should make a longer secret.
+
+Let's go back to writing code within our `main` function:
+
+```js
+const secret = BigInt("0xDEADBEEF"); // or something that only YOU know :)
+const key = computeKey(secret);
+console.log("Key:", key);
+```
+
+We have created our key just like that. Everyone expect you will see that huge number, but only you know that the key was created with `0xDEADBEEF`. Let's put the string "hello world" to this key in our key-value database:
+
+```js
+await sdk.put(key, "hello world"); // put a value
+console.log(await sdk.get(key)); // get that value
+```
+
+As simple as that. Now, let's update our value at this key to `seeya world`. To do that, we will need to create a zero-knowledge proof that we know the secret which belongs to this key.
+
+We can use the Prover utility for that. We need the WASM file for the circuit, and the prover key. You can find these at the links below:
+
+- [WASM Circuit](https://github.com/firstbatchxyz/hollowdb/blob/master/config/circuits/hollow-authz-groth16/hollow-authz.wasm)
+- [Prover Key](https://github.com/firstbatchxyz/hollowdb/blob/master/config/circuits/hollow-authz-groth16/prover_key.zkey)
+
+Download these files and add them to the project directory. Then, create the prover as follows:
+
+```js
+const prover = new Prover(
+ "./hollow-authz.wasm",
+ "./prover_key.zkey",
+ "groth16"
+);
+```
+
+To generate the proof, we provide our secret, along with the current value & next value:
+
+```js
+const { proof } = await prover.prove(secret, "hello world", "seeya world");
+```
+
+We can now update our value using the SDK:
+
+```js
+await sdk.update(key, "seeya world", proof); // update value
+console.log(await sdk.get(key)); // get the new value
+```
+
+## Final Code
+
+That is it! Our final `index.js` looks like the following:
+
+```js
+const { SDK } = require("hollowdb");
+const { WarpFactory } = require("warp-contracts");
+const { Prover, computeKey } = require("hollowdb-prover");
+const fs = require("fs");
+
+async function main() {
+ // read your wallet
+ const wallet = JSON.parse(fs.readFileSync("./wallet.json").toString());
+
+ // use an existing contract
+ // to deploy your own, read "Contract Operations" section :)
+ const contractTxId = "_eVfQYDOLpd-0QX0yt7RV2CXE5D9U0otCz9BNBiJMYY";
+
+ // we will use Mainnet
+ const warp = WarpFactory.forMainnet();
+
+ // create the SDK!
+ const sdk = new SDK(wallet, contractTxId, warp);
+
+ const secret = BigInt(2023);
+ const key = computeKey(secret);
+ console.log("Key:", key);
+
+ console.log("Putting a value...");
+ await sdk.put(key, "hello world"); // put a value
+ console.log(await sdk.get(key)); // get that value
+
+ console.log("Generating a proof for update...");
+ const prover = new Prover(
+ "./hollow-authz.wasm",
+ "./prover_key.zkey",
+ "groth16"
+ );
+
+ console.log("Generating a proof:");
+ const { proof } = await prover.prove(secret, "hello world", "seeya world");
+
+ console.log("Updating the value...");
+ await sdk.update(key, "bye bye world", proof); // update value
+ console.log(await sdk.get(key)); // get the new value
+}
+
+main();
+```
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..267c621
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,194 @@
+# HollowDB
+
+> HollowDB is a decentralized privacy-preserving key-value database on Arweave network, powered by Warp Contracts.
+
+A key-value database is a non-relational database, where data is simply stored as a collection of pairs: a key and a corresponding value. Arweave is a decentralized storage network, essentially providing a decentralized permanent-storage for all kinds of data. You can write smart-contracts in Arweave using SmartWeave. In particular, Warp Contracts provide much more functionality atop SmartWeave for developers to write more advanced smart-contracts.
+
+HollowDB essentially operates on a smart-contract that provides an interface for a key-value database that lives on Arweave, essentially making it a decentralized key-value database. Here is the catch though, if the smart-contract is handling all the operations, how are we going to achieve authenticated operations? That is, we only want owners of data to be able to update them, or remove them.
+
+This is where HollowDB utilizes zero-knowledge proofs. A zero-knowledge proof is a proof where the prover reveals no extra information other than the fact that some statement is true. Thanks to recent developments within the last decade or so, generating proofs and verifying them have become much more efficient. In particular, proof size is very small and verification happens very quickly. Thanks to this, we can verify proofs at contract level!
+
+In HollowDB, users prove that they own a key for some key-value pair, and they do this using a preimage-knowledge proof where the key is the digest from some hash function. In doing so, they do not reveal who they are as the digest is really just a random looking string and the proof reveals nothing about the preimage. This functionality makes HollowDB privacy-preserving.
+
+Each smart-contract in HollowDB can be thought of as a separate key-value database. Users may deploy their own contracts using our repository, and then interact with them using the HollowDB SDK.
+
+In short, HollowDB is made of the following components:
+
+- A SmartWeave smart contract that is the interface to the key-value storage on Arweave.
+- A SnarkJS plugin for Warp Contracts, enabling zero-knowledge proof verification at contract level.
+- An NPM package that provides a very simple interface to interact with a HollowDB contract, as well as administrative operations such as changing owner & updating state.
+
+## Modes of Operation
+
+HollowDB has two modus operandi: proofs and whitelisting; both can be enabled together, or separately.
+
+### Proofs Mode
+
+Using zero-knowledge proofs gives full control to the users on updating the value at their key. When proofs are enabled:
+
+- Anyone can read and put.
+- To update or remove a value at some key, user must provide a zero-knowledge proof (ZKP) related to that key.
+
+You can enable or disable proof checking with the following Admin command:
+
+```ts
+const isProofRequired = true; // or false
+await admin.updateProofRequirement("auth", isProofRequired);
+```
+
+If you are deploying your own contract, you must also provide the verification key that the proof verifier will use. You can find HollowDB's keys [here](https://github.com/firstbatchxyz/hollowdb/tree/master/config/circuits). You can either provide the key in the initial state, or use the Admin to update the verification key at a later time:
+
+```ts
+await admin.updateVerificationKey("auth", newVerificationKey);
+```
+
+### Whitelisting Mode
+
+Whitelisting is an alternative authorization mechanism that can be used within HollowDB. It can provide a fine-grained control over who can put, update, or remove a value to the database. When whitelisting is enabled:
+
+- Anyone can read.
+- To put, the user must have been put-whitelisted by the contract owner.
+- To update or remove, the user must have been update-whitelisted by the contract owner.
+
+There is a separate whitelist for put and update/remove. You can enable/disable them with the Admin:
+
+```ts
+const isPutWhitelistRequired = true; // or false
+const isUpdateWhitelistRequired = true; // or false
+await admin.updateWhitelistRequirement("put", isPutWhitelistRequired);
+await admin.updateWhitelistRequirement("update", isUpdateWhitelistRequired);
+```
+
+You can add & remove users from the whitelist using the Admin:
+
+```ts
+const whitelistType = "put"; // or "update"
+
+// add some users
+const addressesToAdd = [address1, address2 /*, ... */];
+await admin.updateWhitelist(addressesToAdd, whitelistType, "add");
+
+// remove some users
+const addressesToRemove = [address3, address4 /*, ... */];
+await admin.updateWhitelist(addressesToRemove, whitelistType, "remove");
+```
+
+## Large Values
+
+Currently Warp Contracts only support transactions that are below 2KB in size; however you can put arbitrarily large values with the following trick:
+
+1. upload the large value using a bundler, such as Irys
+2. retrieve the transaction id for that value
+3. put the transaction id in the key-value storage
+
+To retrieve the value, simply get the transaction id from the storage and read the value from Arweave with:
+
+```ts
+const response = await fetch(`https://arweave.net/${transactionId}`);
+```
+
+In other words, you will store `key, valueTxId` instead of `key, value`! This will enable you to store arbitrarily large amounts of data, and retrieve them with respect to their transaction ids, also while reducing the overall size of the contract cache. Albeit, it comes with the cost of paying fees for the bundler.
+
+## Supported Wallets
+
+HollowDB supports both Arweave wallets and Ethereum wallets. In this page, we will show several ways to create the `signer` object.
+
+### Arweave
+
+An Arweave wallet is defined by a JWKInterface object, which is simply a JSON file that you can download from arweave.app. You can read the JSON object from disk, and pass it as the signer to HollowDB.
+
+```ts
+import { JWKInterface } from "warp-contracts";
+
+const jwkPath = "./some-wallet.json";
+const signer = JSON.parse(
+ fs.readFileSync(walletPath).toString()
+) as JWKInterface;
+```
+
+Always `.gitignore` your wallets!
+
+### Injected Arweave Wallet
+
+If you are using HollowDB in browser, you can use arweave-wallet-connector to connect to your existing wallet on arweave.app and use it as an injected wallet in HollowDB! This is done by providing the string "use_wallet" as the signer argument, which internally does the same to connect to Warp Contracts.
+
+Using an injected Arweave wallet will trigger a pop-up on each interaction, similar to MetaMask pop-ups where you sign the transaction.
+
+```ts
+// instantiate Arweave Web Wallet
+const arweaveWebWallet = new ArweaveWebWallet(
+ {
+ name: "your-app-name",
+ logo: "your-app-logo",
+ },
+ {
+ state: {
+ url: "arweave.app",
+ },
+ }
+);
+
+// connect
+await arweaveWebWallet.connect();
+
+// the magic string
+const signer = "use_wallet";
+```
+
+### Ethereum Wallets
+
+Similar to injected Arweave wallet, you can use an injected EVM-compatible wallet (such as MetaMask) to connect to HollowDB. This is made possible with the Warp Contract Signature plugin. Please refer to their documentation for more details.
+
+Once you are able to obtain the evmSignature as described in their documentation, you can use the following signer for HollowDB:
+
+```ts
+import { CustomSignature } from "warp-contracts";
+
+// assuming `evmSignature` exists at this point
+const signer: CustomSignature = {
+ signer: evmSignature,
+ signatureType: "ethereum",
+};
+```
+
+## Caching Options
+
+Warp uses LevelDB on Node and IndexedDB on browser for caching by default. It allows overriding the cache to be used via `useStateCache` and `useContractCache`; as well as overriding the underlying key-value storage via `useKVStorageFactory`. These overrides accept any cache interface that supports the [SortKeyCache interface](https://github.com/warp-contracts/warp/blob/main/src/cache/SortKeyCache.ts) of Warp Contracts, see the relevant documentation [here](https://academy.warp.cc/docs/sdk/advanced/kv-storage#implementation-details).
+
+Since HollowDB takes input a warp instance, by applying cache overrides to the warp instance outside and passing that instance to HollowDB, you will be able to use the caching of your choice for HollowDB!
+
+> `useStateCache` and `useContractCache` does not work in Warp's `forLocal` instance, e.g. when used with `arlocal`. You can use KV Storage factory by itself during local tests.
+
+There are alternative caching plugins as well:
+
+- [LMDB](https://www.npmjs.com/package/warp-contracts-lmdb)
+- [Redis](https://www.npmjs.com/package/warp-contracts-redis)
+- [Postgres](https://www.npmjs.com/package/warp-contracts-postgres)
+
+## Use Cases
+
+HollowDB can be used in many ways, with or without a backend and with different types of wallets. Although the interface of HollowDB is very simple, there are several things to consider when you are building an application with HollowDB:
+
+- Side-effects of lazy evaluation that is used in SmartWeave.
+- Value size & usage of bundling.
+- Modes of operation, i.e. **proofs** & **whitelisting**.
+
+### Lazy Evaluation
+
+> SmartWeave uses lazy-evaluation to move the burden of contract execution from network nodes to smart contract users. Currently, SmartWeave supports JavaScript, using the client's unmodified execution engine.
+
+What this means is that, when a new client wants to make a transaction on a contract, they must download all preceding transactions and evaluate them locally. By doing that, they effectively reach the present state of the contract, upon which they execute their transaction.
+
+The immediate side-effect of this is that if a contract has many interactions, it will take a longer time to lazy-evaluate it. Since HollowDB is a key-value database operated by a SmartWeave smart contract, the more keys & interaction a contract has, the longer it will take for clients to lazy-evaluate it.
+
+Depending on your key-count and the data-size, you might prefer different architectures for your application. Let's take a look at these scenarios:
+
+| | Small values / key (<2KB) | Large values / key (>= 2KB) |
+| ---------------- | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
+| Few keys / user | Client-side only is probably enough, unless this is a very active app with many users. | You might use client-side only, but it would require the users to have a funded Bundlr account. |
+| Many keys / user | Client-side only may be enough, but consider a backend if there are many users. Or, use several contracts to group users. | You should use a backend with your own wallet to make the requests & upload to Bundlr when needed. |
+
+Some examples out there:
+
+- [An anonymous authentication service](https://github.com/merdoyovski/hollowdb-next-auth)
+- [An on-chain privacy-preserving calendar](https://github.com/firstbatchxyz/hollowdb-nextjs-calendar)
diff --git a/packages/client/.eslintignore b/packages/client/.eslintignore
new file mode 100644
index 0000000..c7b2a50
--- /dev/null
+++ b/packages/client/.eslintignore
@@ -0,0 +1,5 @@
+build
+lib
+node_modules
+circuits
+coverage
diff --git a/packages/client/.eslintrc.json b/packages/client/.eslintrc.json
new file mode 100644
index 0000000..f95bb33
--- /dev/null
+++ b/packages/client/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "./node_modules/gts/"
+}
diff --git a/packages/client/.gitignore b/packages/client/.gitignore
new file mode 100644
index 0000000..434c2bf
--- /dev/null
+++ b/packages/client/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+build
+lib
+coverage
+yarn-error.log
+.parcel-cache
diff --git a/.parcelrc b/packages/client/.parcelrc
similarity index 100%
rename from .parcelrc
rename to packages/client/.parcelrc
diff --git a/packages/client/.prettierignore b/packages/client/.prettierignore
new file mode 100644
index 0000000..c7b2a50
--- /dev/null
+++ b/packages/client/.prettierignore
@@ -0,0 +1,5 @@
+build
+lib
+node_modules
+circuits
+coverage
diff --git a/packages/client/.prettierrc.cjs b/packages/client/.prettierrc.cjs
new file mode 100644
index 0000000..c5166c2
--- /dev/null
+++ b/packages/client/.prettierrc.cjs
@@ -0,0 +1,3 @@
+module.exports = {
+ ...require('gts/.prettierrc.json'),
+};
diff --git a/packages/client/LICENSE b/packages/client/LICENSE
new file mode 100644
index 0000000..2927c6a
--- /dev/null
+++ b/packages/client/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 FirstBatch
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/client/README.md b/packages/client/README.md
new file mode 100644
index 0000000..c7cdd03
--- /dev/null
+++ b/packages/client/README.md
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+ HollowDB Client
+
+
+ HollowDB client is the simplest way to use HollowDB, a decentralized & privacy-preserving key-value database.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Installation
+
+HollowDB client is an NPM package. You can install it as:
+
+```sh
+yarn add hollowdb-client # yarn
+npm install hollowdb-client # npm
+pnpm add hollowdb-client # pnpm
+```
+
+## Usage
+
+Create a new API key and a database at . Create a new client by providing your API key and the database name to connect:
+
+```ts
+client = await HollowClient.new({
+ apiKey: 'your-api-key',
+ db: 'your-database-name',
+});
+```
+
+After that, using the client is as simple as it gets:
+
+```ts
+// without zero-knowledge proofs
+await client.get(KEY);
+await client.put(KEY, VALUE);
+await client.update(KEY, VALUE);
+await client.remove(KEY);
+```
+
+If you are connecting to a database that has zero-knowledge proof verifications enabled, you will need to provide proofs along with your update & remove requests.
+
+You can use our [HollowDB Prover](https://github.com/firstbatchxyz/hollowdb/packages/prover) utility to generate proofs with minimal development effort. Assuming that a proof is generated for the respective request, the proof shall be provided as an additional argument to these functions.
+
+```ts
+// with zero-knowledge proofs
+await client.get(KEY);
+await client.put(KEY, VALUE);
+await client.update(KEY, VALUE, PROOF);
+await client.remove(KEY, PROOF);
+```
+
+## Testing
+
+To run tests:
+
+```sh
+yarn test
+```
+
+The API calls are mocked during the test, so you can run it offline.
diff --git a/packages/client/jest.config.cjs b/packages/client/jest.config.cjs
new file mode 100644
index 0000000..348760f
--- /dev/null
+++ b/packages/client/jest.config.cjs
@@ -0,0 +1,8 @@
+/** @type {import('ts-jest').JestConfigWithTsJest} */
+module.exports = {
+ preset: 'ts-jest',
+ testEnvironment: 'node',
+ testTimeout: 60000,
+ verbose: true,
+ forceExit: true,
+};
diff --git a/packages/client/package.json b/packages/client/package.json
new file mode 100644
index 0000000..af059f9
--- /dev/null
+++ b/packages/client/package.json
@@ -0,0 +1,87 @@
+{
+ "name": "hollowdb-client",
+ "description": "HollowDB Client",
+ "version": "0.2.5",
+ "author": "FirstBatch Team ",
+ "contributors": [
+ "Faruk Can Özkan ",
+ "Erhan Tezcan "
+ ],
+ "homepage": "https://github.com/firstbatchxyz/hollowdb-client#readme",
+ "license": "MIT",
+ "scripts": {
+ "prebuild": "yarn clean:lib && yarn lint",
+ "build": "parcel build",
+ "test": "jest",
+ "clean": "gts clean",
+ "clean:lib": "rimraf ./lib",
+ "lint": "gts lint",
+ "format": "prettier --write './src/**/*.ts'",
+ "prepublishOnly": "yarn lint",
+ "preversion": "yarn lint && yarn build",
+ "version": "yarn format && git add -A src",
+ "postversion": "git push && git push --tags",
+ "yalc:publish": "yarn build && yalc publish --push"
+ },
+ "files": [
+ "lib/",
+ "LICENSE",
+ "README.md"
+ ],
+ "type": "module",
+ "source": "src/index.ts",
+ "types": "lib/index.d.ts",
+ "cjs": "lib/index.cjs",
+ "mjs": "lib/index.mjs",
+ "exports": {
+ "types": "./lib/index.d.ts",
+ "import": "./lib/index.mjs",
+ "require": "./lib/index.cjs",
+ "default": "./lib/index.mjs"
+ },
+ "targets": {
+ "cjs": {
+ "outputFormat": "commonjs",
+ "isLibrary": true,
+ "context": "node"
+ },
+ "mjs": {
+ "outputFormat": "esmodule",
+ "isLibrary": true,
+ "context": "node"
+ }
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "devDependencies": {
+ "snarkjs": "^0.7.0",
+ "@parcel/packager-ts": "2.9.3",
+ "@parcel/transformer-typescript-tsc": "^2.9.3",
+ "@parcel/transformer-typescript-types": "2.9.3",
+ "@types/jest": "^29.5.3",
+ "@types/node": "^14.11.2",
+ "gts": "^5.2.0",
+ "hollowdb-prover": "^0.1.4",
+ "jest": "^29.6.1",
+ "parcel": "^2.9.3",
+ "rimraf": "^5.0.1",
+ "ts-jest": "^29.1.1",
+ "typescript": "~4.7.0"
+ },
+ "keywords": [
+ "hollowdb",
+ "key-value store",
+ "storage",
+ "database",
+ "zksnark",
+ "blockchain",
+ "smart-contracts",
+ "arweave",
+ "anonymous",
+ "smartweave",
+ "circom",
+ "warp-contracts",
+ "zero-knowledge"
+ ]
+}
diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts
new file mode 100644
index 0000000..9e286bd
--- /dev/null
+++ b/packages/client/src/client.ts
@@ -0,0 +1,295 @@
+import {getToken} from './utilities';
+
+/**
+ * To authenticate yourself, you need to provide the following:
+ *
+ * - `apiKey` belonging to your team.
+ * - `db` database name to connect to.
+ *
+ * To create a new API key or a database, go to
+ */
+export type HollowClientOptions = {
+ apiKey: string;
+ db: string;
+ region?: string;
+ provider?: string;
+};
+
+type BlockchainOption = {
+ /**
+ * Specify whether to store the value on blockchain (Arweave).
+ *
+ * If `undefined`, it defaults to storing the value on blockchain.
+ */
+ blockchain?: 'arweave' | 'none';
+};
+
+type ExpireOption = {
+ /** Specify an expiration time (seconds) for your data. */
+ expire?: number;
+};
+
+/**
+ * **[HollowDB Client](https://docs.hollowdb.xyz/hollowdb/hollowdb-as-a-service#hollowdb-client)**
+ *
+ * HollowDB client provides a very simple interface, abstracting everything behind
+ * the scenes. Create a new client with `new` static function:
+ *
+ * ```ts
+ * const client = await HollowClient.new({
+ * apiKey: 'your-api-key',
+ * db: 'your-database-name',
+ * });
+ * ```
+ *
+ *
+ * You can do the 4 basic CRUD operations as shown below:
+ *
+ * ```ts
+ * await client.get(KEY);
+ * await client.put(KEY, VALUE);
+ * await client.update(KEY, VALUE);
+ * await client.remove(KEY);
+ * ```
+ *
+ * You can also do a multi-get:
+ *
+ * ```ts
+ * await client.getMulti([KEY1, KEY2, ...]);
+ * ```
+ */
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export class HollowClient {
+ protected readonly apiKey: string;
+ protected readonly db: string;
+ protected authToken: string;
+ protected readonly apiVersion = 'v0';
+
+ protected BASE_URL: string;
+
+ // auth token is retrieved by the client code, so it is not provided within `opt`
+ private constructor(opt: HollowClientOptions, authToken: string) {
+ this.apiKey = opt.apiKey;
+ this.authToken = authToken;
+ this.db = opt.db;
+
+ if (!opt.region) opt.region = 'eu-central-1';
+ if (!opt.provider) opt.provider = 'aws';
+
+ this.BASE_URL = `https://${opt.provider}-${opt.region}.hollowdb.xyz/db/${this.apiVersion}`;
+ /** Base API url. */
+ }
+
+ /**
+ * Creates & authenticates a HollowDB client.
+ *
+ * ```ts
+ * type ValueType = {foo: number, bar: string};
+ *
+ * client = await HollowClient.new({
+ * apiKey: 'your-api-key',
+ * db: 'your-database-name',
+ * });
+ * ```
+ *
+ * You can provide a value type to the function to make all functions treat that type
+ * as the value type for your key-value database.
+ */
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ static async new(
+ options: HollowClientOptions
+ ): Promise> {
+ const authToken = await getToken(options.db, options.apiKey);
+ return new HollowClient(options, authToken);
+ }
+
+ /**
+ * **Returns the value at the given key.**
+ *
+ * This operation does not require proofs.
+ *
+ * If the key does not exist, `null` will be returned.
+ */
+ public async get(key: string): Promise {
+ const encodedKey = encodeURIComponent(key);
+ const response = await this.fetch<{result: T | null}>(
+ `${this.BASE_URL}/get/${encodedKey}`,
+ 'GET'
+ );
+
+ if (!response.data) {
+ throw new Error('Expected data in response.');
+ }
+
+ return response.data.result;
+ }
+
+ /**
+ * **Returns the value at the given keys.**
+ *
+ * This operation does not require proofs.
+ *
+ * The returned array is an array of values where `value[i]` is
+ * belongs to `key[i]`. If a key does not exist, `null` is returned
+ * for that key.
+ */
+ public async getMulti(keys: string[]): Promise<(T | null)[]> {
+ const response = await this.fetch<{result: (T | null)[]}>(
+ `${this.BASE_URL}/mget`,
+ 'POST',
+ JSON.stringify({keys})
+ );
+
+ return response.data.result;
+ }
+
+ /**
+ * **Puts a value at the given key.**
+ *
+ * The target key must be empty, i.e. this is not an upsert operation.
+ *
+ * This operation does not require proofs even if zero-knowledge is enabled.
+ */
+ public async put(
+ key: string,
+ value: T,
+ options?: ExpireOption
+ ): Promise;
+ public async put(
+ key: string,
+ value: T,
+ options?: BlockchainOption
+ ): Promise;
+ public async put(
+ key: string,
+ value: T,
+ options?: ExpireOption | BlockchainOption
+ ): Promise {
+ await this.fetch(
+ `${this.BASE_URL}/put`,
+ 'POST',
+ JSON.stringify({key, value, options})
+ );
+ }
+
+ /**
+ * **Puts an array of key-value pairs.**
+ *
+ * The target keys must be empty, i.e. this is not an upsert operation.
+ *
+ * This operation does not require proofs even if zero-knowledge is enabled.
+ *
+ * Returns an array of booleans that indicate whether each key-value pair has
+ * been succesfully put or not.
+ *
+ */
+ public async putMulti(
+ pairs: {key: string; value: T; options?: ExpireOption}[]
+ ): Promise;
+ public async putMulti(
+ pairs: {key: string; value: T; options?: BlockchainOption}[]
+ ): Promise;
+ public async putMulti(
+ pairs: {key: string; value: T; options?: BlockchainOption | ExpireOption}[]
+ ): Promise {
+ const response = await this.fetch<{result: boolean[]}>(
+ `${this.BASE_URL}/mput`,
+ 'POST',
+ JSON.stringify({pairs})
+ );
+
+ return response.data.result;
+ }
+
+ /** **Updates a value at the given key.**
+ *
+ * A zero-knowledge proof is optionally provided, which the service
+ * expects if the connected database has proofs enabled.
+ */
+ public async update(
+ key: string,
+ value: T,
+ proof?: object,
+ options?: BlockchainOption
+ ): Promise;
+ public async update(
+ key: string,
+ value: T,
+ proof?: object,
+ options?: ExpireOption
+ ): Promise;
+ public async update(
+ key: string,
+ value: T,
+ proof?: object,
+ options?: BlockchainOption | ExpireOption
+ ) {
+ await this.fetch(
+ `${this.BASE_URL}/update`,
+ 'POST',
+ JSON.stringify({
+ key,
+ value,
+ proof,
+ options,
+ })
+ );
+ }
+
+ /** **Removes a value at the given key.**
+ *
+ * Subsequent `get` calls to this key shall return `null`.
+ *
+ * A zero-knowledge proof is optionally provided, which the service
+ * expects if the connected database has proofs enabled.
+ */
+ public async remove(key: string, proof?: object, options?: BlockchainOption) {
+ await this.fetch(
+ `${this.BASE_URL}/remove`,
+ 'POST',
+ JSON.stringify({
+ key,
+ proof,
+ options,
+ })
+ );
+ }
+
+ /** Fetch utility for internal use. */
+ private async fetch(
+ url: string,
+ method: 'GET' | 'POST',
+ body?: BodyInit
+ ): Promise<{
+ message: string;
+ data: Data;
+ newBearer?: string;
+ }> {
+ const init: RequestInit = {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-api-key': this.apiKey,
+ authorization: `Bearer ${this.authToken}`,
+ },
+ method,
+ };
+ if (body) init.body = body;
+
+ const response = await fetch(url, init);
+ const json = await response.json();
+
+ if (response.status === 200) {
+ // new bearer has been created due to expiration of previous bearer
+ if (json.newBearer !== undefined) {
+ this.authToken = json.newBearer;
+ }
+ return json;
+ } else if (json.message === 'token expired') {
+ // token has expired, client must initiate the new token request
+ this.authToken = await getToken(this.db, this.apiKey);
+ return json;
+ }
+
+ throw new Error(`${url} got status ${response.status}: ${json.message}`);
+ }
+}
diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts
new file mode 100644
index 0000000..512f762
--- /dev/null
+++ b/packages/client/src/index.ts
@@ -0,0 +1,2 @@
+export {HollowClient} from './client';
+export type {HollowClientOptions} from './client';
diff --git a/packages/client/src/utilities/index.ts b/packages/client/src/utilities/index.ts
new file mode 100644
index 0000000..5f877a2
--- /dev/null
+++ b/packages/client/src/utilities/index.ts
@@ -0,0 +1,32 @@
+/**
+ * Given a database name `db` and an api-key `apiKey`, fetches the
+ * bearer token to be used in header as `authorization: "Bearer ${token}"`.
+ *
+ * If `db` or `apiKey` is invalid, throws an error.
+ */
+export async function getToken(db: string, apiKey: string): Promise {
+ const url = `https://auth.firstbatch.xyz/hollow/create_bearer?db=${db}`;
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-api-key': apiKey,
+ },
+ });
+
+ const authResponse = (await response.json()) as {
+ message?: string;
+ bearerToken?: string;
+ };
+ if (!response.ok) {
+ throw new Error(
+ `${url} got status ${response.status}: ${authResponse.message}`
+ );
+ }
+
+ if (!authResponse.bearerToken) {
+ throw new Error('Server did not provide a bearer token.');
+ }
+
+ return authResponse.bearerToken;
+}
diff --git a/config/circuits/hollow-authz-groth16/hollow-authz.wasm b/packages/client/tests/circuits/hollow-authz-groth16/hollow-authz.wasm
similarity index 100%
rename from config/circuits/hollow-authz-groth16/hollow-authz.wasm
rename to packages/client/tests/circuits/hollow-authz-groth16/hollow-authz.wasm
diff --git a/config/circuits/hollow-authz-groth16/prover_key.zkey b/packages/client/tests/circuits/hollow-authz-groth16/prover_key.zkey
similarity index 100%
rename from config/circuits/hollow-authz-groth16/prover_key.zkey
rename to packages/client/tests/circuits/hollow-authz-groth16/prover_key.zkey
diff --git a/config/circuits/hollow-authz-groth16/verification_key.json b/packages/client/tests/circuits/hollow-authz-groth16/verification_key.json
similarity index 100%
rename from config/circuits/hollow-authz-groth16/verification_key.json
rename to packages/client/tests/circuits/hollow-authz-groth16/verification_key.json
diff --git a/config/circuits/hollow-authz-plonk/hollow-authz.wasm b/packages/client/tests/circuits/hollow-authz-plonk/hollow-authz.wasm
similarity index 100%
rename from config/circuits/hollow-authz-plonk/hollow-authz.wasm
rename to packages/client/tests/circuits/hollow-authz-plonk/hollow-authz.wasm
diff --git a/config/circuits/hollow-authz-plonk/prover_key.zkey b/packages/client/tests/circuits/hollow-authz-plonk/prover_key.zkey
similarity index 100%
rename from config/circuits/hollow-authz-plonk/prover_key.zkey
rename to packages/client/tests/circuits/hollow-authz-plonk/prover_key.zkey
diff --git a/config/circuits/hollow-authz-plonk/verification_key.json b/packages/client/tests/circuits/hollow-authz-plonk/verification_key.json
similarity index 100%
rename from config/circuits/hollow-authz-plonk/verification_key.json
rename to packages/client/tests/circuits/hollow-authz-plonk/verification_key.json
diff --git a/packages/client/tests/client.test.ts b/packages/client/tests/client.test.ts
new file mode 100644
index 0000000..26d7545
--- /dev/null
+++ b/packages/client/tests/client.test.ts
@@ -0,0 +1,78 @@
+import {randomBytes} from 'crypto';
+import {computeKey, Prover} from 'hollowdb-prover';
+import {HollowClient} from '../src';
+import {mockFetchDB} from './mocks';
+
+type ValueType = {
+ test: number;
+ foo: string;
+};
+
+// we can add plonk here too, but note that it has HUGE prover time
+([undefined, 'groth16', 'plonk'] as const).map(protocol =>
+ describe(`client ${
+ protocol ? `(protocol: ${protocol})` : '(not zk)'
+ }`, () => {
+ let client: HollowClient;
+
+ const prover = protocol
+ ? new Prover(
+ `./tests/circuits/hollow-authz-${protocol}/hollow-authz.wasm`,
+ `./tests/circuits/hollow-authz-${protocol}/prover_key.zkey`,
+ protocol
+ )
+ : undefined;
+ const PREIMAGE = BigInt('0x' + randomBytes(16).toString('hex'));
+ const KEY = protocol ? computeKey(PREIMAGE) : 'my lovely testing key';
+ const values: ValueType[] = Array.from({length: 2}, () => ({
+ test: Math.random() * 99,
+ foo: randomBytes(4).toString('hex'),
+ }));
+
+ beforeAll(async () => {
+ global.fetch = mockFetchDB;
+
+ client = await HollowClient.new({
+ apiKey: randomBytes(32).toString('hex'),
+ db: 'testing',
+ });
+ });
+
+ it('should put value', async () => {
+ expect(await client.get(KEY)).toEqual(null);
+ await client.put(KEY, values[0]);
+ expect(await client.get(KEY)).toEqual(values[0]);
+ });
+
+ it('should update values', async () => {
+ for (let i = 1; i < values.length; i++) {
+ const {proof} = protocol
+ ? await prover!.prove(PREIMAGE, values[i - 1], values[i])
+ : {proof: undefined};
+
+ expect(await client.get(KEY)).toEqual(values[i - 1]);
+ await client.update(KEY, values[i], proof);
+ expect(await client.get(KEY)).toEqual(values[i]);
+ }
+ });
+
+ it('should remove value', async () => {
+ const {proof} = protocol
+ ? await prover!.prove(PREIMAGE, values[values.length - 1], null)
+ : {proof: undefined};
+
+ expect(await client.get(KEY)).toEqual(values[values.length - 1]);
+ await client.remove(KEY, proof);
+ expect(await client.get(KEY)).toEqual(null);
+ });
+
+ afterAll(async () => {
+ jest.clearAllMocks();
+
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
+ // SnarkJS may attach curve_bn128 to global, but does not terminate it.
+ if (global.curve_bn128) await global.curve_bn128.terminate();
+ });
+ })
+);
diff --git a/packages/client/tests/mocks/index.ts b/packages/client/tests/mocks/index.ts
new file mode 100644
index 0000000..ac5a852
--- /dev/null
+++ b/packages/client/tests/mocks/index.ts
@@ -0,0 +1,191 @@
+import {randomBytes} from 'crypto';
+import {valueToBigInt, verifyProof} from '../utilities';
+
+const REGION = 'eu-central-1';
+const PROVIDER = 'aws';
+const API_VER = 'v0';
+const BASE_URL = `https://${PROVIDER}-${REGION}.hollowdb.xyz/db/${API_VER}`;
+
+const DB: Record = {};
+
+/** A mocked fetch call, acting like the DB backend. */
+export const mockFetchDB = jest.fn(
+ async (input: RequestInfo | URL, init?: RequestInit) => {
+ const url = input.toString();
+
+ // mock auth call
+ if (url.startsWith('https://auth.firstbatch.xyz/hollow/create_bearer')) {
+ return {
+ ok: true,
+ json: async () => ({bearerToken: randomBytes(32).toString('hex')}),
+ } as Response;
+ }
+
+ // mock write call, no refresh
+ else if (
+ url === `${BASE_URL}/put` ||
+ url === `${BASE_URL}/update` ||
+ url === `${BASE_URL}/remove`
+ ) {
+ if (init === undefined) {
+ throw new Error('expected init');
+ }
+
+ const op = url.slice(`${BASE_URL}/`.length);
+ const body = JSON.parse(init.body as string);
+
+ switch (op) {
+ case 'put':
+ DB[body.key] = body.value;
+ break;
+ case 'update':
+ if (body.proof) {
+ verifyProof(body.proof, [
+ valueToBigInt(DB[body.key]),
+ valueToBigInt(body.value),
+ BigInt(body.key),
+ ]);
+ }
+
+ DB[body.key] = body.value;
+ break;
+ case 'remove':
+ if (body.proof) {
+ verifyProof(body.proof, [
+ valueToBigInt(DB[body.key]),
+ BigInt(0),
+ BigInt(body.key),
+ ]);
+ }
+
+ delete DB[body.key];
+ break;
+ default:
+ throw new Error('unexpected op: ' + op);
+ }
+
+ return {
+ ok: true,
+ status: 200,
+ json: async () => ({message: ''}),
+ } as Response;
+ }
+
+ // mock read call, no refresh
+ else if (url.startsWith(`${BASE_URL}/get/`)) {
+ const key = url.slice(`${BASE_URL}/get/`.length);
+ const decodedKey = decodeURIComponent(key);
+ const val = DB[decodedKey];
+ return {
+ ok: true,
+ status: 200,
+ json: async () => ({message: '', data: {result: val ? val : null}}),
+ } as Response;
+ }
+
+ throw new Error('couldnt mock');
+ }
+);
+
+/** A mocked fetch call, just answers get and refreshes a token. */
+const NEW_AUTH_TOKEN = randomBytes(32).toString('hex');
+let refreshed = false;
+export const mockFetchGetRefresh = jest.fn(
+ async (input: RequestInfo | URL, init?: RequestInit) => {
+ const url = input.toString();
+
+ // mock auth call
+ if (url.startsWith('https://auth.firstbatch.xyz/hollow/create_bearer')) {
+ return {
+ ok: true,
+ json: async () => ({bearerToken: randomBytes(32).toString('hex')}),
+ } as Response;
+ }
+
+ // mock get call with refresh
+ else if (url.startsWith(`${BASE_URL}/get/`)) {
+ if (refreshed) {
+ if (
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (init!.headers! as any)['authorization'] !==
+ `Bearer ${NEW_AUTH_TOKEN}`
+ ) {
+ throw new Error('expected refreshed token');
+ }
+
+ return {
+ ok: true,
+ status: 200,
+ json: async () => ({
+ message: '',
+ data: {result: 'value'},
+ }),
+ } as Response;
+ } else {
+ refreshed = true;
+ return {
+ ok: true,
+ status: 200,
+ json: async () => ({
+ message: '',
+ data: {result: 'value'},
+ newBearer: NEW_AUTH_TOKEN,
+ }),
+ } as Response;
+ }
+ }
+
+ throw new Error('couldnt mock');
+ }
+);
+
+/** A mocked fetch call, just answers get and refreshes a token. */
+let oldtoken: string;
+let refreshed2 = false;
+export const mockFetchGetExpire = jest.fn(
+ async (input: RequestInfo | URL, init?: RequestInit) => {
+ const url = input.toString();
+ // mock auth call
+ if (url.startsWith('https://auth.firstbatch.xyz/hollow/create_bearer')) {
+ return {
+ ok: true,
+ json: async () => ({
+ bearerToken: randomBytes(32).toString('hex'),
+ }),
+ } as Response;
+ }
+
+ // mock get call with refresh
+ else if (url.startsWith(`${BASE_URL}/get/`)) {
+ if (refreshed2) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ if ((init!.headers! as any)['authorization'] === `Bearer ${oldtoken}`) {
+ throw new Error('expected refreshed token');
+ }
+
+ return {
+ ok: true,
+ status: 200,
+ json: async () => ({
+ message: '',
+ data: {result: 'value'},
+ }),
+ } as Response;
+ } else {
+ refreshed2 = true;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ oldtoken = (init!.headers! as any)['authorization'];
+ return {
+ ok: true,
+ status: 403, // should be != 200
+ json: async () => ({
+ message: 'token expired', // this will trigger a token refresh
+ data: {result: 'value'},
+ }),
+ } as Response;
+ }
+ }
+
+ throw new Error('couldnt mock');
+ }
+);
diff --git a/packages/client/tests/refresh.test.ts b/packages/client/tests/refresh.test.ts
new file mode 100644
index 0000000..e82074a
--- /dev/null
+++ b/packages/client/tests/refresh.test.ts
@@ -0,0 +1,53 @@
+import {randomBytes} from 'crypto';
+import {HollowClient} from '../src';
+import {mockFetchGetExpire, mockFetchGetRefresh} from './mocks';
+
+const KEY = 'my lovely key';
+
+describe('token refresh due to new bearer', () => {
+ let client: HollowClient;
+
+ beforeAll(async () => {
+ global.fetch = mockFetchGetRefresh; // mock fetch
+
+ client = await HollowClient.new({
+ apiKey: randomBytes(32).toString('hex'),
+ db: 'testing',
+ });
+ });
+
+ it('should refresh token due to new bearer in response', async () => {
+ await client.get(KEY);
+
+ // should not throw error if token is refreshed correctly
+ await client.get(KEY);
+ });
+
+ afterAll(async () => {
+ jest.clearAllMocks();
+ });
+});
+
+describe('token refresh due to expiration', () => {
+ let client: HollowClient;
+
+ beforeAll(async () => {
+ global.fetch = mockFetchGetExpire; // mock fetch
+
+ client = await HollowClient.new({
+ apiKey: randomBytes(32).toString('hex'),
+ db: 'testing',
+ });
+ });
+
+ it('should refresh token due to expiration', async () => {
+ await client.get(KEY);
+
+ // should not throw error if token is refreshed correctly
+ await client.get(KEY);
+ });
+
+ afterAll(async () => {
+ jest.clearAllMocks();
+ });
+});
diff --git a/packages/client/tests/utilities/index.ts b/packages/client/tests/utilities/index.ts
new file mode 100644
index 0000000..21af797
--- /dev/null
+++ b/packages/client/tests/utilities/index.ts
@@ -0,0 +1,40 @@
+import {createHash} from 'crypto';
+const snarkjs = require('snarkjs');
+
+import groth16Vkey from '../circuits/hollow-authz-groth16/verification_key.json';
+import plonkVkey from '../circuits/hollow-authz-plonk/verification_key.json';
+
+export async function verifyProof(
+ proof: object & {protocol: string},
+ psignals: bigint[]
+) {
+ const protocol = proof.protocol;
+ let verificationKey: object;
+ if (protocol === 'groth16') {
+ verificationKey = groth16Vkey;
+ } else if (protocol === 'plonk') {
+ verificationKey = plonkVkey;
+ } else {
+ throw new Error('Unknown protocol: ' + protocol);
+ }
+
+ const result = await snarkjs[protocol].verify(
+ verificationKey,
+ psignals,
+ proof
+ );
+ if (!result) {
+ throw new Error('Verification failed.');
+ }
+}
+
+export function valueToBigInt(value: unknown): bigint {
+ if (value) {
+ const digest = createHash('ripemd160')
+ .update(JSON.stringify(value), 'utf-8')
+ .digest('hex');
+ return BigInt('0x' + digest);
+ } else {
+ return 0n;
+ }
+}
diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json
new file mode 100644
index 0000000..ee41eac
--- /dev/null
+++ b/packages/client/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "./node_modules/gts/tsconfig-google.json",
+ "compilerOptions": {
+ "types": ["node", "jest"],
+ "rootDir": ".",
+ "outDir": "build",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "lib": ["ES2020", "DOM"],
+ "target": "ES2020"
+ },
+ "include": ["src/**/*.ts"],
+ "exclude": ["./node_modules", "./lib", "./build"]
+}
diff --git a/.eslintignore b/packages/core/.eslintignore
similarity index 100%
rename from .eslintignore
rename to packages/core/.eslintignore
diff --git a/.eslintrc.json b/packages/core/.eslintrc.json
similarity index 100%
rename from .eslintrc.json
rename to packages/core/.eslintrc.json
diff --git a/packages/core/.gitignore b/packages/core/.gitignore
new file mode 100644
index 0000000..6dee5e2
--- /dev/null
+++ b/packages/core/.gitignore
@@ -0,0 +1,27 @@
+# OS junk files
+.DS_Store
+
+# Node files
+node_modules/
+yarn-error.log
+
+# Warp Logger logs
+logs
+
+# Project files
+.parcel-cache/
+cache/
+lib/
+dist/
+
+# Redis stuff
+dump.rdb
+
+# Temporary stuff not-to-be pushed
+*.tmp.ts
+tmp.ts
+
+# Test stuff
+coverage
+
+.vscode
diff --git a/packages/core/.parcelrc b/packages/core/.parcelrc
new file mode 100644
index 0000000..d11e779
--- /dev/null
+++ b/packages/core/.parcelrc
@@ -0,0 +1,8 @@
+{
+ "extends": "@parcel/config-default",
+ "transformers": {
+ "*.ts": [
+ "@parcel/transformer-typescript-tsc"
+ ]
+ }
+}
diff --git a/packages/core/LICENSE b/packages/core/LICENSE
new file mode 100644
index 0000000..2927c6a
--- /dev/null
+++ b/packages/core/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 FirstBatch
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/core/README.md b/packages/core/README.md
new file mode 100644
index 0000000..09ca34f
--- /dev/null
+++ b/packages/core/README.md
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+ HollowDB
+
+
+ HollowDB is a decentralized privacy-preserving key-value database on Arweave network, powered by Warp Contracts.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Installation
+
+To install HollowDB:
+
+```bash
+yarn add hollowdb # yarn
+npm install hollowdb # npm
+pnpm add hollowdb # pnpm
+```
+
+Depending on your use-cases, we have several optional dependencies:
+
+- You can use [hollowdb-prover](https://www.npmjs.com/package/hollowdb-prover) as a simple utility that generates zero-knowledge proofs that are verifiable by HollowDB.
+- You can use LMDB cache within your Warp instance via [warp-contracts-lmdb](https://www.npmjs.com/package/warp-contracts-lmdb).
+- You can use Redis cache within your warp instance via [warp-contracts-redis](https://www.npmjs.com/package/warp-contracts-redis) together with [ioredis](https://www.npmjs.com/package/ioredis).
+- When you are evaluating a contract that uses ZK proofs, you should also use [warp-contracts-plugin-snarkjs](https://www.npmjs.com/package/warp-contracts-plugin-snarkjs) and [warp-contracts-plugin-ethers](https://www.npmjs.com/package/warp-contracts-plugin-ethers).
+
+## Usage
+
+You can read the full documentation of HollowDB at .
+
+> [!NOTE]
+>
+> If you are interested in customizing the smart contract of HollowDB and extending its SDKs, refer to this [README](./src/contracts/README.md).
+
+## Examples
+
+Check out the [examples](./examples/) folder for a few examples of HollowDB usage:
+
+- **Simple**: a single JS file that demonstrates getting & setting a key.
+- **Micro**: a Vercel Micro backend that can serves HollowDB as API endpoints, useful when you want to use HollowDB from another language.
+
+## Testing
+
+You can run all tests via:
+
+```sh
+pnpm test
+```
+
+Tests operate on a local Arweave instance using [arlocal](https://www.npmjs.com/package/arlocal). They will run for all cache types (LMDB, Redis, LevelDB). You will need to have a Redis server running for some of the tests to pass, the URL shall be specified [here](./tests/constants/index.ts).
+
+## Styling
+
+You can check the formatting of the code or lint everything with the following commands:
+
+```sh
+pnpm format # prettier
+pnpm lint # eslint
+```
diff --git a/config/circuits/README.md b/packages/core/config/circuits/README.md
similarity index 100%
rename from config/circuits/README.md
rename to packages/core/config/circuits/README.md
diff --git a/packages/core/config/circuits/hollow-authz-groth16/hollow-authz.wasm b/packages/core/config/circuits/hollow-authz-groth16/hollow-authz.wasm
new file mode 100644
index 0000000000000000000000000000000000000000..8955a52ec4f72d213ca623c7b134c1f9b935d501
GIT binary patch
literal 1627954
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 |