Production Docs

Module-by-Module
Setup Guide.

Everything you need to deploy Adduce for any country’s legal aid system. Real on-chain infrastructure, no simulations. Each module is independently configurable and verifiable on Solana Explorer.

Verify in 60 seconds. Click any link below to confirm this is real, deployed infrastructure.

What is Adduce

Adduce is an on-chain legal-aid plugin. It provides credential issuance, document anchoring, and payment settlement primitives that integrate into existing government legal-aid portals. Adduce does not replace your portal: it replaces the parts of your stack that are slow, opaque, or non-interoperable: identity attestations, document audit trails, and payment rails.

Protocol, not portal. In production, a government integrator (e.g. Government Justice Portal) embeds Adduce SDK calls into their existing system. The reference UI at /dashboard is a demo showing what those calls look like end-to-end.

Integration architecture

Your existing portal
Government IT System
Any justice portal, case management system, or ERP
Adduce SDK calls
Adduce Protocol Layer
  • SAS: identity attestations (eligibility certificates)
  • Light Protocol: ZK-compressed document hashes
  • x402: HTTP-native payment settlement
  • Anchor program: case lifecycle state machine
All transactions on
Public ledger
Solana
Auditable, cross-jurisdictional, no consortium required

What each module handles

1

Identity & Credentials (SAS)

Issues on-chain attestations that serve as the digital equivalent of a legal aid eligibility certificate: Berechtigungsschein (DE), Aide Juridictionnelle certificate, or any country’s legal aid entitlement document. Verifiable by any party without contacting the issuing authority.

sas-lib · Solana Attestation Service
2

Case Management (Anchor Program)

On-chain state machine tracking each case through its lifecycle: Open, InProgress, Closed, Paid. Jurisdiction-scoped configurations. Authority-gated transitions. All state changes produce verifiable transactions.

Anchor 0.32 · Solana Program
3

Document Integrity (SHA-256 Anchoring)

Lawyers anchor SHA-256 hashes of case documents on-chain. No PII is stored: only the cryptographic fingerprint. Any auditor can verify a document’s integrity by comparing hashes without accessing the original.

SHA-256 · anchorDocument instruction
4

ZK Selective Disclosure (Groth16)

Holders prove statements about credentials: “jurisdiction is DE, credential not expired”: without revealing other fields. A custom Circom circuit generates 256-byte Groth16 proofs verified on-chain via Solana’s alt_bn128 pairing. Same BN254 curve as Light Protocol and Ethereum ZK rollups. Mathematical privacy, not access-control privacy.

Circom · snarkjs · Groth16 · alt_bn128 syscall
5

ZK Compression (Light Protocol)

Compressed audit logs reduce storage costs by 98.7% compared to standard Solana PDAs. At government scale (100K+ cases/year), this means $22,000+ annual savings while maintaining full immutability and verifiability.

Light Protocol · Helius Photon Indexer
6

Payment Settlement (x402 + USDC)

Instant stablecoin disbursement upon case closure and credential verification. The x402 protocol handles the HTTP-native payment flow: GET returns 402 Payment Required, POST verifies credential + payment signature.

x402-solana · SPL Token (USDC/EURC)
6

Automation Agent

Background polling agent that scans for closed cases, verifies credentials, executes USDC transfers, creates compressed audit logs, and marks cases as paid. Runs as a cron job or continuous service.

scripts/agent.ts · 15s polling interval

Why a public chain

Legal aid certificates cross institutional boundaries: issued by one authority, verified by another, audited by a third. A private chain requires every participant to join the same network. A public chain lets any court, any lawyer, any auditor verify independently.

Dimension
Private Chain
Adduce (Solana)
Trust source
Consortium agreement (legal)
Cryptographic proof (math)
Settlement speed
Days to weeks (batch clearing)
400ms (USDC on Solana)
Cross-border verification
Requires bilateral agreement per country
Native: any node, any jurisdiction
Infrastructure model
CapEx: dedicated nodes + maintenance
OpEx: $0.004/credential, no servers
Deploy time
Months (consortium formation)
Hours (anchor deploy)
Storage cost per case
Varies by implementation
$0.004 (ZK compressed)
Audit access
Consortium members only
Any auditor, no permission needed

When a private chain is the right choice

Private chains win when strict data-residency is required (all data must stay within national borders), when regulation explicitly mandates a permissioned ledger, or when participating institutions already share network infrastructure. Adduce is designed for the opposite scenario: lightweight credential verification that must work across jurisdictions without bilateral agreements.

How the pieces connect

The protocol is a pipeline. Each stage produces an on-chain artifact that the next stage can verify independently.

Court / Ministry
Issues Credential
Lawyer
Opens Case
Lawyer
Anchors Documents
Court
Closes Case
Protocol
Settles Payment

Program ID: 3f1yBTY6xb6ESdzzb9LxAozv7uVsj9Y9AMEpnAwKJRNV

On-chain accounts

Account
Seeds / Derivation
Purpose
ProgramConfig
["config", jurisdiction]
Authority + jurisdiction metadata, case counter
CaseFile
["case", case_id]
Case lifecycle: status, document hash, lawyer, timestamps
SAS Attestation
deriveAttestationPda(credential, schema, nonce)
Verifiable credential: jurisdiction, tier, expiry

Three roles. One workflow.

Every legal aid system in the world has three actors, regardless of jurisdiction. Each has different permissions, different daily tasks, and different pain points. Adduce maps each role to specific on-chain capabilities.

Court

Court Operator

On-chain role: Authority signer

Government employee at the court or ministry. Reviews eligibility applications, issues legal aid certificates (eligibility certificates), monitors case progress, and approves payment disbursements. Role-separated: authority calls initialize and open_case, reviewer (or delegate) calls link_credential and close_case, payer calls mark_paid. Up to 3 delegates can be added via add_delegate. Custodial cases use open_case_custodial. Lawyers can be reassigned via reassign_lawyer.

Lawyer

Lawyer

On-chain role: Lawyer signer

Private attorney who takes legal aid cases. Must hold a valid SAS credential (the digital eligibility certificate). Receives case assignments, submits case documents by anchoring their SHA-256 hash on-chain, verifies credential validity, and claims payment after case closure. Can call anchor_document (must be the assigned lawyer; credential liveness re-checked on-chain).

Applicant

Applicant (Antragsteller / Citizen)

On-chain role: None (off-chain only)

Citizen seeking legal aid. Files an application at the court, receives an eligibility certificate if eligible, and takes it to a lawyer. The applicant has zero blockchain interaction. the system is invisible to them. Their data never touches the chain. Only entitlement metadata (jurisdiction, tier, expiry) is stored as a verifiable credential.

Issuance, review, and disbursement.

The court operator is the system’s authority. They control the full case lifecycle: from certificate issuance to payment approval.

Step 1
Review
Step 2
Issue Certificate
Step 3
Open Case
Step 4
Close Case
Step 5
Approve Payment

Mapping to on-chain operations

Operator Action
Adduce Operation
On-Chain Instruction
View pending applications
Dashboard: read case list
Read CaseFile PDAs
Issue eligibility certificate
Issue SAS credential to citizen
scripts/issue-credential.ts
Open case
Create case on-chain with lawyer commitment
open_case(case_id, lawyer_commitment, applicant, authorized_amount)
Monitor case progress
Read case status from chain
Read CaseFile.status
Approve payment
Record disbursement + payment reference
mark_paid(case_id, amount, reference)

Work Procedure: 3-step BS issuance

In a typical system, issuing an eligibility certificate follows a Review → Editing → Verification procedure. In Adduce:

  • Review: Operator views the submitted application form and attached documents on-screen
  • Editing: Operator sets the activity remark, selects status (“Issued”), and uploads the signed BS document
  • Verification: The SAS credential is created on-chain, the BS PDF preview is shown with QR code, and the operator clicks “Send” to finalize

Request, verify, submit, get paid.

The lawyer is the primary user of the system. They receive legal aid assignments, verify their eligibility certificate, submit case documents, and claim payment once the case is closed.

Step 1
Connect Wallet
Step 2
Verify Credential
Step 3
Submit Documents
Step 4
Claim Payment

Mapping to on-chain operations

Lawyer Action
Adduce Operation
On-Chain Instruction
Request certificate for client
Off-chain: client applies at court
-
Verify credential validity
Read SAS attestation PDA
fetchMaybeAttestation()
Submit case documents
Documents encrypted (X25519+AES-256-GCM), uploaded to Arweave via Irys (permanent, only assigned lawyer can decrypt). SHA-256 hash of plaintext anchored on-chain.
anchor_document(case_id, hash, lawyer_salt)
Check case status
Dashboard: My Cases view
Read CaseFile.status
Claim payment
POST /api/claim-payment
x402 payment + mark_paid

Pain point solved: In the current paper system, a lawyer must phone the issuing court to verify a eligibility certificate. With Adduce, verification is a single on-chain PDA read: no phone calls, no hold queues, no business-hour dependency.

Sensitive document handling: Case documents (billing forms, court filings, settlement records) are encrypted end-to-end using X25519 ECDH key agreement + AES-256-GCM. The encrypted blob is stored permanently on Arweave via Irys (pay once, stored forever). Only the assigned lawyer can decrypt using their wallet key. The plaintext SHA-256 hash is anchored on Solana for integrity verification. No sensitive content ever touches the public ledger. See scripts/encrypt-and-anchor.ts for the full working implementation.

Apply once. Carry a verifiable certificate.

The applicant is the citizen seeking legal aid. They interact with the court and the lawyer: never with the blockchain directly. Adduce is invisible to them.

  • Applicant goes to court (or online portal) and files an application for Beratungshilfe / legal aid
  • Court clerk reviews eligibility based on income and case merits
  • If approved, court issues an eligibility certificate: in Adduce, this becomes an SAS credential on-chain, tied to the lawyer’s wallet
  • Applicant takes the BS to a lawyer. The lawyer verifies it instantly on-chain: no phone calls needed
  • The applicant never creates a wallet, signs a transaction, or sees a blockchain address. The system is transparent to them.

Pain point solved: Today, applicants carry a paper certificate that can be forged, lost, or expired without anyone knowing. An on-chain attestation cannot be forged, cannot be lost (it lives on the blockchain), and expiry is checked automatically at verification time.

How an eligibility certificate becomes an on-chain lifecycle.

This is the complete flow from a citizen’s application to the lawyer’s payment. Each step shows who performs it, what happens on-chain, and the real Solana Devnet transaction from our sample case (Reference: 123 UR II 143/22).

  1. 1
    ApplicantFiles application at court
    Citizen Max Mustermann visits the court and submits income documents. This step is entirely off-chain.
  2. 2
    CourtReviews and approves eligibility
    Operator Sabine Mueller reviews the application using the 3-step work procedure (Review → Editing → Verification). Off-chain decision.
  3. 3
    CourtIssues Eligibility Certificate (SAS Credential)
    The court creates an on-chain attestation encoding jurisdiction (“DE”), eligibility tier (“TIER_1”), and expiry date. This is the digital eligibility certificate.
  4. 4
    CourtOpens case on-chain
    Authority creates a CaseFile PDA with the case ID, assigns lawyer Dieter Stein. Status: Open.
  5. 5
    LawyerAnchors case documents (SHA-256)
    Lawyer submits billing forms, certificate PDFs, and Vollmacht. The system hashes them and stores the fingerprint on-chain. Status: InProgress.
  6. 6
    ProtocolCreates compressed audit log (ZK)
    Light Protocol creates an immutable, ZK-compressed audit record at 98.7% lower cost than a standard PDA ($0.0038).
  7. 7
    CourtCloses case
    Operator reviews the completed work and closes the case. Status: Closed. The lawyer can now claim payment.
  8. 8
    ProtocolDisburses USDC payment (85 USDC)
    The automation agent or x402 endpoint transfers 85 USDC (EUR equivalent) from the court treasury to the lawyer’s token account.
  9. 9
    ProtocolMarks case as Paid (final)
    On-chain status set to Paid. This is the terminal state : the case is now a permanent, immutable record.

Every step after #3 produces a verifiable Solana transaction. The full cycle: from certificate issuance to payment : completed in under 2 minutes on-chain. In the paper system, the same cycle takes 6–12 months.

What breaks in the paper system.

Legal aid infrastructure across Europe shares the same failure modes. Each is a direct consequence of paper-based, siloed processes.

Certificate Forgery

A paper eligibility certificate can be photocopied and reused. No court can tell if the certificate was already consumed by another lawyer.

Adduce: SAS attestation is cryptographically bound to a specific wallet. Cannot be duplicated.

Payment Delays (6–12 months)

Lawyers submit paper forms, wait for manual review, and receive payment via bank transfer weeks or months later. 40% of lawyers stop taking legal aid cases.

Adduce: USDC disbursement happens in 400ms after case closure verification.

No Cross-Verification

A lawyer in Munich cannot verify a BS issued by a court in Hamburg without calling them during business hours. Cross-border is impossible.

Adduce: Any party reads the attestation PDA on-chain. Works across jurisdictions, 24/7.

Double Billing

A lawyer can submit the same BS for multiple payment claims. Detection relies on manual spreadsheet reconciliation.

Adduce: On-chain state machine enforces Open → InProgress → Closed → Paid. No state can be revisited.

Lost Paper Trail

Document submissions have no tamper-proof audit trail. Forms are lost, filing cabinets are inaccessible, and there is no proof of when something was submitted.

Adduce: SHA-256 hash anchored on Solana with immutable timestamp. Permanent proof of submission.

Manual Reconciliation

Court clerks manually match incoming billing forms to cases and payment records. Error-prone at any scale.

Adduce: The automation agent polls closed cases and disburses payments automatically every 15 seconds.

5-minute integration quickstart

The fastest path. Install the published SDK and call the already-deployed program on devnet. No cloning, no deploying, no Anchor setup. Works from any TypeScript/Node.js project. View on npm

Install the SDK (npm)
npm install @adduce/sdk

All 14 program instructions are callable through the SDK. The program is already deployed at 3f1yBTY6xb6ESdzzb9LxAozv7uVsj9Y9AMEpnAwKJRNV. You connect to it, not deploy your own.

SDK usage (TypeScript)
import { AdduceClient, Keypair } from "@adduce/sdk";

const wallet = Keypair.fromSecretKey(/* your keypair */);
const adduce = new AdduceClient({ cluster: "devnet", wallet });

// Open a case
const { casePda, lawyerCommitment } = await adduce.openCase({
  caseId: "CASE-DE-2025-001",
  lawyerPubkey: lawyerWallet.publicKey,
  applicant: citizenWallet.publicKey,
  authorizedAmount: 8500,
}, "DE");

// Read case status
const caseData = await adduce.getCase("CASE-DE-2025-001");
console.log(caseData.status); // "Open"

Or clone the full repo

For running scripts, modifying the program, or building the ZK circuit locally. Tested with the German jurisdiction but works for any of the 9 supported countries by changing the jurisdiction parameter.

Prerequisites

  • Node.js 22+ (nvm use 22)
  • Rust + Solana CLI + Anchor CLI
  • A Helius API key (free tier at helius.dev, required for Light Protocol)
  • Devnet SOL (solana airdrop 2)

Clones the repository, installs all dependencies (including snarkjs and circomlibjs for ZK proofs), compiles the Anchor program, and deploys it to Solana Devnet. The program ID is printed after deploy. Your wallet at ~/.config/solana/id.json becomes the upgrade authority.

Step 1: Clone, install, deploy
git clone https://github.com/SAHU-01/legal_aid.git && cd legal_aid
nvm use 22 && npm install
cp .env.example .env  # add your Helius API key
anchor build && anchor deploy --provider.cluster devnet

Deploys the eligibility credential schema to the Solana Attestation Service (SAS). The schema defines three fields:jurisdiction (12 bytes), eligibility_tier (12 bytes), and expiry_date (8 bytes). These fields are the same regardless of country. Output: a schema address saved to scripts/schema-address.json.

Step 2: Create a credential schema
npx ts-node scripts/create-schema.ts

Issues an eligibility certificate as an SAS attestation. The script fetches the schema, serializes the credential data (jurisdiction, tier, expiry), and creates the attestation PDA on-chain bound to the citizen’s wallet. This is the digital equivalent of a court issuing a paper certificate. Output: credential info saved to scripts/credential-info.json.

Step 3: Issue a credential to a citizen
npx ts-node scripts/issue-credential.ts

Executes the complete lifecycle on Devnet with real transactions: open_case link_credential anchor_document close_case USDC transfer mark_paid. Every step produces a Solana transaction with an Explorer link. The report includes cost analysis and timing for each instruction.

Step 4: Run the full case lifecycle
npx ts-node scripts/e2e-full-pipeline.ts

Independently verifies the full chain: SAS credential is valid and not expired, case status is Paid, document hash matches, and payment was disbursed. This is what an external auditor or a different jurisdiction would run to verify a case without any access to the issuing court’s internal systems.

Step 5: Verify credential and case
npx ts-node scripts/verify-credential.ts

Compiles the Groth16 circuit, runs the trusted setup ceremony, then generates and verifies a ZK proof that proves “jurisdiction is DE and credential is not expired” without revealing any other fields. The proof is 256 bytes and verifiable on-chain via verify_zk_disclosure.

Optional: Run ZK selective disclosure demo
cd circuits && npm install && ./build.sh
cd .. && npx ts-node scripts/zk-disclosure-demo.ts

Integrating into an existing portal

Adduce is designed to be called from any government portal via REST API. The on-chain program and TypeScript scripts are deployed and working today. The REST API wrapper (/api/gov/*) is the planned integration layer that makes these callable from Java, .NET, or any HTTP client. No existing system needs to be replaced.

Integration architecture

Your Portal (Java/Spring, .NET, etc.)
  ↓ REST/JSON (standard HTTP)
Adduce API Layer (Next.js, deployed alongside or as microservice)
  ↓ Anchor TypeScript SDK
Solana Devnet (public ledger)

What is live vs. planned

Language
Integration Method
Status
TypeScript / Node.js
Anchor SDK: direct program calls via @coral-xyz/anchor
Live (scripts/)
Rust
Anchor CPI: call from another Solana program
Live (CPI feature)
Any
Solana JSON-RPC: raw transaction construction over HTTP
Live (advanced)
Any (Java, .NET, Python)
REST API: /api/gov/* endpoints wrapping the above
Planned

Scenario 1: Court operator issues credential

The operator approves a legal aid application in the existing portal. The portal’s backend adds one call to create the on-chain credential. The TypeScript path works today. The REST path shows the planned API design.

Your Java/Spring backend calls this endpoint after the operator clicks “Approve.” The API handles all Solana transaction construction, signing, and confirmation. Your portal receives the credential address and transaction link for its internal records.

REST API (planned, any language)
POST /api/gov/credentials/issue
Content-Type: application/json

{
  "jurisdiction": "DE",
  "eligibilityTier": "TIER_1",
  "expiryDate": "2027-05-04",
  "citizenWallet": "Citizen4K...pubkey",
  "lawyerCommitment": "a3b2c1d4...sha256hex"
}

# Response: { credentialAddress, transactionSignature, explorerUrl }

For TypeScript/Node.js integrations, call the Anchor program directly. The sas-lib package handles credential creation. The Anchor client handles case management.

TypeScript SDK (live)
import { getCreateAttestationInstruction, deriveAttestationPda } from "sas-lib";

// 1. Derive the credential PDA
const [attestationPda] = await deriveAttestationPda({
  credential: credentialAddress,
  schema: schemaAddress,
  nonce: citizenWallet,
});

// 2. Build and send the attestation instruction
const ix = getCreateAttestationInstruction({
  authority: courtWallet,
  schema: schemaAddress,
  nonce: citizenWallet,
  data: serializedFields,  // jurisdiction + tier + expiry
  expiry: expiryTimestamp,
});

// 3. Open the case with lawyer commitment
await program.methods
  .openCase(caseId, lawyerCommitment, citizenWallet, authorizedAmount)
  .accounts({ config: configPda, caseFile: casePda, authority: courtWallet.publicKey })
  .signers([courtWallet])
  .rpc();

Scenario 2: Lawyer anchors documents

The lawyer uploads case documents through the existing portal. The portal hashes the document and anchors the hash on-chain. Documents stay in the existing DMS. Only the hash goes on-chain.

The portal computes SHA-256 of the uploaded document, then sends it along with the lawyer’s salt (generated during case assignment). The API verifies the lawyer commitment and anchors the hash. The document itself never leaves the portal’s DMS.

REST API (planned, any language)
POST /api/gov/cases/{caseId}/anchor
Content-Type: application/json

{
  "documentHash": "175b1e36...sha256hex",
  "lawyerWallet": "Lawyer5K...pubkey",
  "lawyerSalt": "random32bytehex..."
}

# The lawyer's salt proves they are the assigned lawyer
# (their pubkey + salt hashes to the commitment stored on-chain)

# Response: { transactionSignature, explorerUrl }

Scenario 3: Verify credential (any jurisdiction)

A court in one country needs to verify a credential issued by another country. No bilateral agreement needed. One API call.

Any system in any country can verify a credential by passing its on-chain address. No Solana SDK required. No consortium membership. The API reads the SAS attestation PDA and returns structured JSON.

REST API (planned, any language)
GET /api/gov/credentials/verify?address=C8B4AJp5...credentialPDA

# Response:
{
  "valid": true,
  "jurisdiction": "DE",
  "eligibilityTier": "TIER_1",
  "expiryDate": "2027-05-04",
  "isExpired": false,
  "issuer": "Court7K...pubkey",
  "explorerUrl": "https://explorer.solana.com/address/C8B4AJp5...?cluster=devnet"
}

Scenario 4: Close case and record payment

The existing payment system (bank transfer, SEPA, etc.) processes the actual payment. Then the portal callsmark_paid to record it on-chain with the ERP payment reference. The on-chain program validates that the disbursed amount does not exceed the authorized amount.

REST API (planned, any language)
# Close the case (reviewer or delegate)
POST /api/gov/cases/{caseId}/close
{ "signerWallet": "Reviewer3K...pubkey" }

# Record payment after bank transfer completes
POST /api/gov/cases/{caseId}/pay
{
  "disbursedAmount": 8500,
  "paymentReference": "SAP-INV-2025-04-28-00142",
  "signerWallet": "Payer9K...pubkey"
}

# Response: { status: "Paid", transactionSignature, explorerUrl }

What changes vs. what stays

Portal Action
What Adduce Adds
What Stays the Same
Issue certificate
One POST to create SAS attestation + case PDA
PDF generation, internal DB, operator UI
Submit documents
Documents encrypted end-to-end (X25519+AES-256-GCM), stored permanently on Arweave via Irys. Plaintext hash anchored on-chain. Only assigned lawyer can decrypt.
Upload flow, DMS as primary storage
Verify eligibility
One GET to read credential status
Decision logic, eligibility rules
Approve payment
One POST to record disbursement on-chain
Approval workflow, bank transfer, audit requirements
Reassign lawyer
One POST with new lawyer commitment
Case assignment UI, notification flow
Audit/report
Read case PDAs from Solana (or /api/gov/cases)
Reporting format, compliance requirements

Government tech stack compatibility

Stack
Integration Path
Effort
Java / Spring Boot
REST client (HttpClient or RestTemplate) calling /api/gov/* endpoints
1-2 days
.NET / C#
HttpClient calling /api/gov/* endpoints
1-2 days
Python / Django
requests library calling /api/gov/* endpoints
1 day
Node.js / TypeScript
Direct Anchor SDK calls (no REST layer needed)
Hours
Legacy SOAP/XML
Middleware adapter: SOAP-in, REST-out to Adduce API
1 week
No portal (paper only)
Use Adduce demo dashboard directly at /dashboard
Zero (use as-is)

Anchor program reference

Program ID: 3f1yBTY6xb6ESdzzb9LxAozv7uVsj9Y9AMEpnAwKJRNV

initialize(jurisdiction, expected_schema, reviewer, payer_role)

Property
Value
Notes
Signer
authority
Must be payer. Becomes the jurisdiction authority.
Params
jurisdiction: String, expected_schema: Pubkey, reviewer: Pubkey, payer_role: Pubkey
Role separation: authority opens cases, reviewer links/closes, payer approves payment
Creates
ProgramConfig (293 bytes)
authority, reviewer, payer, jurisdiction, expected_schema, operations_wallet, delegates[], case_timeout_days, total_cases=0

open_case(case_id, lawyer_commitment, applicant, authorized_amount)

Property
Value
Notes
Signer
authority (must match config.authority)
Only the jurisdiction authority can open cases
Params
case_id: String, lawyer_commitment: [u8;32], applicant: Pubkey, authorized_amount: u64
lawyer_commitment = SHA-256(lawyer_pubkey + salt). Authorized amount enforced during mark_paid.
Status
Open
CaseFile (404 bytes) with credential_pubkey=default, commitment_root=[0;32]

link_credential(case_id, commitment_root)

Property
Value
Notes
Signer
reviewer or authority
Role-separated: reviewer links credentials
Accounts
config, case_file (mut), credential_account, authority
credential_account = SAS attestation PDA
Validates
owner == SAS, schema match, expiry, nonce == applicant
Dynamic SAS binary parsing; citizen binding enforced
Params
case_id: String, commitment_root: [u8; 32]
Merkle root of credential field commitments (must be non-zero)

anchor_document(case_id, document_hash, lawyer_salt)

Property
Value
Notes
Signer
lawyer (proves identity via SHA-256 commitment)
SHA-256(lawyer_pubkey + salt) must match case_file.lawyer_commitment
Params
document_hash: [u8;32], lawyer_salt: [u8;32]
Salt used to verify lawyer commitment without exposing pubkey on-chain
Checks
credential liveness, commitment root non-zero
Credential re-checked for liveness (revoked = rejected). LawyerCommitmentMismatch if salt wrong.
Status
Open/InProgress InProgress
SHA-256 document hash stored on CaseFile PDA

close_case(case_id)

Property
Value
Notes
Signer
reviewer, authority, or delegate
If case exceeds case_timeout_days while InProgress, authority can close directly (escalation)
Checks
credential liveness re-check, status == InProgress
Credential must still be live on-chain. Revoked credentials block case closure.
Status
InProgress Closed
Lawyer can now claim payment

mark_paid(case_id, disbursed_amount, payment_reference)

Property
Value
Notes
Signer
payer or authority
Role-separated: payer approves payment after USDC or bank transfer
Params
disbursed_amount: u64, payment_reference: String (max 64)
Amount must be <= authorized_amount. Reference = ERP invoice number or internal tracking ID for audit trail.
Status
Closed Paid (terminal)
PaymentExceedsAuthorized error if disbursed > authorized

Additional instructions (v2)

Instruction
Signer
Purpose
open_case_custodial
Authority
Open case for citizen without wallet. Uses citizen_id_hash (SHA-256 of national ID) instead of pubkey. Court acts as custodian.
reassign_lawyer
Authority / Reviewer / Delegate
Switch lawyer on active case. New lawyer_commitment set. Emits LawyerReassigned event with old/new commitments and reason.
update_case_status
Authority / Reviewer / Delegate
Non-linear transitions: InProgress to Stayed/Appealed/Withdrawn. Closed to Remanded. Reverse transitions back to InProgress.
reopen_case
Authority only
Moves Closed back to InProgress. Requires reason string. Emits CaseReopened event.
verify_zk_disclosure
Any signer
Verifies 256-byte Groth16 proof on-chain via alt_bn128 pairing. Checks commitmentRoot matches PDA, predicateSatisfied == 1.
add_delegate / remove_delegate
Authority only
Manage up to 3 delegate reviewers. Delegates can link credentials, close cases, reassign lawyers.
fund_operations
Authority
Transfer SOL to the operations wallet. Ministry funds all transaction fees from this wallet.

Error codes

Code
Name
Cause
6000
JurisdictionTooLong
jurisdiction > 10 chars
6001
CaseIdTooLong
case_id > 32 chars
6002
Unauthorized
Signer does not match expected authority or lawyer
6003
InvalidStatus
Case status does not permit this operation
6004
CredentialNotLinked
No credential linked: call link_credential first
6005
CredentialWrongOwner
Account not owned by SAS program
6006
CredentialSchemaMismatch
Credential schema doesn't match jurisdiction's expected schema
6007
CredentialExpired
Credential expiry timestamp is in the past
6009
CredentialApplicantMismatch
Credential nonce does not match case applicant
6010
CredentialRevoked
Credential account has been closed (revoked via SAS)

Identity & Credentials

The Solana Attestation Service (SAS) issues verifiable on-chain eligibility credentials. The same schema works across all 9 supported jurisdictions: the Aide Juridictionnelle in France, the Toevoeging in Netherlands, and so on. Configurable per jurisdiction.

Schema fields

Field
Type
Description
jurisdiction
String (12 bytes)
ISO country code + region (e.g. "DE", "FR", "AT")
eligibility_tier
String (12 bytes)
Aid level (TIER_1 = full, TIER_2 = partial, TIER_3 = consultation only)
expiry_date
u64 (Unix timestamp)
Credential validity period, typically 1 year from issuance

Setup steps

  • Deploy the SAS schema once per jurisdiction using scripts/create-schema.ts
  • The schema address and credential address are saved to scripts/schema-address.json
  • Issue credentials to lawyers using scripts/issue-credential.ts or programmatically via the dashboard
  • Verification happens on-chain: any party can read the attestation PDA and check expiry
Issue a credential
# Deploy schema (one-time per jurisdiction)
npx ts-node scripts/create-schema.ts

# Issue credential to a lawyer wallet
npx ts-node scripts/issue-credential.ts

# Verify a credential on-chain
npx ts-node scripts/verify-credential.ts

Case Management

The Anchor program manages the full case lifecycle on-chain with 14 instructions, 4 events, and 26 error codes. The core flow (open, link, anchor, close, pay) is extended with custodial support, lawyer reassignment, delegation, escalation, extended case statuses, payment validation, and ZK proof verification.

Core flow (7 instructions)

Instruction
Signer
Effect
initialize
Authority
Creates ProgramConfig (293 bytes) with roles, operations wallet, timeout
open_case
Authority
Creates CaseFile (404 bytes) with lawyer_commitment + authorized_amount
open_case_custodial
Authority
Same as open_case but for citizens without wallets (citizen_id_hash)
link_credential
Reviewer / Delegate
Binds SAS credential to case. 5-point validation. Supports custodial nonce.
anchor_document
Lawyer (via salt)
Stores document hash. Lawyer proves identity via SHA-256 commitment.
close_case
Reviewer / Delegate
Closes case. Credential liveness re-checked. Timeout escalation supported.
mark_paid
Payer
Records disbursed_amount + payment_reference. Enforces amount <= authorized.

Extended instructions (7 more)

Instruction
Signer
Effect
reassign_lawyer
Authority / Reviewer
Switch lawyer on active case. Emits LawyerReassigned event.
update_case_status
Authority / Reviewer
Non-linear transitions: Stayed, Appealed, Withdrawn, Remanded
reopen_case
Authority only
Moves Closed back to InProgress with reason + event
verify_zk_disclosure
Any signer
Verifies 256-byte Groth16 proof on-chain (alt_bn128 pairing)
add_delegate
Authority
Add delegate reviewer (max 3)
remove_delegate
Authority
Remove delegate
fund_operations
Authority
Top up operations wallet with SOL for transaction fees

State machine (8 statuses)

Open → InProgress → Closed → Paid
InProgress ↔ Stayed (court stay, reversible)
InProgress ↔ Appealed (appeal filed, reversible)
InProgress → Withdrawn (applicant withdraws)
Closed → Remanded (higher court sends back → InProgress)
Closed → InProgress (via reopen_case with reason)
Build and deploy
# Build the Anchor program
anchor build

# Deploy to devnet
anchor deploy --provider.cluster devnet

# Run integration tests
anchor test

Document Integrity & Storage

Adduce uses a layered storage architecture. No personal data or document content touches the public ledger. The chain stores only cryptographic proofs of existence. Actual documents stay where they are today.

Where data lives

Data Type
Where Stored
Technology
Who Can Read
Credential (eligibility)
Solana (PDA)
SAS attestation
Anyone (but fields hidden behind commitments)
Case lifecycle state
Solana (PDA)
Anchor CaseFile account
Anyone (lawyer identity is a hash)
Document hash
Solana (CaseFile PDA)
SHA-256 fingerprint
Anyone (but hash is one-way, can't reconstruct doc)
Compressed audit logs
Solana (compressed state)
Light Protocol + Helius Photon
Anyone with Helius RPC (not enumerable)
Encrypted documents
Arweave (permanent storage)
Irys upload + X25519+AES-256-GCM
Only assigned lawyer (holder of decryption key)
Original case files
Government DMS (unchanged)
Existing ministry system
Existing access controls
Citizen personal data
Government DB only
Never touches blockchain
Government staff under GDPR obligations

Document anchoring flow

  • Lawyer uploads documents via the dashboard or ministry portal
  • System computes SHA-256 hash of the document
  • Lawyer provides their salt to prove assignment (commitment verification)
  • Hash is stored in the CaseFile PDA via anchor_document
  • Credential liveness is re-checked before anchoring is allowed
  • Any auditor can verify: hash the document locally, compare with on-chain hash

Encrypted document storage (optional)

For cases where documents need to be shared securely between the court and the assigned lawyer, Adduce provides end-to-end encrypted storage via Arweave (permanent, decentralized) through the Irys upload service.

The court encrypts the document so only the assigned lawyer can read it. The plaintext hash is anchored on-chain (for integrity verification). The ciphertext is stored on Arweave (permanent, censorship-resistant). The lawyer decrypts using their wallet key. See scripts/encrypt-and-anchor.ts for the full working example.

Encrypt and upload a document (TypeScript, live)
import { encryptDocument, deriveEncryptionKeypair } from "./lib/privacy";
import { uploadToArweave } from "./upload-to-arweave";

// 1. Derive encryption keys from Solana wallet (deterministic)
const senderKeys = deriveEncryptionKeypair(courtWallet.secretKey);
const recipientKeys = deriveEncryptionKeypair(lawyerWallet.secretKey);

// 2. Encrypt document (X25519 ECDH + AES-256-GCM)
const envelope = encryptDocument(
  documentBuffer,
  recipientKeys.publicKey,    // only the lawyer can decrypt
  courtWallet.secretKey       // court signs the encryption
);

// 3. Upload ciphertext to Arweave via Irys (permanent storage)
const arweaveUrl = await uploadToArweave(envelope);

// 4. Anchor the plaintext hash on-chain (proves document existed)
// envelope.plaintextHash goes into anchor_document()
// The actual document is encrypted on Arweave, hash is on Solana

GDPR compliance

  • No personally identifiable information (PII) on-chain
  • Document hashes are one-way: cannot reconstruct the original
  • Encrypted documents on Arweave are readable only by the assigned lawyer
  • Client names, addresses, case details remain in the national system
  • Lawyer identity on-chain is a SHA-256 commitment, not a raw public key

Scripts

Script
What It Does
Status
scripts/encrypt-and-anchor.ts
Encrypt document (X25519+AES-256-GCM), anchor hash on Solana
Live
scripts/upload-to-arweave.ts
Upload encrypted document to Arweave via Irys
Live
scripts/anchor-document-compressed.ts
Anchor hash using Light Protocol compression (98.8% cheaper)
Live
scripts/query-compressed.ts
Query compressed accounts via Helius Photon indexer
Live
scripts/cost-comparison.ts
Compare standard PDA vs compressed storage costs
Live

ZK Compression (Light Protocol)

Light Protocol compresses on-chain state using zero-knowledge proofs, reducing storage costs by 98.8%. This is critical at government scale: a country processing 100,000 legal aid cases per year saves over $22,000 annually on infrastructure costs alone.

Cost comparison

Method
Cost per case
At 100K cases/year
Standard PDA
~$0.30 (1,997,520 lamports)
~$30,000/year
ZK Compressed
~$0.004 (25,003 lamports)
~$400/year

How compression works

  • Light Protocol stores data as leaves in a Merkle tree rather than individual accounts
  • Validity is proved via ZK proofs (same BN254 curve as our selective disclosure circuit)
  • Compressed accounts are not directly enumerable on Explorer (privacy benefit)
  • Helius Photon indexer provides query access to compressed state
  • From the developer perspective: use createRpc() from @lightprotocol/stateless.js and call compress()

The compression script uses Helius as both RPC and Photon indexer. All three parameters to createRpc() point to Helius because it serves JSON-RPC, prover, and indexer functions from a single endpoint. See scripts/anchor-document-compressed.ts for the full implementation.

Compress and anchor audit log (TypeScript, live)
import { createRpc, compress } from "@lightprotocol/stateless.js";

// 1. Connect to Helius (required for Light Protocol)
const rpc = createRpc(HELIUS_RPC_URL, HELIUS_RPC_URL, HELIUS_RPC_URL);

// 2. Compress SOL into Light Protocol state tree
const compressLamports = 10_000;
await compress(rpc, payer, compressLamports, payer.publicKey);

// 3. Anchor case metadata as compressed memo
const memo = JSON.stringify({
  case_id: "BS-DE-143/22",
  document_hash: "175b1e36...",
  timestamp: Date.now(),
  status: "closed"
});
// Memo program anchors this in compressed state
// Cost: ~$0.004 vs ~$0.30 for standard PDA

Requirements

  • Helius RPC endpoint (standard Solana RPC does not support compressed accounts)
  • Set HELIUS_RPC_URL in your .env file
  • Photon indexer is built into Helius: no separate service needed
  • @lightprotocol/stateless.js and @lightprotocol/compressed-token packages

Payment Settlement

The x402 protocol enables HTTP-native payment flows. When a lawyer claims payment, the system verifies their SAS credential and the on-chain payment transaction before confirming. Settlement happens in USDC (or EURC for eurozone deployments).

Payment flow

  • GET /api/claim-payment: Returns 402 Payment Required with x402 spec (amount, asset, credential requirements)
  • POST /api/claim-payment: Accepts payment signature + lawyer wallet. Verifies: (a) SAS credential exists and is valid, (b) USDC transfer confirmed on-chain, (c) case is in Closed status
  • On success, the automation agent or authority calls mark_paid to finalize the case

x402 headers

x402 payment response
HTTP/1.1 402 Payment Required
X-Payment-Amount: 85000000
X-Payment-Asset: USDC
X-Payment-Chain: solana:devnet
X-Payment-Recipient: <authority-wallet>
X-Credential-Required: SAS
X-Credential-Schema: 7uKMGSgup1UZ26MnptCCMCac6rqpe6vBNXET338cDeid

Automation Agent

The agent runs as a background service, polling the on-chain program every 15 seconds for cases in Closed status. When it finds one, it executes the full disbursement pipeline automatically: credential verification, USDC transfer, compressed log creation, and status update.

What the agent does

  • Scans all CaseFile PDAs for status = Closed
  • For each closed case, verifies the assigned lawyer’s SAS credential
  • Transfers USDC from the court treasury to the lawyer’s token account
  • Creates a compressed audit log via Light Protocol
  • Calls mark_paid to finalize the case on-chain
Run the agent
# Run the production automation agent
npx ts-node scripts/agent.ts

# Or run the full e2e pipeline (demo mode)
npx ts-node scripts/e2e-full-pipeline.ts

# Run the German BS sample case
npx ts-node scripts/sample-german-bs-case.ts

Sample Case: German Berechtigungsschein

This is a real case executed on Solana Devnet following the exact German Beratungshilfe workflow. Every transaction below is clickable and verifiable on Solana Explorer. Reference: 123 UR II 143/22.

Step
Operation
Transaction
1
Berechtigungsschein Issued
3
Documents Anchored
4
Compressed Audit Log
6
Payment Disbursed (85 USDC)
7
Case Marked Paid

Key addresses

Entity
Type
Address
Program
Anchor Program
Berechtigungsschein
SAS Attestation PDA
Case PDA
CaseFile Account
SAS Schema
Credential Schema

Configure for any country

Adduce is jurisdiction-agnostic by design. The protocol uses a two-character jurisdiction code as the seed for all jurisdiction-scoped accounts. To deploy for a new country, you configure three things: the jurisdiction code, the SAS credential schema, and the payment asset.

DE
Germany
Berechtigungsschein
Beratungshilfe system. Court-issued certificate. Tiers: consultation, representation, settlement. Payment: fixed fee schedule per assistance type.
FR
France
Aide Juridictionnelle
Bureau d’aide juridictionnelle issues certificates. Income-based tiers (full/partial). Highest volume in EU.
AT
Austria
Verfahrenshilfe
Court-granted procedural assistance. Income verification required. Smaller market, easier pilot entry.
NL
Netherlands
Toevoeging
Raad voor Rechtsbijstand issues assignment letters. High digitalization readiness. Digital credential early adopter.
ES
Spain
Justicia Gratuita
Regional bar association managed. Income threshold based. Comisión de Asistencia Jurídica Gratuita issues certificates.
IT
Italy
Patrocinio a spese dello Stato
Consiglio dell’Ordine degli Avvocati manages applications. Income-based eligibility with regional variations.

Configuration steps for a new country

New jurisdiction setup
# 1. Set jurisdiction code in your environment
export JURISDICTION="FR"  # France

# 2. Deploy SAS credential schema for the jurisdiction
#    Edit scripts/create-schema.ts — change schema fields if needed:
#    - jurisdiction: "FR"
#    - eligibility_tier: mapped to local categories
#    - expiry_date: per local regulation
npx ts-node scripts/create-schema.ts

# 3. Initialize the on-chain jurisdiction config
#    The Anchor program creates a ProgramConfig PDA
#    seeded with ["config", "FR"]
#    This is done automatically on first case open

# 4. Configure payment asset
#    USDC for dollar-pegged, EURC for eurozone
#    Set in .env: PAYMENT_MINT=<token-mint-address>

# 5. Issue credentials to registered lawyers
npx ts-node scripts/issue-credential.ts

# 6. Start the automation agent
npx ts-node scripts/agent.ts

What you customize per country

Parameter
Where
Example
Jurisdiction code
.env, scripts, Anchor init
"DE", "FR", "AT", "NL", "BR"
Credential schema
scripts/create-schema.ts
Field names, sizes, tiers per local law
Payment asset
.env PAYMENT_MINT
USDC, EURC, or custom SPL token
Fee schedule
scripts/agent.ts, API route
Per-case payment amounts by assistance type
Authority wallet
~/.config/solana/id.json
Court/ministry keypair that signs state transitions

Quickstart

Full setup from clone to running sample case in under 10 minutes.

Terminal
# Clone the repository
git clone https://github.com/SAHU-01/legal_aid.git
cd legal_aid

# Install dependencies (requires Node >= 22)
nvm use 22
npm install

# Configure environment
cp .env.example .env
# Edit .env — add your Helius API key:
# HELIUS_RPC_URL=https://devnet.helius-rpc.com/?api-key=YOUR_KEY

# Build the Anchor program
anchor build

# Deploy to Solana devnet
anchor deploy --provider.cluster devnet

# Run the German sample case (creates real on-chain txs)
npx ts-node scripts/sample-german-bs-case.ts

# Start the Next.js dashboard
npm run dev

# Open http://localhost:3000/dashboard

Prerequisites

  • Node.js 22+: Required by Anchor SDK. Use nvm use 22
  • Rust + Anchor CLI: cargo install --git https://github.com/coral-xyz/anchor avm
  • Solana CLI: sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
  • Helius API key: Free tier at helius.dev (required for Light Protocol / compressed accounts)
  • Devnet SOL: solana airdrop 2 (fund your local wallet)

ZK Selective Disclosure

A custom Circom circuit enables true zero-knowledge credential verification. Holders prove statements about credential fields without revealing them. Proofs are verified on-chain using Solana’s native alt_bn128 pairing syscall.

Circuit specification

Property
Value
Notes
Circuit
circuits/selective_disclosure.circom
Circom 2.2
Curve
BN254 (alt_bn128)
Same as Light Protocol and Ethereum ZK rollups
Hash
Poseidon
Snark-friendly, replaces SHA-256 for commitments
Constraints
7,883
Well within Solana compute budget
Proof size
256 bytes
A[G1] + B[G2] + C[G1]
Public inputs
7
commitmentRoot, disclosedValue, disclosureIndex, predicateValue, predicateIndex, predicateSatisfied, issuerPubkeyHash
Verification cost
~200,000 CU
4 pairing + 7 scalar multiplications
Proof generation
~660ms (off-chain)
snarkjs + circuit WASM

What the verifier learns vs. what stays private

Verifier Learns
Stays Private (ZK)
Disclosed field value (e.g., jurisdiction = "DE")
All other field values
Predicate result (e.g., "not expired" = true)
Exact expiry date
Credential issuer hash
Applicant identity, case type, salts

Build the circuit

Circuit build + test
cd circuits
npm install          # installs circomlib + snarkjs
./build.sh           # compile + Powers of Tau + phase 2 setup + export VK
node test_proof.js   # generate + verify a test proof locally

# Run the full ZK demo
cd .. && npx ts-node scripts/zk-disclosure-demo.ts

SDK Integration Guide

How external systems (government portals, case management systems, ERP platforms) integrate with Adduce. Every instruction is callable via the Anchor TypeScript SDK.

PDA derivation

TypeScript: derive account addresses
import { PublicKey } from "@solana/web3.js";

const PROGRAM_ID = new PublicKey("3f1yBTY6xb6ESdzzb9LxAozv7uVsj9Y9AMEpnAwKJRNV");

// Config PDA (one per jurisdiction)
const [configPda] = PublicKey.findProgramAddressSync(
  [Buffer.from("config"), Buffer.from("DE")],
  PROGRAM_ID
);

// Case PDA (one per case ID)
const [casePda] = PublicKey.findProgramAddressSync(
  [Buffer.from("case"), Buffer.from("BS-DE-143/22")],
  PROGRAM_ID
);

Lawyer commitment generation

TypeScript: create lawyer commitment
import { createHash, randomBytes } from "crypto";

// Generate commitment (done once during open_case)
const lawyerPubkey = lawyerKeypair.publicKey.toBytes();
const salt = randomBytes(32);
const commitment = createHash("sha256")
  .update(Buffer.concat([lawyerPubkey, salt]))
  .digest();

// Store salt securely — lawyer needs it for anchor_document
// commitment goes on-chain, salt stays off-chain

Custodial case (citizen without wallet)

TypeScript: open custodial case
import { createHash } from "crypto";

// Hash the citizen's national ID (never stored in plaintext)
const citizenIdHash = createHash("sha256")
  .update("DE-PERSONALAUSWEIS-L01234567")
  .digest();

// Court acts as custodian — citizen never touches blockchain
await program.methods
  .openCaseCustodial(
    "BS-DE-143/22",
    lawyerCommitment,
    Array.from(citizenIdHash),
    custodianPubkey,
    85_000_000  // authorized amount in atomic units
  )
  .accounts({ config: configPda, caseFile: casePda, authority: courtWallet.publicKey })
  .signers([courtWallet])
  .rpc();

API & Scripts

API Routes

Endpoint
Method
Description
/api/claim-payment
GET
Returns 402 with x402 payment requirements and credential spec
/api/claim-payment
POST
Verifies SAS credential + payment tx, returns 200 with confirmation

Scripts reference

Script
Purpose
When to use
sample-german-bs-case.ts
Full German BS workflow
Demo, testing, generating verifiable sample data
e2e-full-pipeline.ts
Complete 10-step pipeline
Full E2E testing, demo report generation
agent.ts
Production automation
Continuous operation, auto-disbursement
create-schema.ts
Deploy SAS schema
One-time per jurisdiction setup
issue-credential.ts
Issue credential
Onboarding a new lawyer
verify-credential.ts
Verify credential
Check credential validity before payment
cost-comparison.ts
Cost analysis
Standard PDA vs ZK compressed cost comparison
create-test-cases.ts
Generate test data
Creating multiple cases for agent testing

Environment variables

.env
# Required
HELIUS_RPC_URL=https://devnet.helius-rpc.com/?api-key=YOUR_KEY
NEXT_PUBLIC_HELIUS_RPC_URL=https://devnet.helius-rpc.com/?api-key=YOUR_KEY

# Program addresses (set after deployment)
NEXT_PUBLIC_PROGRAM_ID=3f1yBTY6xb6ESdzzb9LxAozv7uVsj9Y9AMEpnAwKJRNV
NEXT_PUBLIC_SAS_PROGRAM_ID=22zoJMtdu4tQc2PzL74ZUT7FrwgB1Udec8DdW4yw4BdG

# Network
NEXT_PUBLIC_NETWORK=devnet

Authority model, revocation, and upgrades

Issuer authority model

Each jurisdiction has a single ProgramConfig PDA with anauthority pubkey. This authority is the only signer that can open cases, close cases, mark payments, and issue SAS credentials. In production, this is the court or ministry keypair. Multi-sig (e.g. Squads Protocol) can be used for shared authority.

Credential revocation

SAS attestations support revocation natively. The issuing authority can close or revoke an attestation at any time. On the next verification check, fetchMaybeAttestation() returnsexists: false and the payment claim is rejected. No phone calls, no manual registry updates.

Program upgrade path

  • The Anchor program is currently deployed as upgradeable on Devnet (standard Anchor default)
  • For production, the upgrade authority can be transferred to a multi-sig or frozen entirely
  • Account structures (ProgramConfig, CaseFile) include a bump field for forward-compatible PDA derivation
  • New instructions can be added without breaking existing PDAs: Anchor’s discriminator model ensures backward compatibility
  • SAS schema is immutable once deployed: new fields require a new schema version