Skip to content

Repository files navigation

PatchProof

PatchProof is a private bug-reporting and pre-funded bounty workflow built on Monad, where report contents stay offchain while their integrity, submission time, review outcome, and reward accounting are proven onchain.

Quick links

The problem

As a solo builder, I wanted a practical way to reward people who find bugs without asking them to publish vulnerability details or trust a spreadsheet to prove when they reported an issue. Small teams need a clear submission, triage, and payout trail, but most established bounty operations are too heavy for a project that is still finding its first testers.

Public blockchains are useful for proof and payments, but they are the wrong place for raw vulnerability details. A useful system has to keep the report private while still giving the reporter evidence that a specific submission existed at a specific time.

The solution

PatchProof separates the private report from its public proof. A project owner launches and funds a bounty in native MON. A bug hunter submits a report through the web app; the browser creates a random 32-byte salt and a commitment to the canonical report content. Only that commitment is written to the verified V3 contract.

The report and salt are stored in an access-controlled Turso record. The server returns private content only to the authenticated reporter or the campaign's current onchain owner. During review, the owner decides whether the report is Accepted, Duplicate, or Rejected. Only an accepted report receives an owner-assigned final severity and the matching preconfigured reward. The hunter then claims the credited MON directly from the contract.

Bug details remain offchain and access-controlled, while their integrity and submission time are proven onchain.

How it works

  1. Launch a bounty. The project owner sets the testing scope, deadlines, severity rewards, and initial funding.
  2. Report a bug. A hunter submits factual report details. The private content stays offchain; its commitment is timestamped on Monad.
  3. Review reports. The owner reads the private report and marks it Accepted, Duplicate, or Rejected.
  4. Assign severity. Acceptance requires the owner to select Low, Medium, High, or Critical. The corresponding funded reward is reserved atomically.
  5. Claim the reward. The hunter withdraws accumulated claimable MON with a separate transaction.

Reports are shown as Pending triage until the owner reviews them. The reporter cannot select or influence severity. Reports left Pending after the review deadline receive no reward and do not permanently lock the campaign's remaining funds.

What is stored where

Layer Data
Monad V3 contract Campaign owner, deadlines, funding, severity rewards, report commitment, review result, final severity, remaining balance, claimable rewards
Turso/LibSQL Campaign descriptions, testing scope, private canonical reports, salts, authenticated sessions, campaign event index and cursor
Reporter's browser Canonicalizes the report, creates the random salt and commitment, signs the login nonce, and submits wallet transactions

The contract never receives raw reproduction steps, expected behavior, environment details, or evidence links. Conversely, the database cannot decide who owns a campaign or how much reward is claimable: those decisions are read from the verified contract. Every private record is bound to its chain, contract, campaign, reporter, owner, commitment, and confirmed transaction.

This split gives each side one clear responsibility. Monad provides durable proof and escrow accounting. The application provides access-controlled context that should not be published on a public ledger.

Why Monad

A complete bounty flow requires several user-facing transactions: campaign funding, submission proof, owner review, and reward claim. Monad Testnet provides fast confirmations and inexpensive execution, so proof and escrow updates can remain onchain without making a small bounty cumbersome. Native MON is both the campaign funding asset and the reward asset.

PatchProof uses Monad for more than a deployment badge: the application reads real contract events, verifies confirmed transaction receipts, derives campaign ownership from the contract, and uses onchain accounting for every credited and claimed reward.

Demo flow

The intended demo uses two Monad Testnet wallets:

  1. Connect the project wallet and launch a funded bounty.
  2. Open the shareable campaign page with a second wallet.
  3. Submit a private report and confirm that it appears as Pending triage with no assigned severity.
  4. Return as the project owner, review the report, assign a final severity, and accept it.
  5. Return as the hunter and claim the credited MON reward.
  6. Open the explorer links to verify creation, submission time, review, and payout without exposing the report details.

Verified Monad Testnet deployment

Item Value
Network Monad Testnet (10143)
V3 contract 0x80E3C7CAa882e783B4ce8Fd74Ff8f84765dff8f4
Deployment transaction 0x73f9f2dba04d9ab54b8ba78688fc8ee1b09ac26e56d2774e1f811c9d7342cfef
Deployment block 44844762
Source verification Verified on Sourcify

The frontend reads the address, chain ID, deployment block, and ABI from the version-controlled files in deployments/. The V3 contract is the only active PatchProof deployment used by the application.

Architecture

  • Verified V3 contract: stores campaign terms, report commitments, review outcomes, remaining campaign balances, and claimable rewards.
  • Next.js application: provides the campaign directory, shareable campaign pages, private reporting, owner triage, and reward claiming flows.
  • Monad RPC: supplies live contract state and CampaignCreated events. The resumable index cursor is persisted rather than held in server memory.
  • Turso/LibSQL: stores public campaign descriptions, private report content and salts, authentication nonces, opaque sessions, and campaign index state.
  • Wallet authentication: a short-lived EIP-4361 nonce signature creates an HttpOnly session. Contract ownership remains authoritative for private access.

The chain is the source of truth for campaign existence, ownership, deadlines, funding, review outcomes, and rewards. Missing offchain campaign metadata never hides an onchain campaign; the interface falls back to Campaign #N.

Real campaign discovery

The campaign directory is not seeded with placeholder cards or hardcoded IDs. It begins at the version-controlled V3 deployment block, discovers real CampaignCreated events, stores an idempotent cursor in Turso, and re-reads each campaign's current contract state. The cursor survives serverless restarts and overlapping scans remain deduplicated. Newly confirmed campaigns are indexed as part of the creation success path instead of waiting for a historical scan.

The public Monad Testnet RPC limits eth_getLogs ranges, so the server uses bounded 100-block ranges and JSON-RPC batching. A resumable production backfill can restore the complete event index without copying local data or creating fake campaigns. Transient RPC failures leave previously indexed campaigns available and expose a Retry state rather than presenting stale data as a successful refresh.

Integrity and authorization boundary

Campaign metadata is persisted only after the API validates the confirmed CampaignCreated receipt, event ID, owner, metadata identifier, contract address, and current onchain campaign. Private reports receive the same receipt and event validation before storage. Reads then require a nonce-signature session whose wallet matches the reporter or current onchain campaign owner.

The browser generates the salt, but it does not get to invent a successful onchain result. The application waits for confirmed receipts and checks emitted events before showing creation, submission, review, or claim success.

Detailed contract behavior, schemas, security properties, indexer design, deployment history, smoke transactions, and commands are in docs/TECHNICAL.md.

Technology

  • Solidity 0.8.28, EVM target prague
  • OpenZeppelin Contracts 5.6.1
  • Hardhat 3, Ignition, TypeScript, and Viem
  • Next.js 16, React 19, Wagmi 3, and Viem
  • LibSQL locally and Turso for hosted persistence
  • Monad Testnet and native MON rewards

Node.js 22 or newer is required. The project was developed with Node.js 24 and runs natively on Windows; WSL is not required.

Run locally

git clone https://github.com/Seqo01/PatchProof.git
cd PatchProof
npm ci
copy .env.example .env
npm run dev

Open http://localhost:3000. An injected EIP-1193 wallet such as MetaMask is required for signatures and contract writes. Without hosted database variables, development uses the ignored local data/patchproof.db LibSQL file.

Production requires these server-side environment variable names:

TURSO_DATABASE_URL
TURSO_AUTH_TOKEN
APP_ORIGIN

Optional RPC overrides are MONAD_RPC_URL for the server and NEXT_PUBLIC_MONAD_RPC_URL for the browser. Deployment and the two-wallet smoke test use PRIVATE_KEY; the smoke test additionally uses TESTER_PRIVATE_KEY. Values belong only in the ignored local .env, Hardhat keystore, or hosting provider settings. Never commit them. TURSO_AUTH_TOKEN and private keys must never use a NEXT_PUBLIC_ prefix.

Useful checks:

npm run compile
npm test
npm run test:coverage
npm run typecheck
npm run lint
npm run format:check
npm run build:web

The repository includes Solidity contract tests, TypeScript product and persistence tests, canonical commitment tests, deadline and accounting edge cases, wallet/session tests, campaign-event discovery tests, and a two-wallet Monad Testnet smoke script. The smoke flow uses the version-controlled V3 ABI and deployment record rather than a manually copied address.

For hosted persistence, the idempotent migration and validation commands verify tables, primary keys, indexes, nonce consumption, opaque session expiry, metadata upserts, event deduplication, and cursor resume behavior:

npm run db:migrate
npm run db:backfill:campaigns
npm run db:validate:turso

The migration does not upload data/patchproof.db or insert demo records. The backfill stores only events emitted by the verified V3 contract. See the technical reference for smoke transactions and deployment/verification commands.

Trust model and MVP limitations

  • The campaign owner is the sole reviewer. There is no arbitration, dispute resolution, oracle, or admin override.
  • Private reports are access-controlled, not end-to-end encrypted. The service/database operator is inside the trust boundary.
  • Pending reports do not reserve funds. Rewards become protected from owner withdrawal only when a report is accepted.
  • Reporter address, campaign ID, timestamps, commitment, review result, and any final severity are public. Report contents and salts remain offchain.
  • Rewards use native MON. ERC-20 rewards are out of scope.
  • The contract is immutable and has no protocol admin, upgrade path, or emergency recovery role.
  • This hackathon implementation has not received an independent security audit.

PatchProof is a hackathon MVP. Before handling valuable real-world reports or funds, it would need an independent contract and application review plus formal backup, retention, monitoring, and incident-response policies.

About

Private bug reports, onchain proof, and pre-funded rewards on Monad.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages