KALYPSOOpen app →
Docs/Getting started6 min read
On this page

00 · Getting started

This guide takes you from a fresh clone to a working vault on Arkiv's Rabdi testnet in about ten minutes. It covers running everything locally, a tour of the app, using the SDK from your own code, and fixes for the errors you are most likely to hit.

What Kalypso is, in one line: a TypeScript library that encrypts your data on your device, stores only ciphertext and commitments on Arkiv, lets you prove facts about it in zero knowledge, and erases it for good by destroying the keys.

What you need#

ThingWhyWhere to get it
Node.js 22+ and pnpm 9+builds the packages and the web appnodejs.org, npm i -g pnpm
An Arkiv access keyreads from the Rabdi RPC (10 quota units per query)Hub → access keys
A browser wallet on Rabdi, with test ETHsigns your writesHub → faucet
A relayer key with test ETH (optional)pays gas for one-time signers in "unlinkable" modeany funded Rabdi account you control
Docker (optional)a local Swarm cluster for encrypted attachmentsdocker.com

You do not need Noir or Barretenberg installed. The compiled circuits ship inside @arkivzk/zk. Run pnpm circuits only if you change a circuit.

1. Install and build#

git clone <this repo> arkivzk && cd arkivzk
pnpm install
pnpm build          # crypto, zk, core, vault, shred, sdk
pnpm test           # unit tests: crypto, zk proof round-trip, range cover, vault

2. Configure#

Create apps/web/.env.local. This file is gitignored. Never commit it.

ARKIV_ACCESS_KEY=<your Hub access key>        # server-side only, used by /api/rpc
RELAYER_PRIVATE_KEY=<funded Rabdi key>        # optional: enables one-time signers
GUARDIAN_URLS=http://127.0.0.1:7101,http://127.0.0.1:7102   # default, can be omitted
ARCHIVE_URL=http://127.0.0.1:7301             # default, can be omitted
BEE_URL=http://127.0.0.1:1633                 # optional: Swarm attachments
SWARM_POSTAGE_BATCH_ID=<batch id>             # optional: Swarm attachments

The browser never sees the access key or the relayer key. Reads go through the app's /api/rpc proxy, and writes are signed by your wallet.

3. Start the services#

Use one terminal per service.

# two guardians: each holds one of the three key shares
cd guardian && pnpm build && pnpm start:g1
cd guardian && pnpm start:g2

# archive node: records raw calldata so you can try to break a shred later
cd archive && pnpm build && set -a && . ../apps/web/.env.local && set +a && pnpm start

# the web app
cd apps/web && pnpm build && pnpm start      # http://localhost:3000

Optional: Swarm attachments. This needs Docker.

npx @fairdatasociety/fdp-play start --fresh --detach   # local Bee cluster on :1633
curl -s -X POST localhost:1633/stamps/100000000/20    # copy the batchID into .env.local

4. A five-minute tour of the app#

StepWhereWhat happens
1. Connect/appYour wallet connects. The app creates a device key in IndexedDB and opens your local vault.
2. StoreVault tabFill in the form and press Encrypt & store. The record is encrypted in the tab, its key is split 2-of-3, and one Arkiv transaction writes the ciphertext and the commitment.
3. SearchVault tab → search boxExact match, range (e.g. age 18–40) or Count. Arkiv filters HMAC tags. It never sees values.
4. ProveProve tabPick a claim such as age ≥ 18. A Noir proof is generated in the browser and published. You get a link anyone can open.
5. Verify/verify/<key>No wallet and no key. The page runs nine checks, including the proof, the commitment and whether the record was shredded.
6. ShredShred tabThe guardians and your device destroy their shares and sign. One transaction deletes the entities and writes a public receipt.
7. Break it/archiveThe archive node replays the calldata it captured and tries to decrypt it. It can't.

5. Use the SDK in your own code#

import { ArkivZK, Vault, MemoryKV, rawKeySealer, GuardianClient, verifyProofEntity } from "@arkivzk/sdk";

const zk = new ArkivZK({
  publicClient,                                     // createPublicClient({ chain: rabdi({ accessKey }), transport: http() })
  vault: await Vault.open(new MemoryKV(), rawKeySealer(deviceKey)),
  guardians: [new GuardianClient("http://127.0.0.1:7101"), new GuardianClient("http://127.0.0.1:7102")],
  write: { chain, rpc, publicClient, provider, account },
});

// store: encrypted on device, searchable by range, kept for 7 days
const { entityKey } = await zk.put({
  schemaId: "medical.v1",
  values: { age: 25, country: "IN" },
  rangeField: "age",
  retentionSeconds: 7 * 24 * 3600,
});

// prove: publish a zero-knowledge claim for one verifier
const { proofEntityKey } = await zk.prove(entityKey, { field: "age", min: 18 }, "pharmacy-42");

// verify: anyone, no keys
const result = await verifyProofEntity(publicClient, proofEntityKey);

// query: Arkiv evaluates tags, never values
const adults = await zk.searchRange("medical.v1", "age", 18, 40);
const total  = await zk.count("medical.v1", "age", { min: 18, max: 40 });   // or { eq: "IN" } on a searchable field

// shred: destroy keys for the record and its children, publish a receipt
await zk.shred([entityKey], () => {}, { cascade: true });

Every method, with options and return types, is in 06 · SDK reference. Relational features (multi-insert, parents, JOIN, cascade) are in §8f.

6. Check it against the live chain#

Each script writes real transactions on Rabdi and fails loudly if a result doesn't match. They need DEV_PRIVATE_KEY (a funded Rabdi key) and ARKIV_ACCESS_KEY in the repo-root .env.

ScriptChecks
node packages/sdk/e2e.mjs12 steps: put, get, prove, verify, search, renew, shred, receipt, archive
node packages/sdk/query-encrypted.mjsencrypted range, COUNT and JOIN against decrypt-everything
node packages/sdk/relational.mjsatomic insert, 1:N, M:N, N+1, orphans, cascade, recovery
node packages/sdk/query-live.mjsplanner: IN-batching, keyset paging, projection
node packages/sdk/query-receipts.mjsread-only: lists shred receipts and verifies signatures

7. Troubleshooting#

You seeCauseFix
"This browser's vault can't be unlocked with its device key"the browser's device key changed (cleared site data, another profile)Settings → restore from your recovery kit
MissingAccessKeyError or HTTP 429no or empty ARKIV_ACCESS_KEY on the serverset it in apps/web/.env.local and restart
"ephemeral signer mode needs a relayer"RELAYER_PRIVATE_KEY missing or unfundedset and fund it, or sign with your own wallet
Attachment upload returns 503Swarm not configuredset BEE_URL and SWARM_POSTAGE_BATCH_ID, or skip attachments
Guardian unreachablea guardian process isn't runningstart both guardians. Reads need any 2 of the 3 shares
First proof is slowthe WASM prover loads once per tablater proofs in the same tab take well under a second
A shred stopped after destroying keysthe Arkiv transaction failedopen Shred again. It retries only the on-chain part (publishShredReceipts)

Where to go next#