The ERC-3643 / T-REX-style compliance primitive for Casper — privacy-preserving, agent-operated, on-chain enforced.
Casper has joined the ERC-3643 Association (the standards body for compliant RWA tokenization). Writ is built on that stack: every holder proves eligibility in zero-knowledge, a threshold of autonomous agents attests on-chain, and a native CEP-78 transfer filter enforces compliance at every transfer — without the issuer or the chain ever touching investor PII.
https://writ-app-production.up.railway.app
Product docs (how it works + verify-it-yourself): https://writ-app-production.up.railway.app/docs
All contracts are live on casper-test (Casper 2.x). Every hash below is real and verifiable on testnet.cspr.live.
| Contract | Role | Package hash (stable address) |
|---|---|---|
| groth16-verifier | On-chain Groth16 pairing verify — fraud-challenge path only | 2bc9a855 |
| credential-registry | Per-holder credential: attest, revoke, freeze, status | 2e19e2bf |
| challenge | Economic fraud disputes: bond → challenge → resolve (slash/burn) | c1080d67 |
| transfer-filter | CEP-78 hook — checks sender AND recipient; fail-safe deny | d84a9321 |
| writ-cep78 | RWA bond NFT (real CEP-78), wired to the filter | ad407c6b |
| writ-token | Writ fungible token + Odra transfer filter | 512068de |
| What it proves | Deploy hash |
|---|---|
| Regulated holder attest — genuine Poseidon commitment on-chain | f3fd7cbb |
| Kicker — transfer from a sanctioned sender reverts (filter error 159) | 3448182c |
| Recipient-aware deny — transfer to ineligible recipient reverts (159) | ce0f1a3a |
| Fraud slash — resolve → Groth16 FALSE → slash 500 + burn 110 CSPR | 0ae7aecd |
| Post-fraud transfer reverts — RevokedFraud holder blocked (159) | 8922e979 |
Investor browser Agent quorum (2-of-3) Casper testnet
───────────────── ────────────────────── ───────────────
generate Groth16 verify proof OFF-CHAIN registry.attest()
proof in-browser (snarkjs) - verifies ed25519
(snarkjs; claims + OFAC SDN screening quorum signatures
never leave device) co-sign credential - binds public_inputs
│ │ to nullifier/commitment
└──────────────────────→ sign ──────────────────→ - stores commitment
Onboarding: proof generated and verified off-chain; the chain stores signed credentials. The on-chain attest entrypoint verifies ed25519 quorum signatures and binds the published public_inputs[0:64] to the commitment. It does not run a Groth16/SNARK verifier at onboarding — that is the honest, correct framing.
On-chain Groth16 verification happens only in the fraud-challenge path: a challenger bonds 250 CSPR, claims a credential is fraudulent, and challenge.resolve cross-calls the groth16-verifier. If the proof is FALSE the attestors are slashed and the excess is burned.
Selective disclosure: Poseidon-commitment only — no ciphertext, no escrow. The holder keeps the preimage and reveals it peer-to-peer to a regulator, who recomputes Poseidon(claims) and checks it equals the on-chain commitment byte-for-byte.
Transfer gating: every CEP-78 transfer calls the transfer-filter hook, which checks both sender and recipient against the registry. Ineligible parties are denied by default (fail-safe deny).
| Parameter | Value |
|---|---|
| Attestor bond | 250 CSPR |
| Challenger bond | 250 CSPR |
| Challenger reward | 300 CSPR |
| Gas allowance | 90 CSPR |
| Reward cap | 390 CSPR |
| Burn to treasury | 110 CSPR (500 slashed − 390 reward cap) |
| On-chain Groth16 gas | ~79.29 CSPR |
Griefing invariant: A = G + R + B — a successful challenger is made whole on gas + reward and refunded their bond; the rest is burned. Self-slash deterrence: residual A − R.
| Layer | Technology |
|---|---|
| Smart contracts | Odra 2.8.x (the recommended Casper contract framework) |
| Wallet | CSPR.click (recommended Casper wallet connector) |
| Chain reads | CSPR.cloud (recommended Casper data layer) |
| ZK / proofs | Circom v2 + snarkjs, Groth16-BN254 |
| Agent quorum | Node.js server-side 2-of-3, runs in the app on Railway |
| Sanctions screening | OFAC SDN live screen at onboarding |
| Frontend | Next.js 16 / React 19, TypeScript, Tailwind, deployed on Railway |
| Network | Casper testnet (casper-test, Casper 2.x) |
| Suite | Result |
|---|---|
| credential-registry | 49/49 (both Odra backends) |
| challenge | 18/18 |
| integration lifecycle | full onboard → freeze → revoke → refresh cycle |
| disclosure | 14/14 (recompute Poseidon == live on-chain commitment, byte-for-byte) |
cd frontend
npm install
npm run dev
# → http://localhost:3000contracts/
groth16-verifier/ # on-chain Groth16 pairing verifier (Odra)
credential-registry/ # per-holder credential store
challenge/ # economic fraud-dispute contract
transfer-filter/ # CEP-78 transfer hook (recipient-aware, fail-safe deny)
writ-cep78/ # RWA bond NFT
writ-token/ # writ fungible token
integration/ # end-to-end lifecycle test suite
scripts/deploy/
deploy_v4.py # canonical V4 deployment (casper-test, put-deploy)
e2e_live.py # full lifecycle smoke test against live testnet
matrix_live.py # CEP-78 gated-transfer matrix (13 cases)
DEPLOYMENT.md # full V4 manifest with all tx hashes and gas figures
contracts/ six Odra contracts (see above)
agent/ verifier quorum + autonomous re-screening agent
circuits/ Circom eligibility circuit + snarkjs artifacts
disclosure/ selective-disclosure suite (Poseidon commitment verification)
frontend/ Next.js app (App Router, TypeScript, Tailwind)
scripts/ deploy scripts, lifecycle tests, payable-call tooling
docs/ PRD.md (full product spec), FRONTEND.md (frontend build guide)
- ARCHITECTURE.md — system design, trust model, enforcement model
- ADVERSARIAL_TESTING.md — fraud-challenge cycle, slash mechanics, edge cases
- scripts/deploy/DEPLOYMENT.md — full V4 deploy manifest, all tx hashes, gas figures
- Live /docs — product docs: how it works + verify-it-yourself
- docs/PRD.md — full product requirements