Public interest litigation on Arkiv
A petition, its deadline and the court's answer, on a public record.
OpenPIL records who filed a petition, which registered court holds its review, and whether the court decided before the due date. Anyone can read the same records from the public Tiramisu RPC, without this website.
A demonstration on the Tiramisu testnet. Petitions are synthetic and go to the fictional High Court of Vindhya Pradesh (VPHC). This is not an official filing service.
The problem
Why India
Public Interest Litigation gives civic concerns a route to court. A missing filing record or an unverifiable claim of a decision makes public scrutiny harder for everyone following a case.
PIL petitioners
Need to show that a petition was filed, by whom and when, and to know the date by which the court should respond.
Legal aid NGOs
File and follow petitions on behalf of communities and need a record of each one that does not depend on a single office keeping it.
Journalists and civic watchers
Track which petitions are still pending, which were decided, and which passed their deadline with no decision.
What OpenPIL demonstrates for them
- Proof of filing
- Each petition records its filing wallet as on chain creator metadata. The petition page shows it next to the court and filing date.
- A deadline you can see
- The review period is a separate record that expires at the due date. The petition page counts down to it and names the court that holds it.
- A decision anyone can check
- Accept or reject reasons are stored as records created by the registered court wallet. The Verification panel checks that creator against the registry.
- No silent disappearance
- When the due date passes without a decision, the petition is shown as Expired, in public, rather than quietly dropping off a list.
Every petition in this demo is synthetic and every decision comes from a demonstration court. OpenPIL shows how such a record could work; it is not connected to any real court, and a record here has no legal effect.
Lifecycle
How it works
A petition and its review are two records with different lifetimes. The review is a lease the court holds until the due date.
One transaction to file
Filing creates two records at once. The petition carries typed attributes and a markdown body. The review carries the due date as its expiry and is handed to the court in the same transaction, so the petitioner stays its creator while the court becomes its owner.
petition
- title
- str
- court
- strVPHC
- filedat
- u64
- duedate
- u64
review
- petition
- strkey
- court
- strwallet
- duedate
- u64
- extensions
- i320
- 1
File
The petitioner signs one transaction that creates the petition and its review, and hands the review to the registered court.
- 2
Under review
The review record expires at the due date. While it lives, the petition shows Under review with a countdown.
- 3
Extend
Before the deadline, the court can record a reason and push the due date later. The review's expiry moves with it.
- 4
Decide or expire
A decision is recorded and the review deleted in one transaction. With no decision by the due date, the status is Expired.
Trust model
Why Arkiv
The point is that readers should not have to trust whoever runs the website. Here is what each alternative would leave to trust.
| What the record must prove | Operator database | IPFS | Own contract with a subgraph or API | Arkiv |
|---|---|---|---|---|
| Who filed and who decided | Whatever the operator's table says | Content is addressed by hash; no signer is recorded | Possible, if you deploy a contract and someone runs the indexer | $creator is set from the signing wallet and cannot be changed |
| Court holds the review | A role column the operator can edit | No ownership of a file | Custom contract logic | Ownership transfer to the court wallet, in the filing transaction |
| Deadline enforced by the data | A cron job on the operator's server | No expiry tied to a deadline | Custom contract logic and indexer | The review record expires at its due date |
| Decision closes the review | Two writes the operator promises to do together | Not applicable | Custom contract logic | One batch creates the decision and deletes the live review |
| Anyone can re-read it | Only through the operator's API | Content, yes. Typed queries, no | Through whoever hosts the subgraph or API | Typed queries on the public Tiramisu RPC |
What stays off Arkiv
Attributes and payloads are public. OpenPIL keeps these out of every record:
- Real names and identities of petitioners
- Sensitive documents and evidence
- Credentials and private keys
- Payments
Expiry removes a record from queries but does not erase transaction data or copies others made.
Reproducible without OpenPIL
The app reads through its own keyed proxy at /api/arkiv. The same query works directly against rpc.tiramisu.db-chain.testnet.arkiv.network with no login and no OpenPIL server. Anonymous rate limits still apply.
In the code
How OpenPIL uses Arkiv
Each Arkiv feature and the place you see it in the app. Paths are relative to the repository root.
Every record has app = "openpil" and a type. Review, extension and decision point at their petition through a petition attribute stored as str. Court trust comes from the registry's org records.
Typed attributes and a markdown payload
Court, title and status filters on Petitions, and the rendered petition body and decision reason.
title and court are str, filedat and duedate are u64; the body is text/markdown up to 32 KB.
lib/arkiv/model.tslib/arkiv/writes/markdown.tsAtomic executeBatch filing with predicted keys
New petition submits once and opens both the petition and its review period.
predictEntityKeys reserves two keys; one batch creates petition and review and transfers the review to the court. Returned keys are compared with the prediction.
lib/arkiv/writes/file-petition.tsExpiration as the review deadline
The Review period ring counts down to the due date and the status becomes Expired if it passes.
The review is created with ExpirationTime.atDate(duedate). Status is derived from chain block time.
lib/arkiv/writes/file-petition.tslib/arkiv/trust/revision3.tsExtension batch
The Due date moves and the petition shows how many times the court extended it.
One batch creates the reason record, patches review duedate and extensions, and extends the review expiry.
lib/arkiv/writes/extend-review.tsDecision batch
Accepted or Rejected with the court's reason; the review period closes.
One batch creates the decision and deletes the court owned review when it is still live.
lib/arkiv/writes/decide.tspermissionlessExtension and Keep alive
Any signed in user with test GLM can keep a petition or decision public for 30 more days.
extendEntity to the current expiry plus 30 × 43200 blocks. Petitions, decisions, extensions and court records are created with permissionlessExtension.
lib/arkiv/writes/keep-alive.tslib/arkiv/writes/markdown.tslib/arkiv/writes/entity.ts$creator versus $owner
The Review period lists the petitioner as creator and the court as owner; a seal marks registered courts.
A court counts only if its org record was created by the configured registry wallet. Write preflight queries courts with createdBy(registry).
lib/arkiv/trust/revision3.tslib/arkiv/queries/petitions.tswatchEntityEvents over WebSocket
Live activity on Petitions refreshes the list when known wallets write.
Subscribes without fromBlock. Events carry no attributes, so they only trigger a fresh read.
components/screens/petitions/live-feed.tsxlib/arkiv/clients.tsCursor pagination
Counts and status buckets cover every record, not only the first page.
The shared snapshot follows page.next() until hasNextPage() is false; write preflight follows cursors for courts and related records.
hooks/use-openpil-snapshot.tslib/arkiv/writes/review-context.ts
Transparency
Verify it yourself
Run this with node --input-type=module from the installed repository. It is the same whole application scan the app's screens are derived from.
import { createPublicClient } from "@arkiv-network/sdk";
import { tiramisu } from "@arkiv-network/sdk/chains";
import { eq, or } from "@arkiv-network/sdk/query";
import { http } from "viem";
const client = createPublicClient({ chain: tiramisu, transport: http() });
let page = await client.select("*")
.where(eq("app", "openpil")).limit(200).fetch();
const entities = [...page.entities];
while (page.hasNextPage()) {
page = await page.next();
entities.push(...page.entities);
}
const timing = await client.getBlockTiming();
console.dir({ entities, timing }, { depth: null });scoped helper from the same file:await scoped(eq("petition", petitionKey.toLowerCase())).fetch();
await client.getEntity(petitionKey);- Network
- Arkiv Tiramisu testnet, chain ID 7738577
- Explorer
- tiramisu.explorer.arkiv.network
- Registry wallet
- 0xcBC4caBF5Fbb766ee1796E2Bb5EaEa98a445E14f
- Data contract
arkiv/schema.mdlists every entity type, stored attribute type, expiration and exact query.- Known friction
arkiv/friction.mdrecords SDK and RPC issues met while building, such as typed equality and rate limits.
The court page lets anyone accept, reject or extend a review so judges can try the full cycle. The server signs those writes with the demo court key, so every demo decision is created by the registered court wallet regardless of who clicked. In a real deployment only the registered court's own wallet would sign.
Try it
See the record for yourself.
Browse the synthetic petitions, file one with a test wallet, or act as the demo court.