Skip to content
Architecture · Web3 · DeSci · Agentic Systems

When the Chain Is Not Enough: Building a Full-Stack Agentic DApp

A DeSci peer-review case study, told as a user journey where every step in the workflow introduces the piece of the stack that makes it possible.

SBSiegfried BozzaUpdated 24 min read

Companion piece to the BioVerify project page.

Peer review is often opaque, reviewers are rarely paid, and conflicts of interest are hard to see. I wanted to find out which parts of it could be made verifiable and fairly paid.

I built BioVerify: a full-stack agentic DApp where authors stake ETH on their research, reviewers are selected by Chainlink VRF, the submission and its final verdict are content-addressed on IPFS, and AI agents coordinate the rest, with people writing the reviews and Gemini or a senior reviewer setting the verdict. You can try it on the live demo (Base Sepolia and Ethereum Sepolia, no local setup), and the architecture and source are open on GitHub.

This is a case study in on-chain coordination: the chain records what happened, and agents orchestrate what happens next. One agent key still settles every outcome, so it is not yet trust-minimised. Having spent fifteen years in Molecular Biology and Civil/Environmental Engineering before software, and having been on the author side of peer review myself, I had a specific reason to care. Rather than describe the stack in the abstract, this article follows the system as a user experiences it, and introduces each piece of technology precisely at the moment the story needs it.

"Truth on-chain, orchestration off-chain, with humans involved in verdict calls."

Why decentralised peer review, and why now

Peer review is a coordination game with a trust deficit: opaque processes, inconsistent conflict disclosure, and reviewers who are rarely compensated. Blockchain cannot fix science, but it can make parts of the process verifiable: stakes and outcomes settle on-chain, reviewers sign their reviews, which are checked off-chain, and for one specific subproblem (drawing reviewers at random) Chainlink VRF attaches a cryptographic proof to the randomness so the draw can be audited.

BioVerify is an experiment in the coordination patterns that matter here: long-running workflows, randomly selected participants from a staked pool, human-in-the-loop delays, economic accountability, and settlement back on-chain.

TL;DR
  • What it is: a DeSci peer-review case study with stakes, verifiable reviewer selection, AI screening, signed human reviews and on-chain settlement.
  • CQRS as a first principle: the contract emits events (writes); an off-chain Postgres projection serves the reads of publications and members. The chain is truth, not a database.
  • Durable coordination: Inngest starts each agent run and retries it if it fails (all of the screening, and the review agent up to its first pause); LangGraph interrupt() pauses for human signatures, and Postgres checkpointers persist graph state between resumes, which run in a server action with no retry.
  • Human-in-the-loop: human reviewers sign the peer verdicts and the senior reviewer has the final word after an escalation; an agent records and settles them. The plagiarism screen, the call to escalate and the verdict when there is no escalation are Gemini's.

Author flow

Submitting a publication

Choosing a network

An author wishing to submit their work first chooses the network: Base Sepolia or Ethereum Sepolia. BioVerify deploys BioVerifyV3 independently on both testnets, each with its own contract address, reward and slash pools, and reviewer pool. The same Solidity source is deployed to both networks, producing the same ABI on each; only per-chain parameters such as the Chainlink VRF coordinator and subscription differ. The two networks are hardcoded in the app, though: adding a chain means a new deployment plus changes in several places.

Building the submission: IPFS and content addressing

The author fills a form with the components of their research: title, abstract, authors, body content, and any supporting files. Before anything touches the blockchain, these components go to IPFS, the InterPlanetary File System.

IPFS addresses files by content, not location: a CID (Content IDentifier) is derived from a cryptographic hash of the content. Change a single byte and the CID changes, so a CID recorded on-chain is a commitment to exactly those bytes.

BioVerify uses Pinata as the IPFS pinning service. The title, abstract, body and each attached file are uploaded individually, each with its own CID. The server then pins a research manifest, a JSON object with those CIDs plus the authors and the license. The manifest's CID becomes the single identifier for the whole submission.

Calling the contract: stake and submission fee

With the manifest CID in hand, the author calls submitPublication(cid) on the BioVerifyV3 contract, with one payment that covers two amounts:

  • A stake. The author puts skin in the game. If the publication is rejected for plagiarism or fails peer review, this stake is slashed. If it passes, the stake is returned plus a reputation boost. That alignment makes the incentive structure legible: the author is incentivised to submit only non-plagiarised work and only work they believe will pass review.
  • A submission fee. It funds Chainlink VRF (Verifiable Random Function). On-chain, the fee is msg.value minus the fixed publisher stake; callers may overpay so spikes in gas or VRF costs stay covered. When pickReviewers runs, the contract forwards paidSubmissionFee into the VRF subscription via fundSubscriptionWithNative, so each submission that passes screening tops up the oracle budget. An early-slashed submission never reaches that call: its fee stays in the contract, outside both pools, and nothing in the normal flow moves it. The frontend estimates that fee from the current gas price and a fixed gas budget, and sends the larger of that and the minimum the contract enforces.

The contract records the author's address, the publication CID, and the paid submission fee, transitions the publication to SUBMITTED status, and emits SubmitPublication.


Infrastructure

From on-chain event to off-chain intelligence

Getter-light contract: CQRS as a first principle

BioVerifyV3 is deliberately getter-light. It emits a granular event for every state transition and avoids exposing view functions for UI queries. This is CQRS (Command Query Responsibility Segregation) applied at the chain boundary: the contract is the write model; an off-chain Postgres projection is the read model.

The contract benefits: smaller bytecode means cheaper deployment and a smaller surface to audit (every write emits events, so writes cost a little more gas, not less). The frontend benefits: the UI reads a fast Postgres projection rather than orchestrating dozens of eth_call requests against the chain (the chain is not built for product lists), while Drizzle queries filter the list by network and status and page through it, newest first. The chain is the source of truth; Postgres reflects that truth and is the source of responsiveness, lowering UI latency.

Alchemy Notify: from block to webhook

Alchemy Notify is Alchemy's webhook service. For each network, BioVerify configures one webhook, filtered in Alchemy's dashboard to the deployed BioVerifyV3 address, so Alchemy POSTs a signed payload containing the event logs that contract emitted in each new block; the route itself does not re-check which contract emitted a log. Two such webhooks run in parallel, one for Base Sepolia and one for Ethereum Sepolia, both reacting to the same set of BioVerifyV3 events and both POSTing to the Next.js API route at /api/webhooks/alchemy/all-events.

That route runs as a Vercel serverless function. The first thing it does is verify the webhook's HMAC-SHA256 signature: Alchemy and the server share a per-network secret key, and any payload that does not match is rejected immediately. This prevents spoofed events from triggering agent actions or corrupting the read model.

The Neon Postgres read model: OCC projection

Once the signature is verified, the raw log is decoded with viem and dispatched to processContractEvent(), the core projector in the @packages/cqrs layer. This function translates on-chain events into typed updates and writes them into Neon Postgres.

The correctness mechanism is Optimistic Concurrency Control (OCC): each event carries a position stamp from the blockchain, its block number and log index. The projector only applies an update if the incoming event is more recent than what it has already stored. Late webhook deliveries can repeat; they cannot roll the read model backward. A viem WebSocket subscription to NewPublicationStatus invalidates the matching TanStack Query keys about three seconds after the event is mined (a delay that gives the webhook time to update Postgres), so open lists update without polling.

Optimistic concurrency (OCC)
Webhooks can replay or arrive out of chronological order; that comes with networked indexers and retries. BioVerify attaches a deterministic ordering stamp from the chain, the block number plus log index, to each upsert. An update applies only when the inbound event sorts strictly newer than what is already stored. Duplicates converge idempotently; stale deliveries cannot rewind fresher projections. There is one stamp per row, though, so a late event that touches a different column is dropped, and a projection that fails after the webhook's 200 is lost.
Figure 1. Event pipeline: from contract event to read model and agents
Figure 1. Event pipelineBioVerifyV3· writes· emits granular eventsAlchemy Notifycontract-scoped · signed webhookNext.js serverless handlerHMAC-SHA256 verify → decode → routeNeon Postgres· reads· OCC on (block, logIndex)Inngest· starts, retries runs· wraps LangGraph agentstriggered for 2 events only:· SubmitPublication· Agent_PickReviewersUI read model· fast filtered queries· no eth_call fan-out
Every event from the contract goes through the same webhook: verified, decoded and, for sixteen of the nineteen, projected to Postgres. Only two specific contract events also dispatch an Inngest event that wraps a LangGraph agent.
Event families on BioVerifyV3
Pools: RewardPool, SlashPool; members / stakes: RewardMember, SlashMember, IsAvailableReviewer, MemberReputation, MemberAvailableStake, MemberLockedStake, MemberLockedStakeOnPubId, Claim; publications: SubmitPublication, LockedStakeOnPubId, NewPublicationStatus; agent + VRF: Agent_RequestVRF, Agent_PickReviewers, Agent_RecordReview, Agent_FinalizePublication, treasury moves (Agent_TransferSlashPoolToTreasury, Agent_MoveSlashPoolToRewardPool). The projector in @packages/cqrs folds sixteen of them into three Drizzle-backed tables: protocol, member, publication. Claim, MemberLockedStakeOnPubId and Agent_RequestVRF are not projected.

Why Inngest, and why only two events trigger agents

Almost every BioVerify contract event exists to update the Postgres read model. Only two events also mean new long-running off-chain work must begin: SubmitPublication kicks off plagiarism screening of the submission's abstract; Agent_PickReviewers means VRF has finished, reviewers for that submitted publication have been picked, and human verdict collection must start. Everything else (stake movements, status transitions after settlement, treasury plumbing) is bookkeeping the projector already knows how to fold into Drizzle tables.

Those two moments map to Inngest events CHAIN_SUBMISSION_RECEIVED (submission agent) and CHAIN_PICKED_REVIEWERS_RECEIVED (review agent). Each handler wraps a LangGraph invocation inside an Inngest step.run.

Why Inngest: Vercel serverless functions have tight per-invocation time limits and no guaranteed in-memory state between invocations. Inngest starts each agent run and retries it if it fails. The screening agent runs entirely inside one step.run, so a retry re-runs all of it, and it still has to finish within one function call. The review agent runs there only until its first interrupt(); every later step, settlement included, runs in the server action that resumes it, with no retry. The commands read state before writing where they can. It is not the mechanism that waits days for a reviewer: that pause lives in LangGraph.

Why LangGraph: Human review needs real suspension inside the graph: interrupt() stops execution until a verified EIP-712 payload arrives, while the Postgres checkpointer remembers partial progress (who already reviewed, whether escalation opened). When a Next.js server action resumes the thread with Command({ resume }), the checkpointer reloads that state so the agent continues without replaying completed nodes.

Recap: what has happened so far
  • Author submits: manuscript uploaded to IPFS, CID + stake + submission fee recorded on-chain
  • Contract emits SubmitPublication → Alchemy Notify → Next.js webhook (HMAC-verified)
  • Projector writes to Neon Postgres read model with OCC (no event can roll the state back)
  • Inngest triggers the Submission Agent via a wrapped LangGraph call

Agent 1: Submission screening

Plagiarism check, then pick or slash

The submission agent runs a linear sequence of nodes, with no branching until the very end, which keeps the logic easy to follow. Here is what it does, step by step.

Fetch from IPFS. The agent fetches the manifest by its CID, then the abstract it points to. This is the same content the author uploaded, addressed by its CID. The agent fetches it through the Pinata gateway without re-hashing it, so this relies on trusting the gateway.

Plagiarism check with Exa. The agent passes the abstract to Exa AI, a neural web search API, limited to its "research paper" category. Exa returns up to five similar papers, each with its text and a relevance score.

LLM synthesis with Gemini. The raw Exa results are passed to a Gemini language model, which synthesises the similarity signals and produces a structured verdict inside the graph: pass (continue to review) or fail (plagiarism). The LLM node itself never touches the chain: no contract calls happen inside that step. The output is schema-constrained; after the graph finishes, agent-start.ts maps pass/fail to exactly one CQRS command path.

Branch: plagiarism detected → earlySlashPublication(). On fail, the agent invokes earlySlashPublicationCommand: the AI verdict is pinned to IPFS, then earlySlashPublication(pubId, verdictCid) is called through the agent's immutable wallet. The contract slashes the author's stake and transitions the publication to EARLY_SLASHED. The workflow ends here for this publication.

Branch: clean → pickReviewers(). On pass, pickReviewersCommand calls pickReviewers(pubId) on the contract. This triggers the Chainlink VRF request, where the randomness story begins.

Access control: agent-gated transitions

Only the agent's immutable wallet address, set once at contract deployment and unchangeable afterwards, can call earlySlashPublication, pickReviewers, recordReview, publishPublication, slashPublication, and pool-management moves guarded by onlyAgent. Arbitrary callers cannot trigger those transitions: the contract checks msg.sender against the immutable agent address.

Figure 2. Submission agent: linear screening → branch to earlySlash or pickReviewers
Figure 2. Submission agentSUBMISSION AGENT (Inngest + LangGraph)1. Fetch IPFSretrieve manifestby CID2. Exa plagiarismsemantic web search3. Gemini LLMschema-constrainedverdict4. DecisionearlySlash orpickReviewersplagiarismearlySlashPublication()cleanpickReviewers()
The submission agent's nodes run in sequence; only the final command branches. Schema-constraining the LLM output ensures only two valid on-chain actions are possible.

Demo: AI plagiarism detection and early slashing. Dual-device view: User A (left, mobile, no wallet) on /publications; User B (right, tablet, wallet on Base Sepolia).

Step 1. User B submits on Base Sepolia
User B submits a publication that duplicates existing literature.
Step 2. Realtime list → EARLY_SLASHED
User A sees the row appear in real time over WebSocket, with no wallet and no refresh. The publication ends at EARLY_SLASHED with the AI verdict loaded from IPFS.

Verifiable randomness

Verifiable reviewer selection via Chainlink VRF

The reviewer pool: anyone can join by staking

Any user can register as a reviewer by depositing a reviewer stake into the contract. This stake is the reviewer's skin in the game: if a reviewer delivers a verdict that is later deemed negligent (more on this below), their stake is slashed. Staking also puts a price on Sybil attacks: each fake reviewer account has to keep a reviewer stake deposited to stay in the pool (0.001 test ETH on the current deployments), and VRF only draws a handful of reviewers per publication, so the probability that any given fake account is drawn falls as the honest pool grows.

Why VRF, and why it matters when stakes are real

Blockchains are deterministic. Any randomness derived from public on-chain fields (blockhash, block.timestamp) is predictable or influenceable in principle, which is not enough when selection has economic consequences.

Chainlink VRF v2.5 follows the usual hybrid smart-contract pattern: random words are produced off-chain together with a cryptographic proof; the Chainlink VRF coordinator verifies that proof on-chain before calling back the consumer contract. The draw is verifiable. Think of it as a public lottery that anyone can re-check afterward. One caveat: the contract owner can change the VRF coordinator, so the draw is only as neutral as the coordinator it points to (listed under known edge cases).

In BioVerify's flow: the agent calls pickReviewers() → the contract calls requestRandomWords() → Chainlink fulfills via fulfillRandomWords() → the callback selects reviewers from the staked pool and emits Agent_PickReviewers. That event re-enters the same event pipeline (Alchemy Notify, the HMAC-verified webhook, the Neon Postgres projection), closing the loop through the same webhook and projector.

Figure 3. Chainlink VRF: verifiable reviewer selection
Figure 3. Chainlink VRFAgentpickReviewers()BioVerifyV3requestRandomWords()CHAINLINK VRFRandom words+ cryptographic proofBioVerifyV3fulfillRandomWords()Agent_PickReviewers event → pipeline
Random words come back with a cryptographic proof; the VRF coordinator verifies it on-chain, then calls back BioVerifyV3, which draws reviewers from the staked pool.

Demo: Submitting a publication (success path). Split view: BioVerify Telegram bot (left) and DApp (right).

Step 1. Fill the publication form
The author fills in the publication form and prepares to submit; on submit, the app pins the form's content to IPFS as a manifest.
Step 2. Submitted → In Review
The author confirms the on-chain transaction. The bot posts status notifications as the publication moves from SUBMITTED to IN_REVIEW.
Step 3. VRF reviewers on the publication detail
The publication detail page shows IN_REVIEW and the Chainlink VRF–selected reviewers.

Agent 2: Human-in-the-loop review

Verdicts, escalation, and settlement

After projection, Agent_PickReviewers fires CHAIN_PICKED_REVIEWERS_RECEIVED into Inngest, which kicks off the second LangGraph agent. This agent is the most complex part of the system: it manages a workflow that can pause for days, handles human escalation, and settles the publication on-chain.

Human-in-the-loop (HITL): what pausing actually means

HITL is wired directly into the LangGraph itself. When the review workflow reaches humanReviewsNode, the graph calls interrupt() and stops. The Inngest step that wrapped the agent invocation finishes normally (the work is paused, not blocked), and LangGraph's Postgres checkpointer persists the partial graph state in Neon. Days later, when a reviewer signs their verdict, a Next.js server action verifies the EIP-712 payload and calls resumeReviewersAgent, which issues Command({ resume }). LangGraph reloads the thread state from the checkpointer, applies the new review, records recordReview on-chain once the payload checks out, and continues until the next interrupt or settlement.

How reviewers submit verdicts: EIP-712 gasless signing

Reviewers are not push-notified by the system: they check the reviewer dashboard at /publications/assignments, find the publication they have been assigned to review, and step through the flow from there. They read the manuscript and submit their verdict without paying gas for the review transaction itself. Instead, they sign using EIP-712: typed structured data that yields a standard cryptographic signature without broadcasting a transaction.

The signed payload is posted to the backend off-chain. After viem verifies the signature and confirms the signer is one of the reviewers drawn for this publication, the agent calls recordReview(pubId, reviewer) through the agent wallet. On-chain, that call only records that this reviewer has submitted their review for this publication; the qualitative verdict stays in LangGraph until settlement encodes honest versus negligent addresses.

A Telegram bot also posts an alert to one chat at each step (screening, review and settlement, on both networks); the split-view demos show it.

Peer reviewers, senior reviewers, and the escalation path

BioVerify distinguishes two reviewer roles within a publication cycle (current Base Sepolia and Ethereum Sepolia deployments draw I_VRF_NUM_WORDS = 3 candidates so there are two explicit peers plus one senior; the senior is the drawn reviewer with the highest on-chain reputation, or the first one drawn on a tie):

  • Peer reviewers. The non-senior members of the VRF draw. They submit independent pass/fail verdicts, and Gemini reads them and returns pass, fail or escalate. When it settles (publishPublication on pass, slashPublication on fail), each peer whose verdict matches Gemini's is honest and gets their stake back, the reviewer reward and a reputation bump; a peer who disagrees is negligent and is slashed. The senior reviewer is not asked.
  • Senior reviewer. Drawn alongside peers but designated as the highest-reputation reviewer of the three (the first drawn on a tie). If Gemini escalates (its prompt asks for that when the peers split or one flags a fraud risk the others missed, but the code does not force it), the senior reviewer signs a binding verdict: the code enforces it, and Gemini only rewrites the reasoning. Each peer whose verdict matches the senior's is honest; a peer who disagrees is negligent and is slashed (stake to the slash pool, reputation drop). Settlement always counts the senior as honest, with or without escalation: their locked stake unlocks and they get the reviewer reward for standing by as the escalation authority, so the senior is never slashed.

Possible outcomes and what triggers settlement

The review agent settles the publication once the binding verdict is established: Gemini's verdict or, after escalation, the senior reviewer's. It calls one of two contract functions through the agent address:

  • publishPublication(pubId, honest, negligent, verdictCid) → PUBLISHED. The publisher's locked stake unlocks back into availableStake and their reputation increases. Every address listed as honest (peers who matched the decision plus the senior reviewer) unlocks stake, receives the configured reviewer reward, and gains reputation. Negligent reviewers lose their locked stake to the slash pool and take a reputation penalty.
  • slashPublication(pubId, honest, negligent, verdictCid) → SLASHED. The publisher is slashed the same way as other failure paths. Honest reviewers, including those who caught the failure, still receive stake, reward, and reputation; negligent reviewers are slashed.

Settlement deliberately uses a pull payment pattern: instead of pushing ETH to every participant inside the settlement transaction, where one malicious or buggy receive() hook could revert and grief the entire cohort (a denial-of-service against honest actors), the contract moves value into internal balances and lets each party call claim() on their own schedule. Gas isolates per claimant; failures stay local.

Figure 4. Review agent: HITL, escalation, and settlement
Figure 4. Review agentREVIEW AGENT (Inngest + LangGraph, HITL)LangGraph rehydrates statefrom Neon Postgres checkpointerHuman-in-the-loop pauseLangGraph interrupt(): step or action returnsResume via Command({ resume }) when reviewer signs EIP-712 (may take days)Gemini verdictpass, fail or escalateescalateSenior reviewerbreaks tie → bindingpass / failBinding verdictpass or failfailslashPublication()passpublishPublication()
Inngest starts the review agent and retries that first run; later reviews resume it from a Next.js server action, with no retry. LangGraph owns the human pauses: the checkpointer restores state after each interrupt() so the graph knows both what it last knew and what must happen next.

Demo: Peer review with human-in-the-loop conflict resolution. Two peer reviewers return conflicting verdicts; Gemini escalates, the senior reviewer breaks the tie, and settlement flips the publication to PUBLISHED.

Step 1. First peer reviewer: PASS (EIP-712 signature)
The first peer reviewer submits a pass verdict, signed off-chain with EIP-712; the agent then records on-chain that this reviewer has reviewed.
Step 2. Second peer reviewer: FAIL (EIP-712 signature)
The second peer reviewer submits a fail verdict, also signed off-chain with EIP-712. The two reviews now conflict; Gemini reads both and decides whether to escalate.
Step 3. Senior reviewer breaks the tie (PASS)
Split view: Telegram bot (left) and senior reviewer (right). The bot shows both peer reviews recorded and the agent's decision to escalate. The senior reviewer fills in a tie-breaking pass.
Step 4. PUBLISHED with the verdict resolved from IPFS
After the senior review, Telegram reports that the publication passed review, and the detail page shows PUBLISHED with the final verdict resolved from IPFS.
Recap: full lifecycle
  • Author uploads to IPFS → calls submitPublication with stake + submission fee
  • SubmitPublication → pipeline → Neon DB + Inngest → Submission Agent
  • Submission Agent: IPFS fetch → Exa plagiarism check → Gemini verdict → earlySlashPublication or pickReviewers
  • pickReviewers → Chainlink VRF → Agent_PickReviewers → the same webhook + projector pipeline → Review Agent
  • Review Agent: LangGraph interrupt() while reviewers sign EIP-712 payloads → Gemini verdict or escalation → publishPublication / slashPublication
  • Settlement: rewards honest actors and/or slashes negligent ones; pull payments credit internal balances on-chain, and each party calls claim() themselves

Incentives: making honesty the rational choice

The economic mechanics aim to make careful reviewing pay: a reviewer who matches the binding verdict gets their stake back plus a reward, and one who does not is slashed. The contract never sees the reviews; it applies the outcome and the honest and negligent lists the agent sends.

OutcomeTriggerPublisherHonest reviewersNegligent reviewers
Early SlashedPlagiarism detected pre-reviewStake slashed · rep penaltyNone selectedN/A
PublishedFinal verdict: passStake returned · rep boostStake + reward + rep boostStake slashed · rep penalty
SlashedFinal verdict: failStake slashed · rep penaltyStake + reward + rep boostStake slashed · rep penalty
Honesty means matching the binding verdict (Gemini's, or the senior's after escalation), pass or fail. The senior reviewer is always classified as honest at settlement.

Honesty means matching the binding verdict, whichever way it goes. Without escalation, Gemini's verdict is binding: each peer who matches it is rewarded, and a peer who disagrees is negligent. After escalation, the senior reviewer's verdict is binding: each peer who matches it is honest, and each peer who does not is negligent. The senior is always credited as honest at settlement so their standby stake and reward settle cleanly. The system is intentionally simple, with clear limitations: without escalation, Gemini's reading of the reviews decides who is slashed, and after escalation a wrong or compromised senior reviewer gets the final word. The roadmap replaces the senior tie-break with voting weighted by on-chain reputation; Gemini's role without escalation has no planned fix yet.


Security as a first-class concern

claim and transferSlashPoolToTreasury, the two functions that send ETH to members or the treasury, follow the CEI pattern (Checks–Effects–Interactions) and carry OpenZeppelin nonReentrant. pickReviewers stores the VRF request ID after calling the coordinator, because the coordinator returns it.

Pull payments. As described above, pushing ETH to every recipient in one transaction would concentrate griefing risk: the contract itself becomes the caller, and a single reverting receive() could deny service to the entire cohort. Crediting internal balances plus user-initiated claim() transfers isolates failure modes and caps settlement gas for the agent.

Contract test coverage. BioVerifyV3 has 50 Foundry tests in 12 suites, across 11 test files; forge coverage on 24 October 2026 shows 100% of the lines (240/240), branches (37/37) and functions of src/BioVerifyV3.sol. VRF flows use VRFCoordinatorV2_5Mock with deterministic fulfillment overrides and log assertions, including edge cases such as colliding random words. Only the contract has tests; the agents, the read model and the frontend have none.


The pattern generalises

Peer review is the domain BioVerify is grounded in, but the coordination pattern is not specific to science. The same problems show up wherever multiple mutually untrusting actors need a shared process, with verifiable selection, long-running human-in-the-loop steps, and on-chain settlement.

I have not validated these. They are domains where I see the same structural ingredients (verifiable selection, long-lived HITL, on-chain settlement) and think the coordination pattern could apply:

  • Legal arbitration: verifiable evidence submission, randomly selected arbitrators from a staked pool, durable multi-week deliberation, tamper-evident record of proceedings.
  • Insurance claims: multi-assessor processing where each step needs an audit trail and assessors have economic skin in the game.
  • DAO governance: proposal review with randomly drawn delegates, slashing for negligent voters, durable agent coordination across asynchronous human input.
  • Supply chain compliance: multi-party certification workflows where documents need content-addressed storage and auditor selection needs to be bias-resistant.
These domains share a verifiable draw, stakes and long human pauses. In BioVerify the draw is verifiable; the verdict still rests on the agent.

What this build is, and what it is not

BioVerify is a case study, not a finished product. The current build covers the optimistic happy paths. The hardening backlog and roadmap include:

  • Less trust in the agent: separate the agent, the treasury and the owner, and record each review's signature on-chain so the contract can check who voted what.
  • Author escalation path: today a plagiarism false positive from the submission agent (or a peer-review verdict the author believes is wrong) is terminal; settlement runs immediately and the author has no contractual recourse. The roadmap addresses this with an opt-in escalation: the author posts a larger escalation stake within a bounded window, which triggers a second review cycle restricted to humans only, drawn from a fresh VRF cohort that excludes the original reviewers. The second verdict is binding and reconciles the first: confirmed means an additional slash; overturned means prior rewards and slashes are reversed and recomputed. This implies that terminal settlement effects must be deferred or reversible until the escalation window closes.
  • Weighted majority voting: replaces the single-senior-reviewer tie-break with reputation-weighted consensus, so no single reviewer has the final word.
  • ZK reputation via Reclaim Protocol: lets reviewers prove off-chain credentials (for example h-index or affiliation) without doxxing themselves, enabling credentialed review without exposing identity.
  • Encrypted access (Lit Protocol) and monetisation (x402): manuscript confidentiality during review; micropayments to open datasets and supplementary material, closing the privacy and monetisation gaps.
  • Internal corpus + RAG (Neon + pgvector): Exa searches research papers for text close to the abstract; an internal pgvector index would compare submissions with BioVerify's own past publications.
  • Known edge cases: empty or malformed IPFS payloads can leave a publication stuck in SUBMITTED; after the review agent's first pause, nothing is retried, so if a later step or the settlement transaction fails, for example on a gas spike, the publication stays IN_REVIEW. pickReviewers checks status != IN_REVIEW instead of == SUBMITTED, so the agent could re-open a finished publication; if the reviewer pool shrinks or rewards run out between the VRF request and the callback, the callback reverts and the publication stays IN_REVIEW; and linear probing on collisions slightly biases the draw. The webhook compares its HMAC with ===, not in constant time, and the projection follows the chain tip with no confirmation depth. One agent key, which is also the treasury and the owner on both deployments, settles every outcome, and as owner it can change the VRF coordinator. Reviewers also have no deadline: one who never answers leaves the paper IN_REVIEW, with every stake locked. The README covers the rest, including agent transactions that use simulate-then-write with no gas bump or nonce recovery.

Tech stack at a glance

  • Blockchain & contracts: Foundry, Solidity, Chainlink VRF v2.5
  • Infrastructure: Alchemy Notify (contract-scoped webhooks), Vercel serverless functions
  • Agents & orchestration: LangGraph (graph state + HITL interrupts), Inngest (starts and retries the agent runs that chain events trigger; the review agent only up to its first pause)
  • Data & storage: Neon Postgres (CQRS read model + LangGraph checkpointer), Drizzle ORM, IPFS via Pinata
  • LLM & search: Gemini, Exa AI
  • Frontend: Next.js, TypeScript, wagmi, viem, Reown AppKit, TanStack Query, TanStack Table, nuqs, EIP-712 signing (checked on the server)
  • Schema validation: Zod

Closing

What drew me into this build was the interplay between two layers with distinct responsibilities: the chain as the Truth layer, where stakes settle, agent-gated transitions execute and VRF proofs are verified, and agents as the Orchestration layer, where multi-actor, multi-day workflows play out.

BioVerify is the case study I used to wire that idea end-to-end. The interesting work lives in the seams: between Inngest and LangGraph, between Alchemy webhooks and the OCC projector, and between server-checked EIP-712 reviews and the settlement the agent sends on chain. It also lives in the contract incentives: tuning staking, rewards, and slashing so that long-running review cycles stay economically coherent over days. That is what I want to keep building.

The contracts are deployed on Base Sepolia and Ethereum Sepolia, and the source is open.

The repo has the full architecture doc and a live demo.

The chain records what happened. Agents coordinate what happens next. People write the reviews, and when Gemini escalates, a senior reviewer decides.