Documentation

Everything you need to use, run and trust the grid.

Start with the quickstart, go deeper into how data and money move, or jump straight to the API reference.

Introduction

ZNode Grid is private file storage. Files are encrypted in your browser, then stored as three replicas on independent storage nodes. A coordinator watches those nodes and repairs lost replicas automatically. Storage is paid for once, on-chain, and that payment funds the operators who run the nodes.

01Private

Files and their names are encrypted in your browser before upload.

02Durable

Three replicas, repaired within seconds when a node fails.

03Open

Anyone can run a storage node and earn for it.

04Verifiable

Payments and payouts follow rules enforced by a contract.

Quickstart

Storing your first file takes about two minutes.

  1. Get a wallet. Any browser wallet such as MetaMask, set to Robinhood Chain (chain ID 4663) with a little ETH for the payment.
  2. Open the vault. Go to /app and click Connect wallet. Sign the login message. No gas is spent.
  3. Buy storage. Click Buy storage and confirm the transaction. The price is read from the contract, so you always pay the exact amount.
  4. Upload. Drop files onto the upload area. Each one is encrypted before it leaves your device.
  5. Download anywhere. Sign in from any browser with the same wallet and your files decrypt locally.

Lost track of your purchase? If your browser forgot the payment transaction, just sign in again. The backend checks the contract's hasPurchased for your wallet and restores your plan. You're never charged twice.

Run a node

A node is a small Node.js server that stores encrypted replicas on disk. One install can run several nodes, each with its own home folder.

1. Create a join token

Open /nodes, connect the wallet that should own the node and receive rewards, enter a name and capacity, and click Create join token. Tokens are single-use and expire after 24 hours.

2. Start the node

$ cd usernode
$ npm install
$ node server.js --token znj_… --port 4001 --capacity 5 --home ./nodes/home-server

On first start the node:

  • creates its identity key in <home>/wallet.json;
  • signs a join request with your token;
  • passes a contact check, where the coordinator calls the node's /health and confirms it reports the same identity;
  • saves its assigned ID in <home>/node.json.

Later starts need no token: node server.js --port 4001 --home ./nodes/home-server.

FlagEnv varDefaultPurpose
--tokenJOIN_TOKEN–One-time join token (first start only)
--portPORT4001Port the node listens on
--homeNODE_HOMEusernode/Identity, config and data folder
--capacityCAPACITY_GB1Storage offered to the network
--urlPUBLIC_URLhttp://localhost:<port>Address the coordinator uses to reach you
--coordinatorCOORDINATORhttp://localhost:5000Coordinator address

3. Retiring a node

Use Graceful exit in the console. The node stops receiving new data, its replicas are copied to other nodes, and it shuts itself down once it's empty. If no spare node is available, the exit pauses and can be retried.

Architecture

ComponentResponsibilityCan see
BrowserDerives your key, encrypts and decrypts files and metadataEverything, for you only
Backend :4000Wallet sign-in, payment verification, encrypted file index, repair bookkeepingCiphertext, encrypted names, which nodes hold which file
Coordinator :5000Node admission and auth, placement, health checks, repair, reward metering, settlementOpaque blobs and their sizes
Storage nodesStore and serve replicas; only accept requests from the coordinatorRandom bytes
StorageIncentiveTakes storage payments, holds the reward pool, pays operatorsPublic on-chain state

Upload path

Browser encrypts → backend verifies your session and payment → coordinator picks the 3 least-loaded online nodes (different operators first) → copies are written in parallel → backend records which nodes hold the file.

Repair path

Nodes heartbeat every 5 seconds. After about 15 seconds of silence a node is marked offline and the coordinator asks the backend to repair it. Every replica it held is copied from a healthy replica onto a new node, and the file index is updated.

Security model

  • Keys. A random 32-byte vault secret, created in your browser, encrypts every file with AES-256-GCM. The backend stores it only in wrapped form, wrapped with a key your browser derives with HKDF-SHA-256.
  • Metadata. File names, types, sizes and dates are encrypted too. The server sees only an opaque ID.
  • Node isolation. Nodes accept data requests only with the coordinator's session token, and validate file IDs to prevent path traversal.
  • Node identity. Every node signs its join and login challenges with its own key. A node cannot claim another node's ID.

ZNode Grid is an MVP. The wrapping key is currently derived from your wallet address rather than from a wallet signature, so whoever operates the backend could unwrap vault keys; storage nodes cannot. There are also no storage audits yet (proofs that a node still holds data), and the contract has unit tests but no external audit. Don't store anything irreplaceable that you have no other copy of.

Payments

Storage is a one-time purchase made by calling purchaseStorage() with exactlystoragePrice. The contract rejects any other amount and any second purchase from the same wallet.

How the backend verifies a payment

  1. The server's RPC and the transaction are both on chain 4663.
  2. The transaction succeeded and has the required confirmations.
  3. It was sent to the StorageIncentive address by the signed-in wallet.
  4. Its call data is exactly purchaseStorage().
  5. Its value equals the configured price exactly.
  6. The contract emitted a matching StoragePurchased event.

Each transaction hash can be used once. If the hash is missing or stale, the backend falls back to the contract's hasPurchased(wallet), read at a confirmed block.

See the live state and every function on the contracts page.

Node rewards

The coordinator meters three things for every node while it's online:

SourceMeasured asConfigured rate
StorageBytes held × time0.0001 ETH per GB-hour
EgressBytes served on downloads0.0005 ETH per GB
UptimeTime online and heartbeating0.00001 ETH per hour
Minimum claimPending earnings per operator0.0001 ETH

Getting paid

  1. Register your operator wallet once with registerNode().
  2. Claim. The coordinator, acting as the contract's settler, calls rewardNode() to move your unclaimed earnings from the reward pool to your balance. A claim is capped at what the pool holds, and the rest stays unclaimed.
  3. Withdraw your balance to your wallet at any time with withdraw().

HTTP API

Backend

MethodPathPurpose
POST/nonceGet a login nonce for a wallet
POST/loginExchange a signed nonce for a session
GET/payment-statusWhether the wallet has storage (restores on-chain purchases)
POST/paymentVerify a purchase; txHash optional
POST/uploadUpload an encrypted file and metadata
GET/filesList your encrypted file index
GET/download/:idStream a file from any live replica
DELETE/files/:idDelete a file from every node

Authenticated calls send the session in the x-session header.

Coordinator: operators

MethodPathPurpose
POST/operator/nonce, /operator/loginWallet sign-in for operators
GET/operator/overviewNodes, earnings, tokens and payouts
POST/operator/join-tokensCreate a one-time join token
POST/operator/nodes/:id/exitStart a graceful exit
POST/operator/payoutsSettle unclaimed earnings on-chain
GET/networkPublic network totals and rates

Operator calls send the session in the x-operator-session header.

Troubleshooting

“Switch your wallet to Robinhood Chain”

Add the network in your wallet: chain ID 4663, RPC https://rpc.mainnet.chain.robinhood.com, currency ETH.

Payment says “not confirmed yet”

Wait a few seconds and click Buy storage again. The same transaction is re-checked and you won't be charged twice.

A node says “Contact check failed”

The coordinator couldn't reach the node at its --url. Make sure the port is reachable and the URL is correct.

Claim is disabled

Register your payout wallet first, make sure unclaimed earnings exceed the minimum, and check that settlement is configured on the coordinator.