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.
- Live app: patch-proof.vercel.app
- Demo video: https://www.youtube.com/watch?v=3mL_7tLTyDs
- Monad Testnet contract:
0x80E3C7CAa882e783B4ce8Fd74Ff8f84765dff8f4 - Source verification: Verified on Sourcify
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.
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.
- Launch a bounty. The project owner sets the testing scope, deadlines, severity rewards, and initial funding.
- Report a bug. A hunter submits factual report details. The private content stays offchain; its commitment is timestamped on Monad.
- Review reports. The owner reads the private report and marks it Accepted, Duplicate, or Rejected.
- Assign severity. Acceptance requires the owner to select Low, Medium, High, or Critical. The corresponding funded reward is reserved atomically.
- 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.
| 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.
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.
The intended demo uses two Monad Testnet wallets:
- Connect the project wallet and launch a funded bounty.
- Open the shareable campaign page with a second wallet.
- Submit a private report and confirm that it appears as
Pending triagewith no assigned severity. - Return as the project owner, review the report, assign a final severity, and accept it.
- Return as the hunter and claim the credited MON reward.
- Open the explorer links to verify creation, submission time, review, and payout without exposing the report details.
| 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.
- 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
CampaignCreatedevents. 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.
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.
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.
- 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.
git clone https://github.com/Seqo01/PatchProof.git
cd PatchProof
npm ci
copy .env.example .env
npm run devOpen 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:webThe 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:tursoThe 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.
- 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.