Skip to main content

Architecture

NullGraph is a fully on-chain protocol built on Solana with zero backend infrastructure. All state lives in Program Derived Addresses (PDAs), all logic executes in an Anchor program, and all reads happen via JSON-RPC.

System overview

Data flow

  • Writes go through the Solana Wallet Adapter. The user signs transactions in Phantom, which are submitted to the Anchor program.
  • Reads use getProgramAccounts RPC calls through the Anchor client. There is no backend server, database, or indexer.
  • BIO transfers use CPI transfer_checked to the SPL Token Interface, which validates the mint and decimal precision.
No off-chain infrastructure means no single point of failure, no database migrations, and no hosting costs.

On-chain program

The Anchor program lives in a single file (programs/nullgraph/src/lib.rs, ~593 lines). It uses Anchor 0.31.1 with anchor-spl for SPL Token Interface CPI and associated token accounts.

Program ID

Deployed to Solana Devnet at 2u3DXQq9A6UgMryeVSWCNdYLy3Fjh391R5hcfWYkCgZK.

Account architecture

NullGraph uses four main account types, all implemented as Program Derived Addresses:

1. ProtocolState (Singleton)

Global configuration storing auto-incrementing counters, fee rate, and treasury address. PDA Seeds: ["protocol_state"]
The singleton pattern ensures a single source of truth for protocol configuration and prevents counter collision.

2. NullResult (One per NKA)

Stores all metadata for a Null Knowledge Asset. PDA Seeds: ["null_result", researcher_pubkey, specimen_number_le_bytes]
The data_hash field stores a SHA-256 fingerprint of any attached data file. The file stays off-chain; the hash provides a tamper-proof link.

3. NullBounty (One per bounty)

Represents a bounty posted by a BioDAO or researcher. PDA Seeds: ["null_bounty", creator_pubkey, bounty_number_le_bytes]
Vault Seeds: ["bounty_vault", bounty_pda_key]
Bounty funds are escrowed immediately on creation. The vault token account uses the vault PDA itself as authority, ensuring funds can only be released via program CPI.
Created when a researcher submits their NKA to a bounty. PDA Seeds: ["bounty_submission", bounty_pda_key, null_result_pda_key]

Instructions

The program exposes six instructions:

initialize_protocol(fee_basis_points)

One-time setup. Creates the ProtocolState singleton with counters at zero and the given fee rate.
Emits: ProtocolInitialized { authority, fee_basis_points }

submit_null_result(...)

Mints a new NKA. Increments nka_counter, creates the NullResult PDA, and records all fields.
Emits: NullResultSubmitted { specimen_number, researcher }

create_bounty(description, reward_amount, deadline)

Creates a NullBounty PDA + vault, transfers BIO from the creator’s token account to the vault.
Validation: Requires reward_amount > 0
Emits: BountyCreated { bounty_number, creator, reward_amount, deadline }

submit_to_bounty()

Links a researcher’s NKA to an open bounty. Creates a BountySubmission PDA and transitions the bounty to Matched.
Security: Only the NKA’s owner can call this (enforced via has_one constraint)
Emits: BountySubmissionCreated { bounty_number, specimen_number, researcher }

approve_bounty_submission()

Bounty creator approves the submission. Transfers reward - fee BIO to the researcher and fee BIO to the treasury via vault CPI.
Fee calculation:
Emits: BountyFulfilled { bounty_number, specimen_number, researcher, payout, fee }

close_bounty()

Bounty creator reclaims escrowed BIO from any Open or Matched bounty. Vault balance returns to creator, bounty set to Closed.
Emits: BountyClosed { bounty_number, creator, refunded_amount }

Error codes

Frontend architecture

A React 19 SPA built with Vite 7.3 and Tailwind CSS v4. Connects directly to Solana devnet with no backend server.

Provider stack

The ProgramProvider exposes a single Anchor Program instance constructed from the compiled IDL:

Routes

Data hooks

All data fetching uses custom React hooks that wrap program.account.<type>.all():
  • useProtocolState() - Fetches the singleton ProtocolState
  • useNullResults() - Fetches all NullResult accounts
  • useBounties() - Fetches all NullBounty accounts
  • useBountySubmissions(filters) - Fetches BountySubmission accounts (filterable by bounty or researcher)
All hooks expose { data, loading, error, refetch }.
No indexer required — the frontend fetches all accounts on page load. For production scale, consider using a Solana indexer like Helius or GenesysGo.

Transaction hooks

Transaction hooks derive PDAs, build instructions, send transactions, and show toast notifications:
  • useSubmitNullResult() - Mints a new NKA
  • useCreateBounty() - Creates a bounty with escrowed BIO
  • useSubmitToBounty() - Links an NKA to a bounty
  • useApproveBountySubmission() - Approves a submission and pays out
  • useCloseBounty() - Closes a bounty and refunds escrow
Example from useSubmitNullResult:

Design system

Dark-mode cyberpunk aesthetic defined as Tailwind v4 @theme tokens:
  • Background: #060810 with a 40px CSS grid overlay
  • Surfaces: #0d1017 (surface), #161c2a (raised), #232840 borders
  • Accents: Neon cyan (#5ec4de), magenta (#c8836a), lime (#62b862)
  • Typography: Cabinet Grotesk (display), Satoshi (body), Space Grotesk (mono/data)
  • Effects: CRT scanline animation, SVG noise overlay, glass card hover lifts with colored glow shadows

Security model

On-chain security

  • Signer checks on every write instruction via Anchor struct-level enforcement
  • PDA ownership — all data accounts are program-owned PDAs
  • has_one constraints ensure only the correct wallets can perform actions
  • Status guards validate current status before every state mutation
  • Replay protection — PDA init constraints prevent duplicate accounts
  • Vault authority — vault token accounts use the vault PDA itself as authority
  • Safe arithmetic — all fee/payout math uses checked_mul, checked_div, checked_sub
  • Transfer validation — all BIO transfers use transfer_checked, validating mint and decimal precision
Never use transfer for SPL tokens — always use transfer_checked to prevent decimal/mint confusion attacks.

Frontend security

  • No private keys handled. All signing delegated to Phantom.
  • Client-side file hashing via Web Crypto API (crypto.subtle.digest).
  • Input bounds match on-chain field sizes (128/256 chars via maxLength).

Tech stack summary

On-chain

Frontend

Solana client libraries

What’s next?

Now that you understand the architecture, explore: