Your data already trains AI models. You just never see a cent of it.
Datavar is our attempt to fix that. We are building a data protocol where people contribute data on their own terms and get paid when it gets used. Every record carries a signed consent receipt, so an AI team licensing a dataset knows exactly who agreed to what, for which purpose, and until when. Nothing in it is scraped or pulled from some legal gray area.
The first piece of that protocol is live on Stellar testnet: a Soroban contract
that holds consent receipts as ledger state, in
contracts/. The demand side is still simulated — see
the caveats below for exactly what is and isn't real.
A marketplace can sell you a dataset. It can't prove consent, and it can't revoke access when consent expires. We want consent, licensing terms, and payouts to live at the record level, enforced by the protocol rather than by a PDF nobody reads. That's the part we think is actually hard, and the part worth building.
A receipt says who allowed which dataset to be used by whom, for what purpose, and until when. It lives in contract state rather than in our database, which is the whole argument: a buyer can check a receipt without an account with us, and revoking one ends it somewhere we cannot quietly edit. Two rules are enforced by the contract rather than by policy — consent always has an end date, and only the contributor who granted it can withdraw it.
Deploy your own or point at the existing testnet deployment, then set
NEXT_PUBLIC_CONSENT_CONTRACT_ID. Grant and revoke from
/dashboard/consent; the signature comes from the contributor's wallet, and
nothing on the server can sign in their place. Full instructions, the contract's
interface, and how to query it yourself are in
contracts/README.md.
You'll need Node 20 or newer.
npm install
npm run devThen open http://localhost:3000.
npm run build creates a production build, npm start serves it, and npm run lint runs ESLint.
Copy .env.example to .env.local and fill it in. The Supabase pair is enough
to run the landing page and the contributor dashboard; the rest turns on
payouts and the admin panel.
Run supabase/schema.sql in the Supabase SQL editor to create the tables,
policies and storage bucket. It's idempotent — re-run it after pulling changes
that add to it. Row-level security is on and keyed to the signed-in wallet, so
the dashboard shows nothing until sign-in is configured (below).
Connecting a wallet only asks it what address it holds; anyone can claim any address. Signing in makes it prove it: the server issues a SEP-10 challenge — a transaction built on sequence 0, which no network will ever accept — the wallet signs it, and the server mints a session token carrying the proved address.
That token is a JWT Supabase accepts, which is the point of it. Row-level security reads the wallet out of the token, so the database refuses to hand over someone else's rows rather than trusting this code to filter. A stranger holding the anon key reaches three aggregate views and nothing else: no dataset row, no sale row, no file.
Two server-side secrets make it work:
-
STELLAR_AUTH_SECRET— the key that signs challenges. Never funded, holds nothing; generate one withstellar keys generate datavar-authand read it back withstellar keys show datavar-auth. -
SUPABASE_JWT_SECRET— Supabase → Project Settings → API Keys → JWT Keys, listed as Legacy JWT Secret (older projects: Settings → API → JWT Settings). It is the same secret that signs the anon key, so if the anon key's header decodes toHS256, this is the one. Treat it like a password: anyone holding it can mint a session for any wallet.Should the project ever move to asymmetric signing keys and revoke the legacy secret, this stops working — Supabase keeps the private half of an asymmetric key. The migration is to publish a JWKS endpoint, register it under Third-Party Auth, and sign with our own key in
src/lib/auth/jwt.ts.
Operators are named in ADMIN_WALLETS, server-side. The browser is told whether
it is an operator, inside the signed token, and never gets to decide — and the
same claim is checked again by row-level security on every query the panel
makes.
Sales are simulated, but the payout that settles one is a real Stellar testnet payment. To turn it on:
- Create a testnet keypair in the Stellar Laboratory.
- Put the public key in
NEXT_PUBLIC_STELLAR_TREASURYand the secret inSTELLAR_TREASURY_SECRET. The secret has noNEXT_PUBLIC_prefix on purpose: it stays on the server, and onlysrc/app/api/claims/route.tsreads it. - Put your own wallet address in
ADMIN_WALLETS(comma-separated for more than one) to get into/admin. - Fund the treasury with friendbot — there's a button on the admin overview.
Then sell a dataset from /admin (by hand on the Datasets page, or a random
round on the Sales page) and claim it from /dashboard/earnings. The claim
returns a transaction hash that resolves on
stellar.expert.
Claiming needs a signed-in wallet, and the wallet being paid is read out of the
session rather than the request body — a stranger cannot force someone else's
payout out early. Marking a sale settled needs a settle claim that only the
payout route mints, for two minutes at a time, so a contributor cannot record
their own payout as claimed with an invented hash.
Next.js 16 (App Router), React 19, Tailwind CSS v4, TypeScript.
The App Router entry lives in src/app, and each landing page section (hero, stats, buyers, FAQ and so on) is its own component in src/components.
MIT. See LICENSE.