ProofScore is a GenLayer-native, evidence-settled builder bounty protocol: reputation that settles rewards. A sponsor locks a GEN reward pool and sets a minimum score. Builders submit public evidence identifiers, the contract computes a canonical score, and only an accepted qualifying submission unlocks a reward claim. Counter-evidence challenges remain in the on-chain audit trail without persisting validator-variable prose.
This is an evidence-backed reputation assessment, not identity verification. A submitted profile URL does not prove that a wallet owns the profile.
- Campaign: a sponsor creates a payable campaign and deposits its reward pool in wei.
- Evidence: a builder submits source URLs and contextual notes.
- Canonical score: validated GitHub, X/Twitter, portfolio, additional evidence, and bounded notes receive fixed weights. Qualification requires GitHub or portfolio plus the threshold and every exact requirement token.
- Claim reward:
QUALIFIEDplusscore >= thresholdmakes the builder eligible.claim_rewarddeducts the fixed reward from escrow and schedules the external GEN transfer for finalization. - Challenge: anyone can record bounded counter-evidence. Before claim, only the campaign creator can apply exact invalidation tags that deterministically recompute score and eligibility. Post-claim challenges cannot claw funds back.
One builder wallet can submit only once per campaign. GitHub evidence must use https://github.com/...; X evidence must use https://x.com/... or https://twitter.com/....
Campaign requirements use only these exact lowercase tokens: [requires:github], [requires:x], [requires:portfolio], and [requires:additional]. Other requirement prose remains descriptive and does not silently create mandatory categories.
Score-affecting creator challenges use only these exact lowercase tags: [invalid:github], [invalid:x], [invalid:portfolio], [invalid:additional], [invalid:duplicate], and [invalid:irrelevant]. Category tags remove their points; duplicate and irrelevant tags force INVALID.
See Architecture, Deployment, and Review response.
Payable write:
create_campaign(title, description, threshold_score, reward_per_qualified_builder, deadline, evidence_requirements)
Writes:
submit_builder_profile(campaign_id, handle, github_url, x_url, portfolio_url, additional_evidence_url, notes)claim_reward(campaign_id, submission_id)challenge_score(campaign_id, submission_id, challenge_url, reason)close_campaign(campaign_id)
Read-only views return JSON strings immediately and do not change state:
get_campaign,list_campaignsget_submission,list_submissionsget_challenge,list_challengesget_stats,get_leaderboard,list_top_scores
writeContract returns a transaction hash immediately; it does not mean state changed. The app and smoke script wait for ACCEPTED before reading accepted state. ACCEPTED is always labeled “finalization pending.” External EOA payout/refund messages execute on finalization, so accepted claim state says SCHEDULED_FOR_FINALIZATION, not “paid.” Wait for FINALIZED before relying on final settlement.
Payable value is denominated in wei (1 GEN = 10¹⁸ wei), and create_campaign is decorated @gl.public.write.payable.
npm install
cp .env.example .env.local
npm run devThe default contract address is the zero address and disables live reads/writes. After a separately authorized deployment, set NEXT_PUBLIC_PROOFSCORE_V9_ADDRESS. There is deliberately no fallback to an earlier deployment.
npm run lint
npm run build
npm audit
PYTHONPYCACHEPREFIX=/private/tmp/proofscore-pycache python3 -m py_compile contracts/*.py
git diff --check
git status --shortnpm run deploy:v9readsGENLAYER_DEPLOYER_PKfrom the environment and deploys the v9 source. It never prints the key.npm run diagnose:v9 -- 0x…prints compact receipt and execution-trace failure fields without dumping contract calldata.npm run smoke:v9requiresGENLAYER_DEPLOYER_PKandPROOFSCORE_V9_ADDRESS, safely finds or creates a funded campaign, waits for accepted-state visibility, submits evidence, confirms canonical score state, and claims only if eligible. SetSMOKE_CAMPAIGN_IDto continue exactly one existing campaign and disable creation. Challenge submission is opt-in withSMOKE_CHALLENGE=1.
Neither script retries a write after receiving a transaction hash. The deploy script exposes an address only after FINISHED_WITH_RETURN and a successful get_stats read. The smoke script backs off on transient Bradbury reads, polls all campaign views after acceptance, reuses one matching campaign on continuation, and fails explicitly on UNDETERMINED / NO_MAJORITY.