GitLedger

Build Document · v0.1-ALPHA · May 2026

GitLedger
Protocol Overview

Your review has skin in the game. Staked, on-chain code review accountability on Base L2.

BASE L2EAS ATTESTATIONSx402 PROTOCOLCHAINLINK FUNCTIONS

00 — Abstract

Build Document · v0.1-ALPHA

GitLedger is a GitHub App that transforms PR code reviews into staked EAS attestations on Base L2. When a reviewer approves a pull request, they lock USDC via Coinbase Agentic Wallet into an escrow smart contract. A Chainlink Function monitors the repo for 30 days. If a hotfix targeting the merged code is detected, the stake is slashed. If the window closes clean, the reviewer earns yield. Every outcome is written as a permanent EAS attestation on Base — building a verifiable, monetizable reputation record queryable by hiring tools, DeFi protocols, and AI agents via x402. Core mechanic: approve code that breaks production → you pay. Approve code that ships clean → you earn.

01 — Overview & Thesis

Concept

What is GitLedger?

GitLedger is a GitHub App that transforms PR code reviews into staked EAS attestations on Base L2. When a reviewer approves a pull request, they lock USDC via Coinbase Agentic Wallet into an escrow contract. A Chainlink Function monitors the repo for 30 days. If a hotfix targeting the merged code is detected, the stake slashes. If the window closes clean, the reviewer earns yield. Every outcome is written as a permanent EAS attestation on Base — building a verifiable, monetizable reputation record queryable by any hiring tool, DeFi protocol, or AI agent via x402.

The Problem

Code review is the last line of defense before production — but reviewers carry zero financial accountability. A January 2026 study found agent-generated code introduces more technical debt per change than human code, yet reviewers feel better approving it. More than one in five GitHub code reviews now involve an AI agent. GitHub Copilot alone processed 60 million reviews growing 10x in under a year. Volume is outpacing human oversight. GitLedger fixes the incentive layer: approve code that breaks production and you pay. Approve code that ships clean and you earn.

Ecosystem Integration

GitHub AppWebhook on pull_request_review events. One-click install, zero workflow disruption.
Agentic Wallet (CDP)MPC-secured wallet, gasless USDC on Base. Launched Feb 11, 2026. Session spend caps.
EAS on BaseEthereum Attestation Service — permanent, revocable, onchain reviewer track record.
x402 ProtocolHTTP-native payment protocol. Reviewer reputation sold per-query. No API key needed.
Chainlink FunctionsDecentralized bug oracle. Polls GitHub API, detects hotfix PRs, fires slash/yield.
Basenamesname.base.eth as reviewer identity across profiles, attestations, and leaderboard.
OnchainKitCoinbase's React library — Identity, Avatar, Name, Transaction components on Base.

02 — Protocol Mechanics

Economics + Logic

Stake Lifecycle

StepActorActionResult
1ReviewerSubmits Approved on GitHub PRGitHub webhook fires to GitLedger
2BackendPrompts reviewer to stake USDCCDP Agentic Wallet popup in browser
3ReviewerConfirms stake (above repo minimum)USDC transferred to escrow on Base
4ContractLocks stake, starts 30-day windowEmits StakeLocked(reviewer, prId, amount)
5ContractCalls EAS.attest() with schemaAttestation UID stored in mapping
6ChainlinkUpkeep polls GitHub every 24hDetects qualifying hotfix PRs
7aOracleHotfix detected within windowslashReview() — 70% reporter, 30% treasury
7bOracleWindow closed, no hotfixreleaseYield() — stake + yield to reviewer

Reputation Score Formula

Base Score500 on first EAS attestation minted
Clean Review+5 points per clean 30-day window
High-Stake Clean+10 points if stake was ≥ $500 USDC
Slash−50 points per slash event
High-Stake Slash−100 points if staked ≥ $500 and slashed
Score Range0 to 1000. Decay: −1 per 30 days inactive. Score > 700 earns 1.5x yield.
Yield Ratetreasury_apy × elapsed_days / 365 × stake. APY target 12–24%.

03 — Smart Contracts

Solidity 0.8 · Base L2

GitLedger.sol — Core Logic

soliditysource
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract GitLedger {
    IEAS    public immutable eas;
    IERC20  public immutable usdc;
    bytes32 public immutable schema;
    address public immutable chainlinkOracle;

    uint256 public constant ORACLE_WINDOW   = 30 days;
    uint256 public constant REPORTER_SHARE  = 7000; // 70% bps
    uint256 public constant TREASURY_SHARE  = 3000; // 30% bps

    struct StakeRecord {
        address reviewer;
        bytes32 basename;       // keccak256("name.base.eth")
        string  repoSlug;
        uint256 prId;
        uint256 amount;
        uint256 stakedAt;
        bytes32 attestationUID;
        StakeState state;
    }

    enum StakeState { Active, Slashed, Released }

    mapping(bytes32 => StakeRecord) public stakes;     // keccak256(reviewer + prId)
    mapping(bytes32 => uint256)     public reputation; // basename => score
    address public treasury;

    event StakeLocked(bytes32 stakeId, address reviewer, uint256 amount);
    event ReviewSlashed(bytes32 stakeId, address reporter, uint256 reporterAmt);
    event YieldReleased(bytes32 stakeId, address reviewer, uint256 total);

    function stakeReview(
        bytes32 basename, string calldata repoSlug, uint256 prId, uint256 amount
    ) external returns (bytes32 stakeId) {
        usdc.transferFrom(msg.sender, address(this), amount);
        stakeId = keccak256(abi.encodePacked(msg.sender, prId));
        stakes[stakeId] = StakeRecord(msg.sender, basename, repoSlug, prId,
            amount, block.timestamp, bytes32(0), StakeState.Active);
        bytes32 uid = eas.attest(buildAttestation(basename, repoSlug, prId, amount, "ACTIVE"));
        stakes[stakeId].attestationUID = uid;
        emit StakeLocked(stakeId, msg.sender, amount);
    }

    function slashReview(bytes32 stakeId, address reporter) external onlyOracle {
        StakeRecord storage s = stakes[stakeId];
        require(s.state == StakeState.Active);
        s.state = StakeState.Slashed;
        uint256 rAmt = (s.amount * REPORTER_SHARE) / 10000;
        usdc.transfer(reporter, rAmt);
        usdc.transfer(treasury, s.amount - rAmt);
        reputation[s.basename] = reputation[s.basename] >= 50
            ? reputation[s.basename] - 50 : 0;
        eas.revoke(s.attestationUID);
        eas.attest(buildAttestation(s.basename, s.repoSlug, s.prId, s.amount, "SLASHED"));
        emit ReviewSlashed(stakeId, reporter, rAmt);
    }

    function releaseYield(bytes32 stakeId) external onlyOracle {
        StakeRecord storage s = stakes[stakeId];
        require(s.state == StakeState.Active);
        require(block.timestamp > s.stakedAt + ORACLE_WINDOW);
        s.state = StakeState.Released;
        uint256 yld = computeYield(s);
        usdc.transfer(s.reviewer, s.amount + yld);
        reputation[s.basename] = min(reputation[s.basename] + 5, 1000);
        eas.revoke(s.attestationUID);
        eas.attest(buildAttestation(s.basename, s.repoSlug, s.prId, s.amount, "CLEAN"));
        emit YieldReleased(stakeId, s.reviewer, s.amount + yld);
    }

    function computeYield(StakeRecord storage s) internal view returns (uint256) {
        uint256 elapsed = block.timestamp - s.stakedAt;
        uint256 base    = (s.amount * 1800 * elapsed) / (365 days * 10000); // 18% APY
        uint256 mult    = reputation[s.basename] > 700 ? 15000 : 10000;
        return min((base * mult) / 10000, s.amount * 3);
    }

    modifier onlyOracle() { require(msg.sender == chainlinkOracle); _; }
    function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; }
}

04 — EAS Attestation Schema

Ethereum Attestation Service

Schema String (registered on Base)

soliditysource
string public constant SCHEMA =
    "bytes32 basename,"          // keccak256(name.base.eth)
    "string  repoSlug,"          // 'org/repo'
    "uint256 prId,"              // GitHub PR number
    "uint256 stakeAmount,"       // USDC 6-decimal units
    "string  verdict,"           // ACTIVE | CLEAN | SLASHED
    "uint256 reviewedAt,"        // block.timestamp
    "uint256 resolvedAt,"        // 0 if still active
    "uint256 reputationDelta,"   // +5, -50 etc.
    "string  repoLanguages";     // 'Solidity,TypeScript'

TypeScript Attestation Call

tssource
import { EAS, SchemaEncoder } from '@ethereum-attestation-service/eas-sdk';

const eas = new EAS(EAS_CONTRACT_BASE); eas.connect(signer);
const encoder = new SchemaEncoder(SCHEMA_STRING);

const encoded = encoder.encodeData([
  { name: 'basename',        value: keccak256(toUtf8Bytes('vitalik.base.eth')), type: 'bytes32' },
  { name: 'repoSlug',        value: 'uniswap/v4-core',           type: 'string'  },
  { name: 'prId',            value: 4821n,                        type: 'uint256' },
  { name: 'stakeAmount',     value: 500_000_000n,                 type: 'uint256' },
  { name: 'verdict',         value: 'ACTIVE',                     type: 'string'  },
  { name: 'reviewedAt',      value: BigInt(Math.floor(Date.now()/1000)), type: 'uint256' },
  { name: 'resolvedAt',      value: 0n,                           type: 'uint256' },
  { name: 'reputationDelta', value: 0n,                           type: 'uint256' },
  { name: 'repoLanguages',   value: 'Solidity,TypeScript',        type: 'string'  },
]);

const tx  = await eas.attest({
  schema: SCHEMA_UID,
  data: { recipient: reviewerAddress, revocable: true, data: encoded },
});
const uid = await tx.wait();

Attestation States

StateTriggerEAS ActionRevocable
ACTIVEstakeReview() calledattest() — UID stored onchainYes
CLEANOracle: window passedrevoke(uid) + re-attest verdict=CLEANYes
SLASHEDOracle: hotfix foundrevoke(uid) + re-attest verdict=SLASHEDNo

05 — x402 Paywall API

Reviewer Reputation as a Service

Reviewer reputation is exposed behind an x402 HTTP paywall — 0.001 USDC per query, settled on Base. No API key, no account. Any client presents a signed payment header and receives the full attestation history. AI agents, hiring tools, and DeFi protocols all use the same endpoint.

Endpoint Flow

httpsource
// 1. Client hits endpoint — receives 402
GET /api/reviewer/vitalik.base.eth
HTTP/1.1 402 Payment Required
X-Payment-Requirements: {
  "scheme": "exact", "network": "base-mainnet",
  "maxAmountRequired": "1000",
  "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "payTo": "0xGitLedger...Treasury",
  "description": "GitLedger reviewer reputation query"
}

// 2. Client signs payment and retries with X-PAYMENT header
// 3. Server verifies + settles, returns full profile
HTTP/1.1 200 OK
{
  "basename": "vitalik.base.eth",
  "score": 847, "percentile": 94, "accuracyRate": 0.993,
  "cleanCount": 282, "slashCount": 2,
  "totalYield": "2184000000",
  "attestations": [{ "uid": "0x...", "repo": "uniswap/v4-core",
    "pr": 4821, "verdict": "CLEAN", "stake": 500000000 }]
}

Next.js x402 Middleware

tssource
// src/app/api/reviewer/[basename]/route.ts
import { withPaymentRequired } from '@coinbase/x402-next';

export const GET = withPaymentRequired(
  async (req, { params }) => {
    const { basename } = params;
    const profile = await db.query.reviewers.findFirst({
      where: eq(reviewers.basename, basename),
      with: { attestations: { limit: 50, orderBy: desc(attestations.createdAt) } }
    });
    return profile ? Response.json(formatProfile(profile)) : Response.json({}, { status: 404 });
  },
  { amount: '1000', asset: USDC_BASE, network: 'base-mainnet' }
);

06 — Backend Spec

Bun + tRPC + Drizzle
RuntimeBun 1.x — native TypeScript, fast webhook throughput, built-in test runner
APItRPC v11 — end-to-end type safety, WebSocket for live attestation feed
ORMDrizzle ORM — typed queries, TimescaleDB hypertable for events
CacheRedis 7 — webhook dedup 30s TTL, rate limiting, x402 receipt cache
ChainViem v2 — Base L2 RPC, contract writes, EAS SDK integration
GitHubGitHub App (Octokit) — webhook listener for pull_request_review events
WalletCoinbase CDP SDK — stake collection, USDC transfers, gasless on Base
AuthDynamic.xyz JWT + Basename verification for reviewer dashboard

tRPC Router

tssource
export const appRouter = router({
  reviewer: router({
    profile:     publicProcedure.input(z.string()).query(getReviewerProfile),
    leaderboard: publicProcedure.query(getLeaderboard),
    myStakes:    protectedProcedure.query(getMyStakes),
  }),
  attestation: router({
    byReviewer: publicProcedure.input(z.string()).query(getAttestationsByReviewer),
    byRepo:     publicProcedure.input(z.string()).query(getAttestationsByRepo),
    live:       publicSubscription.subscription(liveAttestationFeed),
  }),
  stake: router({
    initiate: protectedProcedure.input(stakeSchema).mutation(initiateStake),
    status:   protectedProcedure.input(z.string()).query(getStakeStatus),
    history:  protectedProcedure.query(getStakeHistory),
  }),
  repo: router({
    install:  webhookProcedure.mutation(handleGitHubInstall),
    settings: protectedProcedure.input(z.string()).query(getRepoSettings),
  }),
});

GitHub Webhook Handler (simplified)

tssource
app.post('/webhooks/github', async (req, res) => {
  if (!verifyGH(req.headers['x-hub-signature-256'], req.rawBody)) return res.status(401).end();

  const dedup = 'gh:' + req.headers['x-github-delivery'];
  if (await redis.exists(dedup)) return res.status(200).send('dup');
  await redis.set(dedup, 1, 'EX', 30);

  const { review, pull_request, repository } = req.body;
  if (req.headers['x-github-event'] === 'pull_request_review'
      && req.body.action === 'submitted'
      && review.state === 'approved') {

    const repo = await db.query.repos.findFirst({
      where: eq(repos.slug, repository.full_name)
    });

    if (repo?.stakeEnabled) {
      await queue.add('prompt-stake', {
        reviewerId: review.user.login,
        repoSlug:   repository.full_name,
        prId:       pull_request.number,
        minStake:   repo.minStakeUsdc,
      });
    }
  }
  res.status(200).send('ok');
});

07 — Frontend Spec

Next.js 14 + OnchainKit
RouteComponentDescription
/LandingPageHero, live feed, metrics, how-it-works, CTA
/reviewer/[basename]ReviewerProfilePublic EAS history, score, accuracy, yield earned
/dashboardMyDashboardActive stakes, pending windows, yield history
/dashboard/stake/[prId]StakeFlowStake confirmation, Agentic Wallet prompt, EAS preview
/exploreLeaderBoardTop reviewers by score, yield, accuracy, language
/repo/[slug]RepoReviewsAll staked reviews on a repo. Codebase trust score.
/api/reviewer/[basename]x402 Handler0.001 USDC per query via x402 middleware

OnchainKit Identity Components

tsxsource
// ReviewerCard.tsx
import { Identity, Name, Avatar, Badge, Address } from '@coinbase/onchainkit/identity';
import { base } from 'viem/chains';

export function ReviewerCard({ address, stakeAmount, verdict }) {
  return (
    <Identity address={address} chain={base} schemaId={GL_EAS_SCHEMA}>
      <Avatar />
      <Name />     {/* Resolves name.base.eth */}
      <Address />  {/* Shortened 0x... fallback */}
      <Badge />    {/* Coinbase verification badge */}
    </Identity>
  );
}

// StakeTransaction.tsx
import { Transaction, TransactionButton } from '@coinbase/onchainkit/transaction';

export function StakeTransaction({ amount, prId, basename, repoSlug }) {
  const contracts = [{
    address: GITLEDGER_ADDRESS, abi: GitLedgerABI,
    functionName: 'stakeReview',
    args: [basename, repoSlug, BigInt(prId), parseUnits(amount, 6)],
  }];
  return (
    <Transaction contracts={contracts} chainId={8453}>
      <TransactionButton text={"Stake $" + amount + " USDC"} />
    </Transaction>
  );
}

09 — Database Schema

Drizzle + TimescaleDB
tssource
// src/db/schema.ts
export const repos = pgTable('repos', {
  id:           uuid('id').primaryKey().defaultRandom(),
  slug:         text('slug').unique().notNull(),
  ghInstallId:  bigint('gh_install_id', { mode: 'number' }),
  minStakeUsdc: integer('min_stake_usdc').default(10_000_000),
  stakeEnabled: boolean('stake_enabled').default(true),
  installedAt:  timestamp('installed_at').defaultNow(),
});

export const reviewers = pgTable('reviewers', {
  address:          text('address').primaryKey(),
  basename:         text('basename').unique(),
  githubLogin:      text('github_login').unique(),
  reputationScore:  integer('reputation_score').default(500),
  totalStakedUsdc:  numeric('total_staked_usdc').default('0'),
  totalYieldUsdc:   numeric('total_yield_usdc').default('0'),
  totalSlashedUsdc: numeric('total_slashed_usdc').default('0'),
  cleanCount:       integer('clean_count').default(0),
  slashCount:       integer('slash_count').default(0),
  languages:        text('languages').array(),
  lastActiveAt:     timestamp('last_active_at'),
  createdAt:        timestamp('created_at').defaultNow(),
});

export const stakes = pgTable('stakes', {
  id:              uuid('id').primaryKey().defaultRandom(),
  stakeId:         text('stake_id').unique().notNull(),
  reviewerAddr:    text('reviewer_addr').notNull(),
  repoId:          uuid('repo_id').references(() => repos.id),
  prId:            integer('pr_id').notNull(),
  prTitle:         text('pr_title'),
  amountUsdc:      integer('amount_usdc').notNull(),
  state:           text('state').default('active'),
  attestationUid:  text('attestation_uid'),
  txHashStake:     text('tx_hash_stake'),
  txHashResolve:   text('tx_hash_resolve'),
  yieldEarned:     integer('yield_earned').default(0),
  windowEndsAt:    timestamp('window_ends_at'),
  stakedAt:        timestamp('staked_at').defaultNow(),
  resolvedAt:      timestamp('resolved_at'),
});

// TimescaleDB hypertable
// SELECT create_hypertable('attestation_events', 'time');
export const attestationEvents = pgTable('attestation_events', {
  time:           timestamp('time').defaultNow(),
  reviewerAddr:   text('reviewer_addr'),
  repoSlug:       text('repo_slug'),
  prId:           integer('pr_id'),
  eventType:      text('event_type'),
  amountUsdc:     integer('amount_usdc'),
  attestationUid: text('attestation_uid'),
  txHash:         text('tx_hash'),
});

10 — GitHub App Spec

Octokit + Webhook
App NameGitLedger — Staked Code Reviews
Permissionspull_requests: read, issues: read, metadata: read
Webhook Eventspull_request_review, pull_request (merged), installation
Install FlowOAuth → repo select → min-stake config → enable. Stores install ID in DB.
Review HookOn approved → queue stake-prompt → notify reviewer via browser notification
Merge HookOn PR merged → register Chainlink upkeep for the stakeId
Bot CommentPosts to approved PR: GitLedger: stake $X USDC to back this review [Stake Now]
Slash CommentOn slash: bot comments: Slash triggered — reviewer staked $X, $Y paid to reporter

11 — Sprint Roadmap

6-Week Delivery
SprintDaysFocusDescription
Sprint 11–3Chain FoundationGitLedger.sol + EAS schema on Base Sepolia. Slash/yield flow. Chainlink Function script. Viem client.
Sprint 24–6GitHub AppGitHub App registration. Webhook handler for pull_request_review. Bot comment system. Install flow + repo config DB.
Sprint 37–9Stake FlowCoinbase Agentic Wallet + CDP integration. Stake UI. USDC transfer. EAS attestation mint. Session keys.
Sprint 410–12Oracle IntegrationChainlink Automation upkeep deploy. performUpkeep + fulfillRequest. Slash/yield trigger. End-to-end testnet test.
Sprint 513–15FrontendNext.js: Landing, Reviewer Profile, Dashboard, StakeFlow, Leaderboard. OnchainKit Identity + Transaction.
Sprint 616–18x402 + Launchx402 paywall middleware on /api/reviewer. Reputation monetization. Security audit. Base Mainnet deploy.

12 — Monetization

Revenue Model
Repo Subscriptions$29/mo Starter, $79/mo Pro, $299/mo Enterprise. Funds treasury yield pool.
x402 Query Revenue0.001 USDC per reputation query. No API key. Paid by hiring tools, agents, protocols.
Slash Protocol Fee30% of every slashed stake retained in treasury. Substantial at scale.
Premium FeaturesCustom oracle windows, multi-reviewer staking pools, private repo support.
LEDGER TokenFuture: governance + 2x yield multiplier. Distributed to early stakers.

Revenue Projection — Year 1

MetricQ1Q2Q3Q4
Repos installed5002,0006,00014,000
Reviews staked/day803501,2003,000
Avg stake (USDC)$45$60$80$100
Slash rate8%7%6%5.5%
Repo subscriptions$14.5K$58K$174K$406K
x402 query revenue$200$1.5K$6K$18K
Slash fees (30%)$300$1.7K$7.7K$22K
TOTAL MONTHLY~$15K~$61K~$188K~$446K

13 — Environment & Infra

Secrets + Deployment

Environment Variables

envsource
# CHAIN
BASE_RPC_URL=https://mainnet.base.org
BASE_SEPOLIA_RPC=https://sepolia.base.org
GITLEDGER_DEPLOYER_PK=0x...
GITLEDGER_CONTRACT=0x...
EAS_CONTRACT_BASE=0x4200000000000000000000000000000000000021
EAS_SCHEMA_UID=0x...

# COINBASE CDP
CDP_API_KEY_NAME=...
CDP_API_KEY_PRIVATE_KEY=...

# CHAINLINK
CL_FUNCTIONS_ROUTER=0xf9B8fc078197181C841c296C876945aaa425B278
CL_FUNCTIONS_SUBSCRIPTION_ID=...
CL_DON_ID=fun-base-mainnet-1
CL_AUTOMATION_REGISTRY=0x...
CL_GITHUB_TOKEN_SECRET=...

# GITHUB APP
GITHUB_APP_ID=...
GITHUB_APP_PRIVATE_KEY=-----BEGIN RSA...
GITHUB_WEBHOOK_SECRET=...

# DATABASE
DATABASE_URL=postgresql://gitledger:...@db:5432/gitledger
REDIS_URL=redis://cache:6379

# SERVICES
NEXT_PUBLIC_DYNAMIC_ENV_ID=...
NEXT_PUBLIC_GITLEDGER_CONTRACT=0x...
X402_TREASURY_ADDRESS=0x...

Deployment Stack

FrontendVercel — Next.js 14, edge runtime for x402 routes, preview deploys on PR
BackendRailway — Bun process, auto-scale, TimescaleDB (Postgres 16) + Redis add-ons
ContractsFoundry + forge deploy to Base Sepolia then Mainnet. Verified on Basescan. 2/3 multisig owner.
ChainlinkFunctions subscription on Base mainnet. Automation upkeep via registry. DON: fun-base-mainnet-1.
CI/CDGitHub Actions: forge test + lint + deploy contracts + deploy API + deploy frontend. Under 10 min.
MonitorSentry errors, Datadog APM, Basescan event alerts for slashes, Chainlink upkeep health dashboard.

© 2026 GitLedger · Build Document v0.1-ALPHA · Internal · Subject to change

View Roadmap ↗