diff --git a/DESIGN.md b/DESIGN.md
new file mode 100644
index 0000000..a59a95f
--- /dev/null
+++ b/DESIGN.md
@@ -0,0 +1,306 @@
+# PullPay β DESIGN.md (Frontend Design System)
+
+
+
+## 1. Principles
+
+- **Precise, editorial, engineered.** Tampilan developer-tool yang di-ship beneran (Linear / Vercel / Stripe / Rainbow), *bukan* template AI.
+- **Dark-first**, sediakan light theme.
+- **Restraint over decoration** β whitespace lega, border hairline 1px, grid tipografi ketat.
+- **Satu aksen** (indigo) dipakai hemat untuk 1 aksi utama per layar.
+- **Semua data on-chain = monospace + tabular numbers** (address, USDC, tx hash, rewardId).
+- **No** gradient wash, glassmorphism, neon, 3D blob, emoji sebagai ikon, atau lorem ipsum.
+
+## 2. Design tokens
+
+### 2.1 Warna
+
+| Token | Dark (default) | Light | Pakai untuk |
+| --- | --- | --- | --- |
+| `bg` | `#0E0F13` | `#FBFBFD` | Background halaman |
+| `surface` | `#181A20` | `#FFFFFF` | Kartu / panel |
+| `surface-2` | `#1F2229` | `#F4F5F7` | Hover / inset |
+| `border` | `rgba(255,255,255,0.07)` | `#E7E8EC` | Garis hairline 1px |
+| `text` | `#E6E7EB` | `#1A1B1F` | Teks utama |
+| `muted` | `#9498A3` | `#6B6F7A` | Teks sekunder / eyebrow |
+| `accent` | `#7C8CFF` | `#5A67E8` | Aksi utama, angka kunci |
+| `ok` (funded/paid) | `#4ADE80` | `#12B76A` | Status dot hijau |
+| `warn` (verifying) | `#E0B341` | `#D9A404` | Status dot amber |
+| `bad` (disputed) | `#F0715E` | `#E5533C` | Status dot merah |
+
+> Warna dipakai sebagai **sinyal kecil** (dot, pill, satu angka penting), bukan fill besar.
+>
+
+### 2.2 Tipografi
+
+- **UI + heading:** Geist (alt: SΓΆhne, Neue Haas Grotesk). Heading besar, `letter-spacing` rapat.
+- **Eyebrow:** uppercase, 11β12px, `letter-spacing` lebar, warna `muted`.
+- **Data on-chain:** Geist Mono (alt: IBM Plex Mono / JetBrains Mono), aktifkan `tabular-nums`.
+- **Uang:** selalu mono + tabular + chip mata uang kecil.
+
+### 2.3 Spacing, radius & motion
+
+- **Spacing:** skala 4px β `4 / 8 / 12 / 16 / 24 / 40`.
+- **Radius:** `10px` kartu, `6px` input/tombol. Hindari serba-pill.
+- **Border:** selalu 1px hairline (`border` token).
+- **Motion:** tenang, 150β250ms `ease-out`. Count-up angka, fade/blur-up saat reveal, magnet hover-lift di tombol primary. Tidak bouncy.
+
+## 3. Setup (copy ke repo)
+
+### 3.1 `app/globals.css`
+
+```css
+@import "tailwindcss";
+
+/* Dark-first: :root = dark. Light theme via [data-theme="light"]. */
+:root {
+ --bg: #0E0F13;
+ --surface: #181A20;
+ --surface-2: #1F2229;
+ --border: rgba(255, 255, 255, 0.07);
+ --text: #E6E7EB;
+ --muted: #9498A3;
+ --accent: #7C8CFF;
+ --accent-hover: #93A0FF;
+ --ok: #4ADE80;
+ --warn: #E0B341;
+ --bad: #F0715E;
+
+ --radius-card: 10px;
+ --radius-input: 6px;
+}
+
+[data-theme="light"] {
+ --bg: #FBFBFD;
+ --surface: #FFFFFF;
+ --surface-2: #F4F5F7;
+ --border: #E7E8EC;
+ --text: #1A1B1F;
+ --muted: #6B6F7A;
+ --accent: #5A67E8;
+ --accent-hover: #4C58D6;
+ --ok: #12B76A;
+ --warn: #D9A404;
+ --bad: #E5533C;
+}
+
+/* Tailwind v4 CSS-first theme mapping */
+@theme inline {
+ --color-bg: var(--bg);
+ --color-surface: var(--surface);
+ --color-surface-2: var(--surface-2);
+ --color-border: var(--border);
+ --color-text: var(--text);
+ --color-muted: var(--muted);
+ --color-accent: var(--accent);
+ --color-accent-hover: var(--accent-hover);
+ --color-ok: var(--ok);
+ --color-warn: var(--warn);
+ --color-bad: var(--bad);
+ --radius-card: var(--radius-card);
+ --radius-input: var(--radius-input);
+ --font-sans: var(--font-geist-sans);
+ --font-mono: var(--font-geist-mono);
+}
+
+body {
+ background: var(--bg);
+ color: var(--text);
+ font-feature-settings: "cv11", "ss01";
+}
+
+/* Angka & data on-chain */
+.tnum { font-feature-settings: "tnum" 1; font-variant-numeric: tabular-nums; }
+
+/* Latar dot-grid sangat samar (~3%) */
+.dot-grid {
+ background-image: radial-gradient(rgba(255,255,255,0.06) 1px, transparent 1px);
+ background-size: 22px 22px;
+}
+```
+
+> Kalau pakai **Tailwind v3**, pindahkan mapping `@theme` ke `tailwind.config.ts` di bawah `theme.extend.colors` (`bg: "var(--bg)"`, dst.) dan `borderRadius`.
+>
+
+### 3.2 Fonts β `app/layout.tsx`
+
+```tsx
+import type { Metadata } from "next";
+import { GeistSans } from "geist/font/sans";
+import { GeistMono } from "geist/font/mono";
+import "./globals.css";
+
+export const metadata: Metadata = { title: "PullPay" };
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+
{children}
+
+ );
+}
+```
+
+> `geist/font` sudah expose `--font-geist-sans` & `--font-geist-mono`, yang di-map ke `--font-sans` / `--font-mono` di `globals.css`.
+>
+
+## 4. Component conventions
+
+### 4.1 Buttons
+
+- **Primary** (1 per view): solid `accent`, teks gelap, radius `6px`, hover β `accent-hover` + lift halus.
+- **Ghost / Outline:** transparan, border hairline, teks `text`.
+- **Danger:** border/teks `bad`; solid `bad` hanya untuk aksi destruktif terkonfirmasi.
+
+```tsx
+// Contoh varian dengan class token
+
+
+
+```
+
+### 4.2 Status pill (+ dot)
+
+```tsx
+const STATUS = {
+ Open: { dot: "var(--muted)" },
+ Funded: { dot: "var(--ok)" },
+ "In Review":{ dot: "var(--accent)" },
+ Verifying: { dot: "var(--warn)" },
+ Paid: { dot: "var(--ok)" },
+ Disputed: { dot: "var(--bad)" },
+ Refunded: { dot: "var(--muted)" },
+ Rejected: { dot: "var(--bad)" },
+} as const;
+
+export function StatusPill({ status }: { status: keyof typeof STATUS }) {
+ const dotStyle = { background: STATUS[status].dot };
+ return (
+
+
+ {status}
+
+ );
+}
+```
+
+### 4.3 Cards & tables
+
+- Kartu: `bg-surface`, `border border-border`, `rounded-[10px]`, padding `16β24px`.
+- Tabel: **zebra-free**, pisahkan baris dengan hairline (`divide-y divide-border`), header sticky, angka rata kanan + `tnum`.
+
+### 4.4 Inputs
+
+- `bg-surface`, `border border-border`, `rounded-[6px]`, focus ring tipis `accent` (bukan glow tebal).
+- Segmented toggle (Instant / Safeguarded): 2 opsi, indikator aktif solid `accent` tipis.
+
+### 4.5 On-chain data & angka (helper)
+
+```tsx
+// lib/format.ts
+export const truncateAddr = (a: string) => `${a.slice(0, 6)}β¦${a.slice(-4)}`; // 0x9f2cβ¦a41b
+export const usd = (n: number) =>
+ n.toLocaleString("en-US", { style: "currency", currency: "USD" });
+```
+
+```tsx
+// Selalu mono + tnum untuk uang & hash
+{usd(20)} USDC
+{truncateAddr(hash)} β
+```
+
+## 5. Iconography
+
+- **Lucide** atau **Phosphor**, line, stroke 1.5px β **satu set saja**.
+- Ukuran default 16β18px, warna `muted` (aktif β `text`/`accent`).
+- Tidak ada emoji sebagai ikon UI.
+
+## 6. Motion & komponen animasi β ReactBits ([reactbits.dev](http://reactbits.dev))
+
+[reactbits.dev](http://reactbits.dev) adalah koleksi komponen React animasi (text effects, background, interaksi) yang di-**copy-paste / CLI** ke repo (bukan dependency berat), berbasis Tailwind + Framer Motion/GSAP. Di PullPay, ReactBits dipakai **hanya untuk aksen halus** yang memperkuat kesan *engineered* β bukan dekorasi. Prinsip Β§1 (restraint, 1 aksen, no wash/glow) tetap berlaku di atas ReactBits.
+
+### 6.1 Cara pakai
+
+- Tambah via `npx jsrepo add ...` atau copy manual ke `components/reactbits/`, lalu **re-style pakai token kita** (`accent`, `muted`, `.dot-grid`, radius) β buang warna neon/gradient bawaan.
+- Semua efek harus **hormati `prefers-reduced-motion`** (matikan animasi kalau user minta) dan pakai durasi motion Β§2.3 (150β250ms, `ease-out`, tidak bouncy).
+- **Maksimal ~2β3 efek ReactBits per layar.** Data on-chain (address, angka, hash) tetap **statik & terbaca** β hanya `CountUp` yang boleh menganimasikan angka.
+
+### 6.2 Komponen yang dipakai (dan penyesuaiannya)
+
+| Komponen ReactBits | Dipakai di | Penyesuaian ke design system |
+| --- | --- | --- |
+| **Count Up** | Angka USDC / total locked di hero & dashboard | Mono + `tnum`, sekali saat masuk viewport, 150β250ms |
+| **Split Text / Blur Text** | Headline hero (reveal sekali saat load) | Jangan loop; `ease-out`; warna `text` |
+| **Shiny Text** | 1 eyebrow / label kecil saja | Sheen sangat halus di atas `muted` β **bukan** gradient warna-warni |
+| **Animated / Fade Content** | Reveal section saat scroll (landing) | Fade + blur-up tipis, sekali, jarak kecil |
+| **Magnet** | Tombol primary (hover-lift) | Offset kecil, selaras token motion; jangan berlebihan |
+| **Dot Grid** (background) | Latar hero / bounty board | Opacity ~3β6%, samakan dengan util `.dot-grid` (Β§3.1) |
+| **Click Spark** (opsional) | Feedback klik aksi on-chain | Warna `accent`, sangat hemat; boleh di-skip |
+
+### 6.3 Hindari (bentrok dengan anti-AI Β§7)
+
+- **Background wash warna-warni:** Aurora, Silk, Iridescence, Threads, Balatro, Liquid Chrome.
+- **Glass / glow / neon:** GlassSurface, efek blur panel, star/neon border yang menyala.
+- **Gerak norak:** blob cursor 3D, teks Rotating/Scramble/Decrypt yang **loop terus**, background partikel padat.
+- Aturannya: kalau sebuah efek menarik perhatian **lebih dari** data on-chain di layar itu, jangan dipakai.
+
+## 7. Do / Don't (anti-AI)
+
+| β
Do | π« Don't |
+| --- | --- |
+| Left-aligned, asimetris, grid editorial | Semua center + hero simetris |
+| Hairline 1px, radius kecil | Shadow tebal, serba pill besar |
+| 1 aksen indigo, hemat | Gradient ungu/biru, wash |
+| Mono + tabular untuk data on-chain | Inter di mana-mana |
+| Konten nyata (repo, amount, tx hash) | Lorem ipsum, kotak abu-abu |
+| Dot-grid 3% samar | Glassmorphism, blur panel, neon glow, 3D blob |
+
+## 8. Suggested file structure
+
+```
+app/
+ layout.tsx # fonts + data-theme
+ globals.css # tokens + tailwind theme
+ page.tsx # landing
+ bounties/page.tsx # public bounty board
+ reward/[id]/page.tsx # reward detail & status
+ create/page.tsx # create reward
+ providers.tsx # wagmi/rainbowkit/react-query (client)
+components/
+ ui/ # button, pill, card, table, input (shadcn-based)
+ bounty/ # BountyCard, BountyRow, FilterRail
+ onchain/ # ProofPanel, Stepper, DisputePanel
+lib/
+ format.ts # truncateAddr, usd
+ wagmi.ts # config (ssr:true + cookieStorage)
+ explorer.ts # explorerTx / explorerAddr
+```
+
+## 9. Accessibility
+
+- Kontras teks minimal WCAG AA (dark: `text` di atas `bg` β).
+- Fokus ring selalu terlihat (`accent`, 2px offset), jangan andalkan warna saja untuk status β selalu ada label teks di pill.
+- Target sentuh β₯ 40px; motion hormati `prefers-reduced-motion`.
+
+---
+
+
\ No newline at end of file
diff --git a/PRD.md b/PRD.md
new file mode 100644
index 0000000..48e2702
--- /dev/null
+++ b/PRD.md
@@ -0,0 +1,1412 @@
+# PullPay x Optimism β Full PRD (v1.0)
+
+
+
+
+
+## 1. Executive Summary
+
+PullPay is **open source reward infrastructure** on Optimism. A maintainer locks USDC in an on-chain escrow and adds a single workflow file to the repo. When a Pull Request (PR) is merged and its **eligibility is verified in a decentralized way**, USDC is automatically paid to the contributor β without the contributor needing any ETH β and a reputation attestation is minted on-chain.
+
+Unlike competitors that only check `merged == true` via a centralized server, PullPay answers a sharper question: **"is this PR actually worth paying for?"** through the UMA Optimistic Oracle.
+
+## 2. Background & Problem
+
+The open source ecosystem runs on hundreds of small contributions (bug fixes, docs, translations, tooling). But paying a $5β20 reward is surprisingly hard:
+
+- **Fees eat the reward:** Stripe ~2.9% + $0.30 (36% erosion on $5); bank wire $15β$45; Ethereum L1 gas $15+.
+- **Manual overhead:** payouts are coordinated over Discord, spreadsheets, DMs, and collecting wallet addresses β costing more than the reward itself.
+- **Trust gap:** contributors don't know if a bounty is truly funded; maintainers have no quality guarantee.
+- **Weakness of existing crypto solutions:** they all depend on a **centralized bot/server** and only check merge status β manipulable and blind to quality.
+
+## 3. Goals & Non-Goals
+
+### Goals
+
+- Automate reward payout when a PR is merged & verified, end-to-end.
+- **Decentralized** verification (not a single server) with a dispute mechanism.
+- **Zero setup** for maintainers (1 workflow file) & **gasless** for contributors.
+- Build **portable on-chain contributor reputation**.
+- Settlement cost < 1% of the reward value.
+
+### Non-Goals
+
+- Not a centralized paid bounty marketplace.
+- No contributor KYC/tax handling (out of MVP scope).
+- No support (for MVP) beyond GitHub, or beyond USDC.
+- Not an automated code-quality judge (quality is judged via human dispute).
+
+## 4. Target Users & Personas
+
+| Persona | Need | Value from PullPay |
+| --- | --- | --- |
+| **OSS Maintainer** | Reward small contributions without payout hassle | Lock funds once, the rest is automatic |
+| **Contributor** | Get paid fast, without crypto friction | USDC arrives automatically, no ETH needed |
+| **Sponsor/Ecosystem** | Fund repos & see real impact | Transparent pool + reputation/attestations |
+| **Disputer/Verifier** | Incentive to keep the system honest | Bond reward from winning a dispute |
+
+## 5. Competitive Landscape & Differentiation
+
+| Project | Chain | Verification | Limitation |
+| --- | --- | --- | --- |
+| Octasol | Solana | Centralized bot | Checks merge only |
+| [Collaborators.build](http://Collaborators.build) | On-chain USDC | Centralized bot | Checks merge only |
+| [boss.dev](http://boss.dev), Opire, Gitpay | Web2/mixed | Centralized | Not trust-minimized |
+| [tea.xyz](http://tea.xyz) | OP Stack L2 | Proof of Contribution | Package-focused, not PR-level |
+| PullPay (Stellar, reference) | Stellar | Centralized Cloudflare Worker | Centralized oracle |
+
+**PullPay x Optimism differentiation:**
+
+1. **Decentralized verification + dispute layer (UMA)** β moving from "is it merged?" to "is it worth paying?".
+2. **On-chain reputation (EAS)** β portable, verifiable developer CV.
+3. **Gasless (ERC-4337)** β frictionless onboarding for non-crypto developers.
+4. **Multi-contributor auto-split** (roadmap) β the unsolved co-author problem.
+
+## 6. Key Features (Product Overview)
+
+- **F1 β Create & Fund Reward:** maintainer locks USDC into escrow, links to a GitHub Issue.
+- **F2 β Zero-setup Workflow:** a `pullpay.yml` file triggers the process when a PR is merged.
+- **F3 β Decentralized Verification:** assert "PR is eligible" to UMA + challenge window.
+- **F4 β Dispute Resolution:** anyone can challenge; resolved via UMA voting.
+- **F5 β Auto Payout (Gasless):** contract releases USDC to the contributor; gas sponsored by a paymaster.
+- **F6 β Reputation Attestation:** EAS mints proof of contribution on-chain.
+- **F7 β Refund:** funds return to the maintainer if the PR is never merged / a dispute wins.
+- **F8 β Dashboard:** reward status, history, reputation.
+
+## 7. User Flow
+
+```mermaid
+flowchart TD
+ subgraph S1[PHASE 1 - Maintainer locks funds]
+ A[Connect wallet MetaMask] --> B[Create reward, link GitHub Issue, set USDC]
+ B --> C[approve USDC to escrow]
+ C --> D[createReward -> USDC LOCKED in escrow]
+ D --> E[Add one pullpay.yml file to repo]
+ end
+ E --> F[Reward visible on-chain: real and funded bounty]
+ subgraph S2[PHASE 2 - Contributor works]
+ F --> G[Contributor links GitHub username -> wallet]
+ G --> H[Open a Pull Request]
+ H --> I{Maintainer merges?}
+ end
+ I -->|Close / timeout| R[refund -> USDC back to maintainer]
+ I -->|MERGE| J[GitHub Action triggered]
+ subgraph S3[PHASE 3 - Verification and payout]
+ J --> K[Assert PR to UMA + bond]
+ K --> L{Any dispute within challenge window?}
+ L -->|Yes| V[DVM voting resolves the dispute]
+ L -->|No| M[Claim treated as true]
+ V --> M
+ M --> N[release USDC gasless to contributor]
+ N --> O[EAS mints reputation attestation]
+ end
+ O --> Q([Done])
+```
+
+## 8. Functional Requirements (per component)
+
+### 8.1 Escrow Contract
+
+- FR-1: Maintainer can create a reward bound to a unique `rewardId` + metadata (repo, issue, amount, token, mode).
+- FR-2: Funds are pulled via `approve` + `transferFrom` and locked until settle/refund.
+- FR-3: Only the verification module (UMA callback) can trigger `release`.
+- FR-4: `release` pays the contributor, marks the reward `settled`, prevents double-spend.
+- FR-5: `refund` returns funds to the maintainer after the deadline / a won dispute.
+- FR-6: Per-reward isolation (a relayer/oracle cannot drain the escrow).
+
+### 8.2 Verification (UMA)
+
+- FR-7: On merge, the system `assertTruth`s a structured statement (see Β§11).
+- FR-8: Bond & liveness (challenge window) are configurable.
+- FR-9: If not disputed β auto-settle β callback to escrow.
+- FR-10: If disputed β escalate to DVM voting β result triggers release/refund.
+
+### 8.3 GitHub Automation
+
+- FR-11: `pullpay.yml` triggers on `pull_request` (closed + merged).
+- FR-12: The Action sends a payload (repo, PR#, issue#, author) to the relayer.
+- FR-13: The relayer re-verifies the merge via the GitHub API before asserting.
+
+### 8.4 Reputation (EAS)
+
+- FR-14: On settle, create an attestation containing {repo, contribution type, amount, date, contributor}.
+- FR-15: Attestations can be queried to build a public profile.
+
+### 8.5 Gasless (AA)
+
+- FR-16: Contributor claims/receives without holding ETH (paymaster sponsors gas).
+
+## 9. System Architecture
+
+```mermaid
+flowchart LR
+ subgraph OnChain[On-chain - Optimism]
+ ESC[PullPayEscrow]
+ UMA[UMA Optimistic Oracle V3]
+ EAS[EAS Attestation]
+ PM[Paymaster / EntryPoint 4337]
+ end
+ subgraph OffChain[Off-chain]
+ GA[GitHub Actions - pullpay.yml]
+ REL[Relayer / Worker]
+ API[GitHub API]
+ end
+ FE[Frontend Next.js + wagmi/viem/RainbowKit] --> ESC
+ GA --> REL
+ REL --> API
+ REL --> UMA
+ UMA --> ESC
+ ESC --> EAS
+ ESC --> PM
+```
+
+**Components:**
+
+- **PullPayEscrow** β the core: stores rewards, triggers release/refund.
+- **UMA OOv3** β verification & dispute layer.
+- **EAS** β reputation recorder.
+- **EntryPoint + Paymaster (ERC-4337)** β gasless transactions.
+- **Relayer/Worker** β assertion proposer (verifies merge then asserts to UMA).
+- **GitHub Actions** β event trigger.
+- **Frontend** β create reward, dashboard, claim UI.
+
+## 10. Data Model
+
+**Reward (on-chain):**
+
+| Field | Type | Description |
+| --- | --- | --- |
+| rewardId | bytes32 | Unique key (hash of repo+issue+nonce) |
+| maintainer | address | Reward creator |
+| token | address | USDC address |
+| amount | uint256 | Reward amount |
+| repo | string | "owner/repo" |
+| issueNumber | uint256 | Issue number |
+| mode | enum | Instant / Timelock |
+| deadline | uint256 | Refund deadline |
+| assertionId | bytes32 | UMA assertion ID |
+| contributor | address | Recipient (set on verification) |
+| status | enum | Funded / Asserted / Settled / Refunded |
+
+## 11. Smart Contract Specification
+
+### UMA assertion claim structure
+
+> βPR # in repo has been merged into the default branch AND resolves Issue # per the reward description . The rightful recipient is .β
+>
+
+### Core contract (concise)
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import "@openzeppelin/contracts/access/Ownable.sol";
+
+interface IOptimisticOracleV3 {
+ function assertTruth(
+ bytes memory claim, address asserter, address callbackRecipient,
+ address escalationManager, uint64 liveness, IERC20 currency,
+ uint256 bond, bytes32 identifier, bytes32 domainId
+ ) external returns (bytes32 assertionId);
+}
+
+contract PullPayEscrow is Ownable {
+ enum Mode { Instant, Timelock }
+ enum Status { None, Funded, Asserted, Settled, Refunded }
+
+ struct Reward {
+ address maintainer; address token; uint256 amount;
+ string repo; uint256 issueNumber; Mode mode; uint256 deadline;
+ bytes32 assertionId; address contributor; Status status;
+ }
+
+ IOptimisticOracleV3 public oo;
+ address public relayer;
+ mapping(bytes32 => Reward) public rewards;
+ mapping(bytes32 => bytes32) public assertionToReward;
+
+ event RewardCreated(bytes32 id, address maintainer, uint256 amount);
+ event RewardAsserted(bytes32 id, bytes32 assertionId, address contributor);
+ event RewardSettled(bytes32 id, address contributor, uint256 amount);
+ event RewardRefunded(bytes32 id);
+
+ modifier onlyRelayer() { require(msg.sender == relayer, "not relayer"); _; }
+
+ constructor(address _oo, address _relayer) Ownable(msg.sender) {
+ oo = IOptimisticOracleV3(_oo); relayer = _relayer;
+ }
+
+ function createReward(
+ bytes32 id, address token, uint256 amount, string calldata repo,
+ uint256 issueNumber, Mode mode, uint256 deadline
+ ) external {
+ require(rewards[id].status == Status.None, "exists");
+ IERC20(token).transferFrom(msg.sender, address(this), amount);
+ rewards[id] = Reward(msg.sender, token, amount, repo, issueNumber,
+ mode, deadline, bytes32(0), address(0), Status.Funded);
+ emit RewardCreated(id, msg.sender, amount);
+ }
+
+ // Relayer triggers the UMA assertion after verifying the merge
+ function assertMerge(bytes32 id, address contributor, bytes calldata claim,
+ uint64 liveness, IERC20 bondCurrency, uint256 bond)
+ external onlyRelayer
+ {
+ Reward storage r = rewards[id];
+ require(r.status == Status.Funded, "bad status");
+ bondCurrency.transferFrom(msg.sender, address(this), bond);
+ bondCurrency.approve(address(oo), bond);
+ bytes32 aId = oo.assertTruth(claim, msg.sender, address(this), address(0),
+ liveness, bondCurrency, bond, bytes32("ASSERT_TRUTH"), bytes32(0));
+ r.assertionId = aId; r.contributor = contributor; r.status = Status.Asserted;
+ assertionToReward[aId] = id;
+ emit RewardAsserted(id, aId, contributor);
+ }
+
+ // Callback from UMA when the assertion resolves true
+ function assertionResolvedCallback(bytes32 assertionId, bool truthfully) external {
+ require(msg.sender == address(oo), "only oo");
+ bytes32 id = assertionToReward[assertionId];
+ Reward storage r = rewards[id];
+ if (truthfully && r.status == Status.Asserted) {
+ r.status = Status.Settled;
+ IERC20(r.token).transfer(r.contributor, r.amount);
+ emit RewardSettled(id, r.contributor, r.amount);
+ // (hook) call EAS.attest(...) here
+ } else {
+ r.status = Status.Funded; // disputed & failed -> can be re-asserted / refunded
+ }
+ }
+
+ function refund(bytes32 id) external {
+ Reward storage r = rewards[id];
+ require(msg.sender == r.maintainer, "not maintainer");
+ require(r.status == Status.Funded && block.timestamp > r.deadline, "not refundable");
+ r.status = Status.Refunded;
+ IERC20(r.token).transfer(r.maintainer, r.amount);
+ emit RewardRefunded(id);
+ }
+}
+```
+
+
+
+### Example `pullpay.yml`
+
+```yaml
+name: PullPay
+on:
+ pull_request:
+ types: [closed]
+jobs:
+ settle:
+ if: github.event.pull_request.merged == true
+ runs-on: ubuntu-latest
+ steps:
+ - name: Notify PullPay relayer
+ run: |
+ curl -X POST https://relayer.pullpay.dev/settle \
+ -H 'Content-Type: application/json' \
+ -d '{
+ "repo": "REPO_PLACEHOLDER",
+ "pr": PR_NUMBER_PLACEHOLDER,
+ "issue": ISSUE_NUMBER_PLACEHOLDER,
+ "author": "AUTHOR_PLACEHOLDER"
+ }'
+```
+
+### Frontend config (wagmi + RainbowKit + viem)
+
+```tsx
+import { getDefaultConfig } from '@rainbow-me/rainbowkit'
+import { optimism, optimismSepolia } from 'viem/chains'
+
+export const config = getDefaultConfig({
+ appName: 'PullPay',
+ projectId: 'WALLETCONNECT_PROJECT_ID',
+ chains: [optimismSepolia, optimism],
+ ssr: true,
+})
+```
+
+## 12. Full Tech Stack
+
+| Layer | Technology | Notes |
+| --- | --- | --- |
+| **Blockchain** | Optimism (OP Mainnet) / Optimism Sepolia | EVM L2, cheap gas |
+| **Smart contract** | Solidity ^0.8.24, OpenZeppelin | Escrow, access control |
+| **Contract dev/test** | Foundry (forge, cast, anvil) | Test + deploy + fuzz |
+| **Alternative tooling** | Hardhat + TypeChain | If the team prefers TS |
+| **Asset** | USDC (ERC-20) | approve + transferFrom pattern |
+| **Verification** | UMA Optimistic Oracle V3 | assertTruth + escalation manager |
+| **Reputation** | Ethereum Attestation Service (EAS) | Contribution attestation schema |
+| **Gasless** | ERC-4337 (EntryPoint, Bundler, Paymaster) | e.g. Pimlico / Alchemy AA / Biconomy |
+| **Automation** | GitHub Actions (`pullpay.yml`) | PR event trigger |
+| **Relayer/Backend** | Node.js/TypeScript + viem; Cloudflare Workers or serverless | Verify GitHub API, propose assertion |
+| **GitHub integration** | GitHub REST/GraphQL API, Octokit | Verify merge, PR metadata |
+| **Frontend framework** | Next.js 16 (App Router) + React 19 + TypeScript | Latest stable (16.2.x, React 19.2); scaffold `npx create-next-app@latest` |
+| **Wallet & chain** | RainbowKit + wagmi v2 + viem + @tanstack/react-query | MetaMask & other wallets |
+| **Styling/UI** | Tailwind CSS + shadcn/ui | Fast & clean |
+| **Proof storage** | IPFS ([web3.storage](http://web3.storage) / Pinata) | Hash on-chain, file off-chain |
+| **Indexing/data** | The Graph or Ponder | Query events for the dashboard |
+| **RPC/infra** | Alchemy / Infura / QuickNode (Optimism) | Node endpoints |
+| **Contributor auth** | GitHub OAuth (link username -> wallet) | Recipient mapping |
+| **CI/CD & hosting** | GitHub Actions, Vercel (frontend), Fleek/Cloudflare | Deploy |
+| **Monitoring** | Tenderly (contract), Sentry (frontend) | Observability |
+| **Languages** | Solidity, TypeScript, YAML | β |
+
+## 13. Security & Threat Model
+
+| Threat | Mitigation |
+| --- | --- |
+| Self-merge / sybil (maintainer pays own alt) | Bond + UMA dispute; anyone can challenge |
+| Malicious/compromised relayer | Contract isolates per-reward; relayer can only assert, not release directly; release only via UMA callback |
+| `pullpay.yml` tampering | Relayer re-verifies via GitHub API; UMA as backstop |
+| Reentrancy | Checks-Effects-Interactions + nonReentrant guard |
+| Claim front-running | rewardId & contributor bound in the assertion |
+| Fake proof | IPFS proof hash + challenge window |
+| Relayer private key leak | Key rotation, limited allowance, least privilege |
+
+## 14. Economics & Gas
+
+- Contract interactions on Optimism are typically a few cents; an ERC-20 transfer can be tiny when the network is idle.
+- Fee = L2 execution (cheap) + L1 data fee (variable; dropped sharply post EIP-4844/blobs).
+- Erosion < 1% on $5β20 rewards β far below Stripe (36% on $5) or Ethereum L1 ($15+).
+- **Optional revenue:** small protocol fee / escrow yield (Aave) β out of MVP scope.
+
+## 15. Success Metrics (KPIs)
+
+- Number of rewards created & settled.
+- USDC value distributed.
+- Average merge β payout time.
+- Dispute rate (low target = healthy optimistic system).
+- Number of reputation attestations minted.
+- Number of newly funded Optimism wallets (ecosystem impact).
+
+## 16. MVP Scope vs Roadmap
+
+### MVP (hackathon, 2β3 days)
+
+- [ ] `PullPayEscrow`: createReward / assertMerge / callback / refund
+- [ ] UMA OOv3 integration (short liveness 30β120s or sandbox oracle)
+- [ ] `pullpay.yml` + minimal relayer (verify GitHub API -> assert)
+- [ ] Frontend: create reward (approve) + status dashboard
+- [ ] 1 EAS attestation on settle (simple)
+- [ ] Deploy to Optimism Sepolia
+- [ ] End-to-end demo: create -> PR -> merge -> (window) -> USDC arrives -> attestation
+
+### Roadmap
+
+- Full gasless (production paymaster)
+- Multi-contributor auto-split (co-author trailer)
+- 24h Timelock mode + dispute UI
+- RetroPGF-style per-repo pool
+- Escrow yield (Aave)
+- GitLab/Gitea support; multi-token
+
+## 17. Hackathon Timeline (indicative)
+
+| Day | Focus |
+| --- | --- |
+| Day 0 (evening) | Setup repo, Foundry, scaffold frontend (RainbowKit), deploy mock USDC |
+| Day 1 | Escrow contract + tests; UMA integration (assert + callback) |
+| Day 2 | Relayer + `pullpay.yml`; frontend create reward + dashboard; EAS attestation |
+| Day 3 | Polish demo, deploy Sepolia, record demo, pitch slides |
+
+## 18. Risks & Mitigations
+
+| Risk | Mitigation |
+| --- | --- |
+| Idea is crowded | Differentiate via UMA + EAS + gasless |
+| UMA resolution too slow for a demo | Short liveness / sandbox oracle |
+| Scope overrun | Lock one end-to-end flow; the rest is roadmap |
+| AA (4337) complexity | MVP can mock gasless; paymaster on roadmap |
+| EAS integration eats time | Ship a minimal attestation first |
+
+## 19. Resolved Design Decisions
+
+These resolve the previously open questions.
+
+### 19.1 Who pays the assertion bond?
+
+**Decision:** the **maintainer posts the bond bundled at `createReward`** (a small amount on top of the reward, e.g. in USDC). If the assertion settles honestly (undisputed), the bond is returned to the maintainer. If a dispute proves the claim false, the bond compensates the honest disputer per UMA rules. Rationale: keeps the relayer capital-free and stateless, and aligns incentives (the party funding the reward also backs its correctness).
+
+- *Alternative (roadmap):* protocol treasury fronts the bond, recovered via a small protocol fee.
+
+### 19.2 Objective definition of βworth payingβ
+
+**Decision:** eligibility is defined **per reward** by the maintainer at creation and encoded into the assertion claim. The claim binds three objective, checkable facts plus one criteria reference:
+
+1. PR merged into the default branch (GitHub API verifiable).
+2. PR closes/links the specified Issue #.
+3. Recipient = the PR author (or the mapped wallet).
+4. Work matches the **acceptance criteria URI** (a link/hash stored in reward metadata, e.g. the Issue body or an IPFS spec).
+
+Disputers judge against that fixed criteria reference, so voting is anchored to a pre-agreed spec rather than subjective taste.
+
+### 19.3 Multi-author PRs (MVP)
+
+**Decision:** MVP pays a **single recipient** (the PR author). Co-author splitting is roadmap. To avoid locked funds, the data model already stores `contributor` as a settable field so a splitter module can be added later without a storage migration.
+
+### 19.4 Contributor never connects a wallet
+
+**Decision:** funds are **never stuck**. If no recipient wallet is mapped by the reward `deadline`, the maintainer can call `refund`. Contributors map GitHub username β wallet via GitHub OAuth before settlement; unmapped rewards simply time out to refund.
+
+## 20. Fully Integrated Contract (UMA + EAS + Bond + Guards)
+
+This extends Β§11 with real UMA callbacks, EAS attestation on settle, bundled bond, reentrancy protection, and per-reward isolation.
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
+import "@openzeppelin/contracts/access/Ownable.sol";
+
+/* ---------- UMA Optimistic Oracle V3 (minimal interface) ---------- */
+interface IOptimisticOracleV3 {
+ function assertTruth(
+ bytes memory claim,
+ address asserter,
+ address callbackRecipient,
+ address escalationManager,
+ uint64 liveness,
+ IERC20 currency,
+ uint256 bond,
+ bytes32 identifier,
+ bytes32 domainId
+ ) external returns (bytes32 assertionId);
+
+ function settleAndGetAssertionResult(bytes32 assertionId) external returns (bool);
+ function defaultIdentifier() external view returns (bytes32);
+}
+
+/* ---------- EAS (minimal interface) ---------- */
+struct AttestationRequestData {
+ address recipient;
+ uint64 expirationTime;
+ bool revocable;
+ bytes32 refUID;
+ bytes data;
+ uint256 value;
+}
+struct AttestationRequest {
+ bytes32 schema;
+ AttestationRequestData data;
+}
+interface IEAS {
+ function attest(AttestationRequest calldata request) external payable returns (bytes32);
+}
+
+contract PullPayEscrow is Ownable, ReentrancyGuard {
+ using SafeERC20 for IERC20;
+
+ enum Mode { Instant, Timelock }
+ enum Status { None, Funded, Asserted, Settled, Refunded, Rejected }
+
+ struct Reward {
+ address maintainer;
+ address token; // reward token (USDC)
+ uint256 amount; // reward amount
+ uint256 bond; // bond bundled by maintainer (same token for simplicity)
+ string repo;
+ uint256 issueNumber;
+ bytes32 criteriaHash; // hash/URI ref of acceptance criteria
+ Mode mode;
+ uint256 deadline;
+ bytes32 assertionId;
+ address contributor;
+ Status status;
+ }
+
+ IOptimisticOracleV3 public immutable oo;
+ IEAS public immutable eas;
+ bytes32 public immutable easSchema; // pre-registered EAS schema UID
+ IERC20 public immutable bondCurrency; // e.g. USDC used for bonds
+ address public relayer;
+ uint64 public liveness = 7200; // default challenge window (2h); short for demo
+
+ mapping(bytes32 => Reward) public rewards;
+ mapping(bytes32 => bytes32) public assertionToReward;
+
+ event RewardCreated(bytes32 indexed id, address indexed maintainer, uint256 amount);
+ event RewardAsserted(bytes32 indexed id, bytes32 indexed assertionId, address contributor);
+ event RewardSettled(bytes32 indexed id, address indexed contributor, uint256 amount, bytes32 attestationUID);
+ event RewardRejected(bytes32 indexed id);
+ event RewardRefunded(bytes32 indexed id);
+
+ modifier onlyRelayer() { require(msg.sender == relayer, "not relayer"); _; }
+
+ constructor(
+ address _oo, address _eas, bytes32 _easSchema,
+ address _bondCurrency, address _relayer
+ ) Ownable(msg.sender) {
+ oo = IOptimisticOracleV3(_oo);
+ eas = IEAS(_eas);
+ easSchema = _easSchema;
+ bondCurrency = IERC20(_bondCurrency);
+ relayer = _relayer;
+ }
+
+ function setRelayer(address _relayer) external onlyOwner { relayer = _relayer; }
+ function setLiveness(uint64 _liveness) external onlyOwner { liveness = _liveness; }
+
+ /* ---------- 1) Maintainer funds reward + bond ---------- */
+ function createReward(
+ bytes32 id, address token, uint256 amount, uint256 bond,
+ string calldata repo, uint256 issueNumber, bytes32 criteriaHash,
+ Mode mode, uint256 deadline
+ ) external nonReentrant {
+ require(rewards[id].status == Status.None, "exists");
+ require(amount > 0, "zero amount");
+ IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
+ // bond bundled in bondCurrency (USDC); may differ from reward token
+ if (bond > 0) bondCurrency.safeTransferFrom(msg.sender, address(this), bond);
+ rewards[id] = Reward({
+ maintainer: msg.sender, token: token, amount: amount, bond: bond,
+ repo: repo, issueNumber: issueNumber, criteriaHash: criteriaHash,
+ mode: mode, deadline: deadline, assertionId: bytes32(0),
+ contributor: address(0), status: Status.Funded
+ });
+ emit RewardCreated(id, msg.sender, amount);
+ }
+
+ /* ---------- 2) Relayer asserts eligibility to UMA ---------- */
+ function assertMerge(bytes32 id, address contributor, bytes calldata claim)
+ external onlyRelayer nonReentrant
+ {
+ Reward storage r = rewards[id];
+ require(r.status == Status.Funded, "bad status");
+ require(contributor != address(0), "no contributor");
+ r.contributor = contributor;
+ r.status = Status.Asserted;
+ bondCurrency.forceApprove(address(oo), r.bond);
+ bytes32 aId = oo.assertTruth(
+ claim, msg.sender, address(this), address(0),
+ liveness, bondCurrency, r.bond, oo.defaultIdentifier(), bytes32(0)
+ );
+ r.assertionId = aId;
+ assertionToReward[aId] = id;
+ emit RewardAsserted(id, aId, contributor);
+ }
+
+ /* ---------- 3) UMA callback on resolution ---------- */
+ function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully)
+ external nonReentrant
+ {
+ require(msg.sender == address(oo), "only oo");
+ bytes32 id = assertionToReward[assertionId];
+ Reward storage r = rewards[id];
+ require(r.status == Status.Asserted, "bad status");
+ if (assertedTruthfully) {
+ r.status = Status.Settled;
+ // return bond to maintainer, pay reward to contributor
+ if (r.bond > 0) bondCurrency.safeTransfer(r.maintainer, r.bond);
+ IERC20(r.token).safeTransfer(r.contributor, r.amount);
+ bytes32 uid = _attest(id, r);
+ emit RewardSettled(id, r.contributor, r.amount, uid);
+ } else {
+ // disputed & proven false: reward + bond return to maintainer
+ r.status = Status.Rejected;
+ IERC20(r.token).safeTransfer(r.maintainer, r.amount);
+ if (r.bond > 0) bondCurrency.safeTransfer(r.maintainer, r.bond);
+ emit RewardRejected(id);
+ }
+ }
+
+ // UMA calls this if the assertion is disputed (optional hook)
+ function assertionDisputedCallback(bytes32 assertionId) external {
+ require(msg.sender == address(oo), "only oo");
+ // no-op: final outcome handled in assertionResolvedCallback
+ }
+
+ /* ---------- 4) EAS reputation attestation ---------- */
+ function _attest(bytes32 id, Reward storage r) internal returns (bytes32) {
+ bytes memory data = abi.encode(
+ r.repo, r.issueNumber, r.amount, r.criteriaHash, block.timestamp
+ );
+ return eas.attest(AttestationRequest({
+ schema: easSchema,
+ data: AttestationRequestData({
+ recipient: r.contributor,
+ expirationTime: 0,
+ revocable: false,
+ refUID: id,
+ data: data,
+ value: 0
+ })
+ }));
+ }
+
+ /* ---------- 5) Refund after deadline if never settled ---------- */
+ function refund(bytes32 id) external nonReentrant {
+ Reward storage r = rewards[id];
+ require(msg.sender == r.maintainer, "not maintainer");
+ require(r.status == Status.Funded && block.timestamp > r.deadline, "not refundable");
+ r.status = Status.Refunded;
+ IERC20(r.token).safeTransfer(r.maintainer, r.amount);
+ if (r.bond > 0) bondCurrency.safeTransfer(r.maintainer, r.bond);
+ emit RewardRefunded(id);
+ }
+}
+```
+
+**Notes on the integrated version:**
+
+- **Bond bundled at creation** (Β§19.1) and refunded to the maintainer on honest settle; on a proven-false dispute both reward and bond return to the maintainer.
+- **EAS attestation** minted to the contributor on settle, referencing the reward `id` via `refUID` and the acceptance-criteria hash (Β§19.2).
+- **`SafeERC20` + `ReentrancyGuard`** cover the token-transfer and reentrancy threats from Β§13.
+- **`forceApprove`** avoids the non-zero-allowance ERC-20 pitfall for the bond.
+- **Per-reward isolation**: every transfer references a single reward's `amount`/`bond`; the relayer can only `assertMerge`, never move funds directly.
+- **EAS schema** must be pre-registered on Optimism (e.g. `string repo, uint256 issue, uint256 amount, bytes32 criteria, uint256 ts`) and its UID passed to the constructor.
+
+## 21. Glossary
+
+- **Optimistic Oracle:** an oracle that treats a claim as true unless challenged within the challenge window.
+- **Bond:** economic collateral staked by the proposer/disputer.
+- **DVM:** UMA's Data Verification Mechanism (dispute-resolution voting).
+- **EAS:** Ethereum Attestation Service for recording on-chain claims/attestations.
+- **ERC-4337 / AA:** Account Abstraction; enables gasless transactions via a paymaster.
+- **SAC/SEP-41:** the Stellar equivalents (from the original PullPay reference), not used in the EVM version.
+
+## 22. Who Can Protest? Dispute Roles & Contributor Protection
+
+Two different "third parties" are often conflated:
+
+- **Disputer (protester)** β raises "this is wrong".
+- **Judge / arbiter** β decides *who is right* when there is disagreement. This neutral role is the **actual reason UMA exists** β not to invite idle outsiders to complain.
+
+We keep a **neutral arbiter (UMA) even for maintainer-funded rewards**, specifically to **protect the contributor**. Restricting *who may dispute* is not the same as removing the need for a neutral judge.
+
+### 22.1 Yes β the contributor can protest
+
+There are **two failure modes**, and the system must be fair to both:
+
+| Failure mode | Who is harmed | Who protests | Protects |
+| --- | --- | --- | --- |
+| False positive: payout for fake / undeserving work | Funder | Maintainer / sponsor / watchdog | The funder |
+| False negative: legit work merged but **not paid** | Contributor | **The contributor (self-claim)** | The contributor |
+
+So the contributor is a **first-class claimant**, not just a passive recipient. If the work meets the criteria and the PR is merged but payment never happens (relayer offline, maintainer stalls), the contributor can **initiate their own assertion** β "PR #X is merged and meets the criteria, pay me" β post the bond, and if the maintainer disagrees *they* must dispute, after which a **neutral judge decides**. The bond is returned on an honest settle, so an honest contributor pays nothing net. This removes the contributor's dependency on the maintainer's goodwill.
+
+### 22.2 Symmetric claim flow
+
+```mermaid
+flowchart TD
+ subgraph Normal[Normal path - funder protection]
+ RA[Relayer asserts: pay contributor] --> W1{Maintainer or sponsor disputes?}
+ W1 -->|No| P1[Auto-settle: contributor paid]
+ W1 -->|Yes| DVM1[Neutral judge DVM decides]
+ end
+ subgraph Protect[Contributor protection path]
+ CA[Contributor self-claims: work done, pay me] --> W2{Maintainer disputes?}
+ W2 -->|No| P2[Auto-settle: contributor paid]
+ W2 -->|Yes| DVM2[Neutral judge DVM decides]
+ end
+```
+
+### 22.3 Contract hook β contributor-initiated claim
+
+```solidity
+// Contributor can self-assert eligibility if unpaid after a merge.
+// mappedWallet links a verified GitHub author (via OAuth) to a payout wallet.
+function claimByContributor(bytes32 id, bytes calldata claim) external nonReentrant {
+ Reward storage r = rewards[id];
+ require(r.status == Status.Funded, "bad status");
+ require(msg.sender == mappedWallet[id], "not the contributor");
+ // The contributor posts the bond themselves (returned on honest settle).
+ bondCurrency.safeTransferFrom(msg.sender, address(this), r.bond);
+ bondCurrency.forceApprove(address(oo), r.bond);
+ r.contributor = msg.sender;
+ r.status = Status.Asserted;
+ bytes32 aId = oo.assertTruth(
+ claim, msg.sender, address(this), escalationManager,
+ liveness, bondCurrency, r.bond, oo.defaultIdentifier(), bytes32(0)
+ );
+ r.assertionId = aId;
+ assertionToReward[aId] = id;
+ emit RewardAsserted(id, aId, msg.sender);
+}
+```
+
+Both the relayer path (`assertMerge`) and the contributor path (`claimByContributor`) feed the same `assertionResolvedCallback`, so settlement, bond return, and EAS attestation logic stay identical.
+
+### 22.4 Voting scheme (only if disputed)
+
+Most cases never reach voting. If no one disputes within the **liveness window**, the assertion auto-settles as true. A dispute escalates to UMA's **DVM (Data Verification Mechanism)** β an on-chain court using **commitβreveal** so voters can't copy each other.
+
+```mermaid
+flowchart TD
+ A[Assertion + bond, liveness starts] --> B{Dispute within window?}
+ B -->|No| C[Auto-settle TRUE - pay contributor. No voting]
+ B -->|Yes, disputer posts bond| D[Escalate to DVM]
+ D --> E[COMMIT phase ~24-48h: voters submit salted vote hash]
+ E --> F[REVEAL phase ~24-48h: voters reveal actual vote]
+ F --> G[Tally: weighted by staked UMA]
+ G --> H{Majority result}
+ H -->|TRUE| I[Asserter wins. Disputer loses bond. Contributor paid]
+ H -->|FALSE| J[Disputer wins asserter bond. Funds + bond back to funder]
+ I --> K[Callback to PullPayEscrow]
+ J --> K
+```
+
+- **Commit phase (~24β48h):** each staked-$UMA voter submits a *hash* of {vote + secret salt}; the real vote is hidden to prevent herd-following.
+- **Reveal phase (~24β48h):** voters reveal the actual vote; unrevealed votes are discarded.
+- **Tally:** votes are **weighted by staked UMA** (not one-person-one-vote); the token majority decides TRUE/FALSE.
+- **Incentives:** voters aligned with the majority earn staking rewards; deviators or non-voters are slashed (Schelling-point drives honesty).
+- **Bond settlement:** the losing side forfeits its bond (part rewards the winner, part is burned to deter collusion). Result returns via `assertionResolvedCallback`.
+- **Total DVM time:** ~2β4 days. For the hackathon demo, use **short liveness** (30β120s) or a **mock/sandbox oracle**.
+
+## 23. Design Variant β Permissioned Disputers (Escalation Manager)
+
+By default disputes are **permissionless** (anyone with a bond). For a more controlled process, UMA OOv3 supports an optional **Escalation Manager (EM)** β a contract attached per assertion via the `escalationManager` argument (set to `address(0)` in the default design). It can **restrict who may dispute** while keeping the DVM as the final judge.
+
+
+
+### 23.1 Policy hook
+
+```solidity
+// UMA reads this policy per assertion; if validateDisputers is true it calls isDisputeAllowed.
+struct AssertionPolicy {
+ bool blockAssertion; // if true, assertion is not allowed
+ bool arbitrateViaEscalationManager; // EM resolves instead of the DVM
+ bool discardOracle; // ignore DVM result
+ bool validateDisputers; // if true, UMA calls isDisputeAllowed
+}
+
+interface EscalationManagerInterface {
+ function getAssertionPolicy(bytes32 assertionId) external view returns (AssertionPolicy memory);
+ function isDisputeAllowed(bytes32 assertionId, address disputeCaller) external view returns (bool);
+ function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully) external;
+ function assertionDisputedCallback(bytes32 assertionId) external;
+}
+```
+
+### 23.2 Minimal whitelist EM (includes the contributor)
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import "@openzeppelin/contracts/access/Ownable.sol";
+
+struct AssertionPolicy {
+ bool blockAssertion;
+ bool arbitrateViaEscalationManager;
+ bool discardOracle;
+ bool validateDisputers;
+}
+
+contract PullPayWhitelistEM is Ownable {
+ mapping(address => bool) public allowedDisputer; // funders + the contributor
+
+ event DisputerSet(address indexed who, bool allowed);
+
+ constructor() Ownable(msg.sender) {}
+
+ function setDisputer(address who, bool allowed) external onlyOwner {
+ allowedDisputer[who] = allowed;
+ emit DisputerSet(who, allowed);
+ }
+
+ // Enable disputer validation, keep the DVM as the arbiter.
+ function getAssertionPolicy(bytes32) external pure returns (AssertionPolicy memory) {
+ return AssertionPolicy({
+ blockAssertion: false,
+ arbitrateViaEscalationManager: false, // DVM still decides who is right
+ discardOracle: false,
+ validateDisputers: true // <-- enables isDisputeAllowed
+ });
+ }
+
+ function isDisputeAllowed(bytes32, address disputeCaller) external view returns (bool) {
+ return allowedDisputer[disputeCaller];
+ }
+
+ // No-op callbacks (required by the interface).
+ function assertionResolvedCallback(bytes32, bool) external {}
+ function assertionDisputedCallback(bytes32) external {}
+}
+```
+
+### 23.3 Trade-offs
+
+| Aspect | Permissionless (default) | Permissioned (Escalation Manager) |
+| --- | --- | --- |
+| Who can dispute | Anyone with a bond | Only whitelisted addresses (must include the contributor) |
+| Trust assumption | Fully trust-minimized | Trust in the whitelist admin |
+| Spam / griefing | Deterred purely by bond | Extra gate before bond |
+| Best for | Public, credibly-neutral protocol | Private / enterprise repos, curated orgs |
+| Extra config | None | Deploy + maintain EM whitelist |
+
+**Recommendation:** ship **permissionless as the default** (stronger trust-minimized story for judges) and offer the **whitelist EM as an opt-in mode** for private/enterprise repos. Setting `arbitrateViaEscalationManager: true` also lets a small committee resolve disputes instead of the global DVM β faster and handy as a demo fallback, at the cost of decentralization.
+
+## 24. Verification Tiers β UMA as a Safety Net (not always-on)
+
+**Key principle:** UMA only exists to *resolve disagreement*. When the payer and the decider are the same person and they agree, there is nothing to dispute β so the oracle should not run at all. PullPay therefore uses **two tiers**, and UMA is a **fallback**, not a mandatory step on every payout.
+
+### 24.1 The two tiers
+
+| Situation | Tier | UMA? | Path |
+| --- | --- | --- | --- |
+| Maintainer funds their own money **and** approves (merge) | **Instant** | β No | Merge/approve β direct release |
+| Maintainer stalls / never acts, work already meets criteria | **Instant β escalated** | β
Yes | Contributor self-claim β UMA protects the contributor |
+| Funds come from a **pool / sponsor** (payer β decider) | **Safeguarded** | β
Yes | UMA prevents self-dealing, arbitrates disputes |
+| Anyone wants to **dispute** an assertion | **Safeguarded** | β
Yes | Escalate to the neutral judge |
+
+**Why this is honest for judges:** on the Instant path you *do* resemble competitors (you trust the merge) β and that's fine. The pitch shifts to: *"a fast lane for parties that trust each other, and a decentralized safety net for when that trust is absent."* This maps directly onto the existing `Mode` enum: **`Instant`** = trust the merge (no oracle); **`Safeguarded`** = UMA-backed.
+
+### 24.2 Decision & settlement flow
+
+```mermaid
+flowchart TD
+ Start[Maintainer creates reward] --> Q0{Funds source}
+ Q0 -->|Own wallet| Q1{Trust the merge?}
+ Q0 -->|Pool / sponsor| SG[Mode: Safeguarded - UMA required]
+ Q1 -->|Yes| INS[Mode: Instant - no UMA]
+ Q1 -->|No| SG
+ INS --> M{PR merged and approved?}
+ M -->|Yes| PAY[Direct release - no oracle]
+ M -->|Maintainer stalls past grace period| ESC[Contributor escalates to UMA]
+ SG --> ASSERT[assertTruth to UMA + bond]
+ ESC --> ASSERT
+ ASSERT --> DISP{Disputed within liveness?}
+ DISP -->|No| PAY2[Auto-settle - pay contributor]
+ DISP -->|Yes| DVM[Neutral judge DVM decides]
+ DVM --> PAY2
+ PAY --> ATT[EAS attestation]
+ PAY2 --> ATT
+ ATT --> Done([Contributor paid + on-chain reputation])
+```
+
+### 24.3 Implementation
+
+Use the `Mode` enum to branch settlement. Instant mode skips UMA and the bond entirely; Safeguarded mode uses the UMA flow from Β§20/Β§22. A **grace deadline** on Instant rewards is what wires the safety net: if the maintainer never settles, the contributor can escalate the same reward into the UMA path.
+
+```solidity
+enum Mode { Instant, Safeguarded }
+
+// --- INSTANT PATH A: maintainer explicitly approves a recipient (strongest trust match) ---
+function approveAndRelease(bytes32 id, address contributor) external nonReentrant {
+ Reward storage r = rewards[id];
+ require(msg.sender == r.maintainer, "not maintainer");
+ require(r.status == Status.Funded, "bad status");
+ require(r.mode == Mode.Instant, "not instant");
+ require(contributor != address(0), "no contributor");
+ r.contributor = contributor;
+ r.status = Status.Settled;
+ IERC20(r.token).safeTransfer(contributor, r.amount); // no oracle, no bond
+ bytes32 uid = _attest(id, r);
+ emit RewardSettled(id, contributor, r.amount, uid);
+}
+
+// --- INSTANT PATH B: relayer settles after verifying the merge via GitHub API ---
+function settleInstant(bytes32 id, address contributor) external onlyRelayer nonReentrant {
+ Reward storage r = rewards[id];
+ require(r.status == Status.Funded, "bad status");
+ require(r.mode == Mode.Instant, "not instant");
+ require(contributor != address(0), "no contributor");
+ r.contributor = contributor;
+ r.status = Status.Settled;
+ IERC20(r.token).safeTransfer(contributor, r.amount);
+ bytes32 uid = _attest(id, r);
+ emit RewardSettled(id, contributor, r.amount, uid);
+}
+
+// --- SAFETY NET: contributor escalates a stalled Instant reward into the UMA path ---
+function escalateToUMA(bytes32 id, bytes calldata claim) external nonReentrant {
+ Reward storage r = rewards[id];
+ require(r.status == Status.Funded, "bad status");
+ require(msg.sender == mappedWallet[id], "not the contributor");
+ require(block.timestamp > r.deadline, "grace not elapsed"); // maintainer had time to act
+ // From here it behaves exactly like Safeguarded: contributor posts the bond and asserts.
+ bondCurrency.safeTransferFrom(msg.sender, address(this), r.bond);
+ bondCurrency.forceApprove(address(oo), r.bond);
+ r.contributor = msg.sender;
+ r.status = Status.Asserted;
+ bytes32 aId = oo.assertTruth(
+ claim, msg.sender, address(this), escalationManager,
+ liveness, bondCurrency, r.bond, oo.defaultIdentifier(), bytes32(0)
+ );
+ r.assertionId = aId;
+ assertionToReward[aId] = id;
+ emit RewardAsserted(id, aId, msg.sender);
+}
+```
+
+**Notes:**
+
+- **Instant mode carries no bond and never calls UMA** β cheapest and fastest; ideal for solo maintainers paying from their own wallet.
+- **`approveAndRelease`** (maintainer signs) is the purest expression of "merge = approval = pay"; **`settleInstant`** lets the `pullpay.yml` relayer auto-settle right after a verified merge.
+- **Pool/sponsor rewards are created as `Safeguarded` and cannot use the Instant functions**, so a maintainer can't unilaterally release someone else's money.
+- **The grace deadline is the safety net**: an honest contributor is never stuck on a stalled Instant reward β after the deadline they call `escalateToUMA`, which drops them into the exact same UMA settlement (and the same `assertionResolvedCallback`, bond return, and EAS attestation) as Safeguarded mode.
+- Everything converges on one settlement path, so EAS attestation and bond handling stay identical across tiers.
+
+## 25. Reward & PR Status Lifecycle
+
+Users see **one unified status**, but it is composed of two layers:
+
+- **Off-chain PR status** β read from GitHub (open, in review, changes requested, merged, closed).
+- **On-chain reward status** β the contract `Status` enum (Funded, Asserted, Settled, Rejected, Refunded).
+
+### 25.1 Unified status model
+
+| UI status | Meaning | On-chain | Trigger |
+| --- | --- | --- | --- |
+| π’ Open | Bounty funded, awaiting work | Funded | `createReward` |
+| π΅ In Review | PR opened & linked, under maintainer review | Funded | PR opened (GitHub App) |
+| π‘ Changes Requested | Maintainer requested changes | Funded | PR review event |
+| βͺ Merged β Pending Payout | PR merged, settlement starting | Funded | PR merged |
+| π£ Verifying | Safeguarded: UMA liveness window running | Asserted | `assertMerge` / `escalateToUMA` |
+| π΄ Disputed β In Voting | Challenged, DVM commitβreveal in progress | Asserted | `assertionDisputedCallback` |
+| β
Paid | Contributor paid + attestation minted | Settled | resolved-true / instant release |
+| β Rejected | Dispute proved the claim false | Rejected | resolved-false |
+| β©οΈ Refunded / Cancelled | Deadline passed, funds returned | Refunded | `refund` |
+
+### 25.2 State diagram
+
+```mermaid
+stateDiagram-v2
+ [*] --> Open: createReward
+ Open --> InReview: PR opened & linked
+ InReview --> ChangesRequested: review requests changes
+ ChangesRequested --> InReview: contributor pushes update
+ InReview --> MergedPending: PR merged
+ Open --> Refunded: deadline passed (no PR)
+ MergedPending --> Paid: Instant mode (approve/merge)
+ MergedPending --> Verifying: Safeguarded (assert to UMA)
+ Verifying --> Paid: no dispute in liveness
+ Verifying --> Disputed: challenged
+ Disputed --> Paid: DVM rules TRUE
+ Disputed --> Rejected: DVM rules FALSE
+ Rejected --> Refunded: funds return to funder
+ Paid --> [*]
+ Refunded --> [*]
+```
+
+### 25.3 Where the status is shown
+
+- **PullPay dashboard:** a colored badge per reward + a phase timeline (with live timers during Verifying/Disputed, indexed from the UMA subgraph).
+- **On the PR itself (via GitHub App):** a status check + a comment that updates in place, e.g. βπ° $20 bounty Β· status: Verifying Β· payout in ~2hβ.
+- **Dispute UI:** a βDisputeβ button (wraps the UMA dispute call so the disputer stays in PullPay) and a read-only voting-status view (phase, timers, participation). Individual votes appear only after the reveal phase (by design).
+
+## 26. Integration Surface β GitHub App vs Workflow File
+
+### 26.1 Comparison
+
+| Aspect | `pullpay.yml` (workflow file) | GitHub App |
+| --- | --- | --- |
+| Setup per repo | Commit a file (+ maybe a secret) | One-click install on org/repos |
+| Events | Only what the workflow emits | Real-time webhooks (PR, review, merge) |
+| Trust of merge signal | Workflow can be edited by repo admins | App verifies merge server-side via API |
+| PR feedback (comments/checks) | Hard/limited | Native status checks + comments |
+| Multi-repo / org rollout | Manual per repo | Centralized, scalable |
+| Build cost | Minimal (great for demo) | Higher (server + GitHub review) |
+| Key management | Repo secret | App private key (must be secured) |
+
+### 26.2 Decision
+
+**MVP / hackathon:** ship `pullpay.yml` β fastest to demo, no app-review latency.
+
+**Production:** move to a **GitHub App** as the primary integration surface for a seamless, real-time experience (matching competitors), while `pullpay.yml` remains a lightweight fallback for users who prefer not to install an app.
+
+
+
+## 27. Public Bounty Board (Discovery)
+
+### 27.1 Why
+
+Because every reward is created on-chain, anyone can index them into a **public directory** of open bounties. This delivers three wins:
+
+- **Proof of legitimacy:** each listing is a real, on-chain-funded bounty (not an empty promise).
+- **Discovery / growth:** contributors find paid work; a network-effect flywheel for the protocol.
+- **Sponsor visibility:** funders and ecosystems see live impact.
+
+### 27.2 What it shows
+
+A browsable, filterable list: repo, issue title, reward amount, token, status (from Β§25), mode (Instant/Safeguarded), deadline, and a link to the GitHub issue. Filters by language, label/tag, amount range, and status.
+
+### 27.3 Implementation
+
+- **Indexing:** a subgraph (The Graph / Ponder) indexes `RewardCreated` / `RewardAsserted` / `RewardSettled` / `RewardRefunded` events.
+- **Enrichment:** the GitHub App/relayer adds issue metadata (title, labels, language) off-chain.
+- **Frontend:** a public Next.js route (SSR/ISR) reads the subgraph β no wallet required to browse.
+- **Privacy:** public repos are listed by default; **private/enterprise repos are opt-out (hidden)** so the board never leaks private work.
+
+### 27.4 As a feature
+
+- **F9 β Public Bounty Board:** discoverable, on-chain-verifiable directory of funded bounties.
+- **F10 β Contributor Profile:** public page aggregating a contributor's EAS attestations (portable reputation), linkable from the board.
+
+## 28. Proof of Funding β Contributor-Verifiable Escrow
+
+A contributor must never work on a bounty that isn't actually funded. Because the escrow is on-chain, **funding is publicly and independently verifiable** β the contributor does not have to trust PullPay's UI or the maintainer's word.
+
+### 28.1 What βfundedβ means on-chain
+
+At `createReward`, USDC is pulled into the escrow via `safeTransferFrom` and the reward is stored with `status = Funded`. This produces three permanent, public artifacts:
+
+- **A `RewardCreated` event** (indexed by `id` + `maintainer` + `amount`).
+- **An ERC-20 `Transfer` event** moving USDC into the escrow contract address.
+- **On-chain state** `rewards[id]` readable by anyone: `amount`, `token`, `status`, `deadline`, `repo`, `issueNumber`.
+
+### 28.2 Four ways a contributor can verify (no trust in PullPay required)
+
+| Path | How | Trust level |
+| --- | --- | --- |
+| **In PullPay UI** | Badge ββ
Funded β $20 USDC lockedβ with a link to the funding tx + contract | Convenient |
+| **Block explorer** | Open the escrow on Optimistic Etherscan β see the `RewardCreated` tx + the USDC `Transfer` in | Trustless |
+| **Direct contract read** | Call the `getReward(id)` / `isFunded(id)` view (or read `rewards[id]`) via any RPC / cast | Trustless |
+| **On the GitHub issue** | GitHub App posts βπ° $20 USDC funded Β· verify on-chain β β on the issue | Convenient (links to trustless proof) |
+
+### 28.3 Read-only helpers
+
+```solidity
+// Anyone (including the contributor) can confirm funding without any privilege.
+function getReward(bytes32 id) external view returns (
+ address maintainer, address token, uint256 amount,
+ string memory repo, uint256 issueNumber, Status status, uint256 deadline
+) {
+ Reward storage r = rewards[id];
+ return (r.maintainer, r.token, r.amount, r.repo, r.issueNumber, r.status, r.deadline);
+}
+
+// Quick boolean: is this reward currently funded and live?
+function isFunded(bytes32 id) external view returns (bool) {
+ Reward storage r = rewards[id];
+ return r.status == Status.Funded && block.timestamp <= r.deadline;
+}
+
+// Optional: prove solvency β escrow token balance covers this reward.
+function isSolvent(bytes32 id) external view returns (bool) {
+ Reward storage r = rewards[id];
+ return IERC20(r.token).balanceOf(address(this)) >= r.amount;
+}
+```
+
+### 28.4 Binding funding to the exact issue (anti-spoofing)
+
+To stop a maintainer from βshowingβ funding that belongs to a different issue:
+
+- `rewardId = keccak256(repo, issueNumber, nonce)` β the reward is **deterministically bound** to one repo+issue, so a contributor can recompute the `id` from the issue and look it up directly.
+- The `criteriaHash` binds the acceptance criteria (Issue body / IPFS spec) into the same record.
+- The funding tx and the `RewardCreated` event both carry that `id`, so the on-chain proof maps 1:1 to the GitHub issue the contributor is looking at.
+
+### 28.5 As a requirement / feature
+
+- **F11 β Proof of Funding:** every bounty exposes a one-click, contract-level verification (UI badge + explorer link + `getReward`/`isFunded` view) so a contributor can confirm funds are locked **before** starting work.
+- The **Public Bounty Board (Β§27) only lists rewards whose on-chain status is `Funded`**, so anything visible there is provably backed.
+
+## 29. Frontend Framework Decision β React (SPA) vs Next.js
+
+**Decision: Next.js 16 (App Router) β the latest stable major (16.2.x, on React 19.2); scaffold with `npx create-next-app@latest`.** A plain React SPA (Vite/CRA) is simpler, but PullPay has needs that make Next.js the clearly stronger fit. This reinforces Β§12 (the tech stack already specifies Next.js App Router + RainbowKit + wagmi v2 + viem).
+
+### 29.1 Why Next.js over a plain React SPA
+
+| Requirement | Plain React (Vite SPA) | Next.js (App Router) |
+| --- | --- | --- |
+| Public Bounty Board discovery/SEO | Client-rendered β poor SEO | SSR/ISR β indexable β discovery flywheel |
+| Relayer / GitHub webhook endpoint | Needs a separate backend service | Built-in Route Handlers (`app/api`) |
+| Secret handling (RPC / relayer keys) | Everything risks shipping client-side | Server-only env + Server Components |
+| Initial load / streaming | Large single JS bundle | RSC + streaming, smaller client JS |
+| Board data freshness | Manual client polling | ISR `revalidate` |
+| Deploy | Static host + separate API | One Vercel deploy |
+| Font / image optimization | Manual | Built-in |
+
+**A plain SPA is only fine** for internal-only dashboards with no SEO and an existing separate backend β not PullPay's case.
+
+### 29.2 Rendering strategy per screen
+
+| Screen | Strategy |
+| --- | --- |
+| Landing | Static / ISR |
+| Public Bounty Board | ISR (`revalidate`) or SSR; RSC reads the subgraph |
+| Create Reward | RSC shell + Client Components for wallet / tx |
+| Reward Detail / Status | SSR for the on-chain snapshot + client polling for live timers |
+| Contributor Profile | SSR (shareable) + client for wallet-scoped data |
+
+### 29.3 Web3 + App Router best practices
+
+- **Isolate the Web3 stack** β keep all wagmi / RainbowKit / react-query inside a single client `Providers` boundary; keep pages as Server Components wherever possible.
+- **Fix hydration** β enable wagmi `ssr: true` + `cookieStorage`, and hydrate with `cookieToInitialState` so wallet state doesn't cause hydration mismatches.
+- **Server-side secrets** β put the GitHub webhook + subgraph proxy in Route Handlers; verify webhook signatures server-side; only expose safe values via `NEXT_PUBLIC_`. Never ship relayer/RPC private keys to the client.
+- **Type-safe contracts** β generate ABIs (wagmi CLI) and call via viem for end-to-end types.
+- **Keep marketing light** β lazy-load the wallet modal (`dynamic`, `ssr: false`) so landing pages don't hydrate the whole Web3 bundle.
+- **Suggested structure:** `app/(marketing)`, `app/(app)` dashboard group, `components/ui`, `lib/wagmi.ts`, `lib/contracts`, `app/api/webhook`, `app/api/subgraph`.
+
+### 29.4 Reference wiring
+
+```tsx
+// app/providers.tsx
+'use client'
+import { WagmiProvider } from 'wagmi'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
+import { config } from '@/lib/wagmi'
+import type { State } from 'wagmi'
+
+const queryClient = new QueryClient()
+
+export function Providers({ children, initialState }: {
+ children: React.ReactNode; initialState?: State
+}) {
+ return (
+
+
+ {children}
+
+
+ )
+}
+```
+
+```tsx
+// lib/wagmi.ts
+import { getDefaultConfig } from '@rainbow-me/rainbowkit'
+import { optimism, optimismSepolia } from 'viem/chains'
+import { cookieStorage, createStorage } from 'wagmi'
+
+export const config = getDefaultConfig({
+ appName: 'PullPay',
+ projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID!,
+ chains: [optimismSepolia, optimism],
+ ssr: true, // SSR-safe
+ storage: createStorage({ storage: cookieStorage }),
+})
+```
+
+```tsx
+// app/layout.tsx (excerpt) β hydrate wagmi from cookies to avoid mismatch
+import { headers } from 'next/headers'
+import { cookieToInitialState } from 'wagmi'
+import { config } from '@/lib/wagmi'
+import { Providers } from './providers'
+
+export default async function RootLayout({ children }: { children: React.ReactNode }) {
+ const initialState = cookieToInitialState(config, (await headers()).get('cookie'))
+ return (
+
+ {children}
+
+ )
+}
+```
+
+## 30. Wallet Connection & Network Flow (Technical)
+
+Β§11 and Β§29 specify the wagmi/RainbowKit *config*; this section specifies the actual **connect + network UX**, the guard code, and how a contributor avoids needing ETH.
+
+### 30.1 Two connection profiles
+
+| Profile | Wallet | Gas needs |
+| --- | --- | --- |
+| **Maintainer** (funds rewards) | Standard EOA (MetaMask / WalletConnect / Coinbase) | Holds USDC + a little ETH for `approve` β’ `createReward` |
+| **Contributor** (gets paid) | EOA to *receive*, or an ERC-4337 smart account to *self-claim* | **Receiving USDC costs no gas**; only sending a tx (self-claim/escalate) does β sponsored by a paymaster |
+
+> Key insight: a contributor **never needs ETH just to get paid** β receiving an ERC-20 transfer is gasless for the recipient. Gas only appears if they *initiate* a tx (`claimByContributor`/`escalateToUMA`), which the ERC-4337 paymaster sponsors (Β§30.4).
+>
+
+### 30.2 Connect + network-guard flow
+
+```mermaid
+flowchart TD
+ A[Open app] --> B[Click Connect - RainbowKit modal]
+ B --> C[User picks wallet: MetaMask / WalletConnect / Coinbase]
+ C --> D[wagmi useConnect establishes session]
+ D --> E{chainId == OP Sepolia 11155420?}
+ E -->|Yes| F[Ready - show address + balance]
+ E -->|No, known chain| G[wallet_switchEthereumChain]
+ E -->|No, unknown chain| H[wallet_addEthereumChain then switch]
+ G --> F
+ H --> F
+ F --> I[Account/chain change listeners keep UI in sync]
+```
+
+### 30.3 Network guard (add-chain params + wagmi hook)
+
+```tsx
+// lib/chains.ts β params for wallet_addEthereumChain (OP Sepolia)
+export const OP_SEPOLIA_PARAMS = {
+ chainId: '0xaa37dc', // 11155420
+ chainName: 'OP Sepolia',
+ nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
+ rpcUrls: ['https://sepolia.optimism.io'],
+ blockExplorerUrls: ['https://sepolia-optimism.etherscan.io'],
+}
+```
+
+```tsx
+// hooks/useNetworkGuard.ts
+'use client'
+import { useAccount, useSwitchChain } from 'wagmi'
+import { optimismSepolia } from 'viem/chains'
+
+export function useNetworkGuard() {
+ const { chainId, isConnected } = useAccount()
+ const { switchChain } = useSwitchChain()
+ const wrongNetwork = isConnected && chainId !== optimismSepolia.id // 11155420
+ return { wrongNetwork, fixNetwork: () => switchChain({ chainId: optimismSepolia.id }) }
+}
+```
+
+RainbowKit already renders a built-in "Wrong network" button; the hook above backs any custom banner and the Connect Wallet modal's "wrong network" variant in the UI spec.
+
+### 30.4 Contributor gasless (no ETH held)
+
+- **Receiving** the payout = zero gas for the contributor (the escrow pays gas to `transfer`).
+- **Self-claim / escalate** = a tx signed by the contributor. With **ERC-4337** (smart account via permissionless.js / Alchemy AA + a **Pimlico/Alchemy paymaster**), the contributor signs a `UserOperation`; the bundler submits it and the paymaster pays gas (optionally deducted from the reward).
+- **MVP:** gasless may be mocked (relayer submits) and the full paymaster moved to roadmap (Β§16/Β§18).
+
+### 30.5 GitHub username β wallet mapping
+
+1. Contributor signs in with **GitHub OAuth**.
+2. Contributor signs a **SIWE-style message** with the wallet to prove ownership.
+3. The relayer (or a contract call) records `mappedWallet[id]` β the wallet consumed by `claimByContributor` / `escalateToUMA` (Β§22, Β§24).
+4. No mapping before `deadline` β the reward simply times out to `refund` (Β§19.4) β funds are never stuck.
+
+## 31. Network & Environment Configuration (Testnet β Mainnet)
+
+### 31.1 Networks
+
+| Network | chainId | RPC | Explorer |
+| --- | --- | --- | --- |
+| **OP Sepolia** (demo) | `11155420` (`0xAA37DC`) | `https://sepolia.optimism.io` | `https://sepolia-optimism.etherscan.io` |
+| **OP Mainnet** (prod) | `10` (`0xA`) | `https://mainnet.optimism.io` | `https://optimistic.etherscan.io` |
+
+### 31.2 Dependency addresses (source per network β do NOT hardcode from memory)
+
+| Dependency | Where to get the address |
+| --- | --- |
+| USDC (test) | Circle testnet USDC on OP Sepolia ([developers.circle.com](http://developers.circle.com)) β **or** deploy your own `MockUSDC` (recommended for a fast demo) |
+| UMA `OptimisticOracleV3` | [docs.uma.xyz](http://docs.uma.xyz) β "Networks / Contract addresses", or the `@uma/contracts` package |
+| UMA bond currency + `defaultIdentifier` | Must be UMA-whitelisted (e.g. test USDC/WETH); read `defaultIdentifier()` on-chain |
+| EAS + SchemaRegistry | [docs.attest.sh](http://docs.attest.sh) β deployments (Optimism); register your schema to obtain the `schemaUID` |
+
+
+
+### 31.3 Faucets
+
+- **OP Sepolia ETH:** Optimism Superchain faucet (`app.optimism.io/faucet`), or bridge Sepolia ETH to OP Sepolia.
+- **Test USDC:** Circle faucet (`faucet.circle.com`) β or just mint your `MockUSDC`.
+
+### 31.4 `.env.example`
+
+```bash
+# ---- Frontend (public, safe to expose) ----
+NEXT_PUBLIC_CHAIN_ID=11155420
+NEXT_PUBLIC_WC_PROJECT_ID=your_walletconnect_project_id
+NEXT_PUBLIC_ESCROW_ADDRESS=0x...
+NEXT_PUBLIC_USDC_ADDRESS=0x...
+NEXT_PUBLIC_SUBGRAPH_URL=https://api.studio.thegraph.com/query/...
+
+# ---- Server-only (NEVER prefix with NEXT_PUBLIC_) ----
+OP_SEPOLIA_RPC_URL=https://sepolia.optimism.io
+RELAYER_PRIVATE_KEY=0x... # asserts to UMA; least-privilege key
+GITHUB_WEBHOOK_SECRET=... # verify webhook signatures server-side
+GITHUB_APP_ID=...
+GITHUB_APP_PRIVATE_KEY=...
+PIMLICO_API_KEY=... # paymaster for gasless (optional/MVP-mock)
+
+# ---- Contract deploy (Foundry) ----
+DEPLOYER_PRIVATE_KEY=0x...
+UMA_OOV3_ADDRESS=0x...
+EAS_ADDRESS=0x...
+EAS_SCHEMA_UID=0x...
+BOND_CURRENCY_ADDRESS=0x... # UMA-whitelisted token
+ETHERSCAN_API_KEY=...
+```
+
+### 31.5 Deploy to OP Sepolia (Foundry)
+
+```bash
+source .env
+
+# Constructor args: (_oo, _eas, _easSchema, _bondCurrency, _relayer) β see Β§20
+forge script script/Deploy.s.sol:Deploy \
+ --rpc-url $OP_SEPOLIA_RPC_URL \
+ --private-key $DEPLOYER_PRIVATE_KEY \
+ --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY
+
+# Then copy the deployed escrow address into NEXT_PUBLIC_ESCROW_ADDRESS
+```
+
+Register the EAS schema once (`SchemaRegistry.register`, e.g. `string repo, uint256 issue, uint256 amount, bytes32 criteria, uint256 ts`) and put the returned UID in `EAS_SCHEMA_UID`. The wagmi chain config (Β§29.4) already targets OP Sepolia + OP Mainnet.
+
+## 32. End-to-End Technical Walkthrough (connect β payout)
+
+One call-by-call sequence tying it all together (Safeguarded tier shown; Instant skips UMA per Β§24).
+
+```mermaid
+sequenceDiagram
+ participant M as Maintainer
+ participant FE as Frontend
+ participant ESC as PullPayEscrow
+ participant GH as GitHub
+ participant REL as Relayer
+ participant UMA as UMA OOv3
+ participant EAS as EAS
+ participant C as Contributor
+ M->>FE: Connect wallet + guard network to OP Sepolia
+ M->>ESC: USDC.approve then createReward
+ ESC-->>FE: RewardCreated event, status Funded
+ M->>GH: Add pullpay.yml or install GitHub App
+ C->>FE: Link GitHub username to wallet via OAuth + signature
+ C->>GH: Open PR then merge
+ GH->>REL: pull_request merged webhook
+ REL->>GH: Re-verify merge via GitHub API
+ REL->>ESC: assertMerge
+ ESC->>UMA: assertTruth + bond
+ UMA-->>ESC: liveness window, no dispute
+ UMA->>ESC: assertionResolvedCallback true
+ ESC->>C: transfer USDC
+ ESC->>M: return bond
+ ESC->>EAS: attest reputation
+ ESC-->>FE: RewardSettled, status Paid
+```
+
+### 32.1 Step-by-step (which network/contract each step hits)
+
+1. **Connect + network guard** β FE, wallet on OP Sepolia (Β§30).
+2. **Fund** β M: `USDC.approve(escrow, amount+bond)` β `ESC.createReward(...)`; USDC locked, `status = Funded`, `RewardCreated` emitted (publicly verifiable, Β§28).
+3. **Wire the repo** β M adds `pullpay.yml` or installs the GitHub App (Β§26).
+4. **Map identity** β C links GitHub β wallet (OAuth + SIWE signature); receiving needs no gas (Β§30.5).
+5. **Work** β C opens a PR and it gets merged; GitHub fires the event.
+6. **Relayer verify** β Action calls the relayer `/settle`; relayer re-verifies the merge via the GitHub API before trusting the signal (Β§8.3, Β§13).
+7. **Assert / settle** β *Safeguarded:* `ESC.assertMerge` β `assertTruth` to UMA + bond, `status = Asserted`, liveness starts (Β§20). *Instant:* `ESC.settleInstant` / `approveAndRelease` pays immediately, no oracle (Β§24.3).
+8. **Resolve** β No dispute in liveness β UMA β `assertionResolvedCallback(true)` β USDC to C, bond back to M, `EAS.attest`, `status = Settled` (Β§20, Β§25). If disputed β DVM commitβreveal decides (Β§22.4).
+9. **Safety net** β If an Instant reward stalls past the grace deadline, C calls `escalateToUMA` and drops into the same UMA path (Β§24.3).
+10. **Surfaces update** β Dashboard + Public Bounty Board refresh from the subgraph; the PR gets a live status comment/check (Β§25.3, Β§27).
\ No newline at end of file
diff --git a/Panduan_Demo_PullPay.md b/Panduan_Demo_PullPay.md
new file mode 100644
index 0000000..409afc5
--- /dev/null
+++ b/Panduan_Demo_PullPay.md
@@ -0,0 +1,104 @@
+# π PANDUAN DEMO & PENJELASAN PROYEK: PULLPAY
+
+Dokumen ini adalah rangkuman dari semua berkas `.md` yang ada di proyek **PullPay** (`penjelasan_pullpay.md`, `isiProject.md`, `DESIGN.md`, `PRD.md`, `Planning.md`, dan `README.md`).
+
+Dokumen ini ditulis menggunakan **bahasa yang mudah dipahami orang awam/juri**, terstruktur, dan siap dijadikan panduan berbicara (contekkan) saat Anda melakukan demo aplikasi.
+
+---
+
+## π― 1. Apa itu PullPay? (Penjelasan 30 Detik)
+> *"PullPay adalah platform pembayaran hadiah (bounty) open-source otomatis berbasis blockchain **Optimism**. Konsepnya sederhana: **Merge Pull Request (PR) Anda, dan kontributor langsung dibayar dalam USDC secara otomatis, tanpa gas fee, dan tanpa perlu saling percaya.**"*
+
+### π Masalah yang Diselesaikan:
+Di dunia perangkat lunak gratisan (open-source), memberi upah kecil (misalnya $5 - $20) untuk perbaikan bug kecil sangat sulit dilakukan. Biaya transfer bank internasional atau PayPal sangat mahal (terkadang biaya admin lebih besar dari hadiahnya). Koordinasi manual lewat Discord atau email juga rawan penipuan atau memakan waktu.
+
+### π‘ Solusi PullPay:
+Menghubungkan aktivitas kode di **GitHub** langsung dengan dompet **on-chain**. Hadiah dikunci di awal oleh pemilik proyek (Maintainer) di blockchain, dan akan langsung cair ke pembuat kode (Contributor) ketika kodenya disetujui (*merged*).
+
+---
+
+## π 2. Alur Kerja PullPay (Bagaimana Cara Kerjanya?)
+
+Sistem berjalan dalam 3 fase utama yang menghubungkan dunia pemrograman (GitHub) dengan dunia Blockchain:
+
+```
+[ Maintainer ] -> Kunci dana USDC di Contract -> Tambah pullpay.yml ke repo
+ |
+ v (Tugas dipublikasikan)
+[ Contributor ] -> Buat perbaikan kode -> Kirim Pull Request (PR)
+ |
+ v (Pekerjaan disetujui & di-merge)
+[ GitHub Action ] -> Memicu Relayer -> Membuat pernyataan (Assertion) ke UMA Oracle
+ |
+ v (Challenge Window / Masa Sanggah 30-120 detik untuk demo)
+[ Tanpa Protes? ] -> USDC cair otomatis ke dompet Contributor (Klaim Gasless)
+ -> Cetak Sertifikat Digital Portofolio (EAS Attestation)
+```
+
+1. **Fase 1: Penguncian Dana (Escrow)**
+ * Pemilik proyek (Maintainer) memilih isu di GitHub yang ingin diselesaikan, menentukan nilai hadiah (misal: $50 USDC), lalu mengunci dana tersebut ke dalam **Smart Contract** di jaringan Optimism.
+ * Maintainer menambahkan file konfigurasi `pullpay.yml` ke repositori mereka agar sistem PullPay memantau repositori tersebut.
+2. **Fase 2: Pengerjaan & Merge**
+ * Developer (Contributor) melihat tugas tersebut, mengerjakan kodenya, dan mengirimkan **Pull Request (PR)**.
+ * Maintainer meninjau kode. Jika cocok, Maintainer melakukan **Merge PR** tersebut.
+3. **Fase 3: Verifikasi & Pembayaran**
+ * Robot GitHub Actions mendeteksi *merge* dan memicu verifikasi ke **UMA Optimistic Oracle** (Hakim Blockchain).
+ * Setelah lolos masa sanggah tanpa ada sengketa, uang USDC otomatis dikirim ke dompet Contributor tanpa biaya gas transaksi (Gasless), dan sertifikat bukti kontribusi dicetak di blockchain (**EAS**).
+
+---
+
+## π 3. Mengapa PullPay Unik? (Diferensiasi dengan Kompetitor)
+
+Di hadapan juri, Anda harus menekankan bahwa ide *"bayar saat merge"* memang sudah ada (seperti Octasol atau Opire). Namun, **semua kompetitor memiliki kelemahan fatal**: mereka bergantung pada server terpusat/bot yang bisa rusak/dicurangi, dan hanya mengecek *"apakah PR sudah di-merge"* (bukan apakah kualitas kerjanya bagus).
+
+PullPay menggebrak pasar dengan **3 Fitur Unggulan**:
+
+### A. βοΈ Verifikasi Terdesentralisasi dengan UMA Oracle (Hakim Netral)
+* **Masalah Kompetitor:** Jika bot server mereka mati, atau pemilik proyek melakukan *merge* tapi mengunci pembayaran secara sepihak, kontributor dirugikan. Atau sebaliknya, kontributor mengirim kode sampah lalu bekerja sama dengan admin palsu untuk mencairkan dana.
+* **Solusi PullPay:** Kami menggunakan **UMA Optimistic Oracle**. Setiap klaim pembayaran diajukan dengan jaminan (*bond*). Jika ada kecurangan (misalnya kode asal-asalan tapi tetap di-merge demi mencuri uang), siapa pun di dunia dapat mengajukan protes (*dispute*). Sengketa akan diselesaikan secara adil oleh komunitas global UMA.
+
+### B. β½ Pembayaran Tanpa Biaya Gas (Gasless Claims via ERC-4337)
+* **Masalah Kompetitor:** Untuk mencairkan uang crypto di blockchain, Anda harus memiliki koin asli (seperti Ethereum/ETH) untuk membayar biaya gas. Developer web tradisional (non-crypto) akan bingung jika harus membeli ETH dulu di bursa.
+* **Solusi PullPay:** Menggunakan teknologi Account Abstraction. Kontributor tidak perlu memiliki saldo ETH sama sekali. Biaya gas transaksi otomatis disponsori oleh protokol atau langsung dipotong dari hadiah USDC yang diterima.
+
+### C. π Sertifikat Portofolio Anti-Pemalsuan (EAS Attestation)
+* Setiap kali pembayaran sukses, PullPay menerbitkan sertifikat digital permanen di blockchain menggunakan **Ethereum Attestation Service (EAS)**.
+* Sertifikat ini mencatat secara resmi: nama repositori, jenis kontribusi, tanggal, dan jumlah bayaran. Ini menjadi **CV Digital portabel** bagi developer yang tidak bisa dimanipulasi untuk melamar kerja di masa depan.
+
+---
+
+## π¦ 4. Penjelasan Tiap Berkas `.md` di Repositori
+
+Berikut adalah peta isi dari masing-masing file markdown yang ada di proyek Anda:
+
+### 1. [`penjelasan_pullpay.md`](file:///d:/SEMESTER%204/BANDUNG%20DEVELOPER%20BLOCKCHAIN/TEMPELAN%20PROJECT/pullpay/penjelasan_pullpay.md) (Panduan Sederhana)
+* **Fokus:** Penjelasan super kasual untuk pemula.
+* **Isi Penting:** Menggunakan analogi **Budi** (Maintainer) dan **Andi** (Contributor) untuk menjelaskan bagaimana uang mengalir secara aman tanpa perlu rasa saling percaya.
+
+### 2. [`isiProject.md`](file:///d:/SEMESTER%204/BANDUNG%20DEVELOPER%20BLOCKCHAIN/TEMPELAN%20PROJECT/pullpay/isiProject.md) (Esensi & Strategi Pitch)
+* **Fokus:** Visi bisnis, perbandingan kompetitor, arsitektur, dan cara presentasi ke juri.
+* **Isi Penting:** Diagram alur lengkap (*mermaid diagram*), tabel komparasi kompetitor, konsep "Instant vs Safeguarded Mode", dan bukti dana (*Proof of Funding*).
+
+### 3. [`DESIGN.md`](file:///d:/SEMESTER%204/BANDUNG%20DEVELOPER%20BLOCKCHAIN/TEMPELAN%20PROJECT/pullpay/DESIGN.md) (Panduan Antarmuka / UI)
+* **Fokus:** Standar keindahan visual frontend.
+* **Isi Penting:** Menolak desain templat AI yang membosankan. Desain PullPay dibuat premium ala Vercel/Linear: menggunakan warna latar gelap murni (`#0B0B0C`), aksen ungu, huruf monospace untuk angka on-chain agar terlihat rapi dan presisi.
+
+### 4. [`PRD.md`](file:///d:/SEMESTER%204/BANDUNG%20DEVELOPER%20BLOCKCHAIN/TEMPELAN%20PROJECT/pullpay/PRD.md) (Spesifikasi Kebutuhan Produk)
+* **Fokus:** Detail teknis super mendalam untuk implementator kode.
+* **Isi Penting:** Cara integrasi *Smart Contract* dengan kontrak UMA, skema penerbitan sertifikat EAS, struktur *database*/event blockchain yang perlu dibaca frontend, dan cara menangani kegagalan (misalnya dana kedaluwarsa).
+
+### 5. [`Planning.md`](file:///d:/SEMESTER%204/BANDUNG%20DEVELOPER%20BLOCKCHAIN/TEMPELAN%20PROJECT/pullpay/Planning.md) (Langkah Pengembangan)
+* **Fokus:** Catatan harian/mingguan tim selama hackathon.
+* **Isi Penting:** Pembagian tugas (kontrak pintar, relayer backend, UI frontend) dan target yang harus diselesaikan untuk MVP (Minimum Viable Product).
+
+### 6. [`README.md`](file:///d:/SEMESTER%204/BANDUNG%20DEVELOPER%20BLOCKCHAIN/TEMPELAN%20PROJECT/pullpay/README.md) (Pintu Masuk Repositori)
+* **Fokus:** Petunjuk cara menjalankan aplikasi secara lokal.
+* **Isi Penting:** Perintah instalasi (`npm install`), cara menjalankan server lokal (`npm run dev`), dan prasyarat variabel lingkungan (Environment Variables).
+
+---
+
+## π‘ 5. Tips Sukses Saat Melakukan Demo (Tips Presentasi)
+1. **Mulailah dengan Masalah Nyata:** *"Banyak developer open-source menyumbangkan waktu mereka gratis, tapi saat maintainer ingin memberi tips $10, prosesnya sangat rumit dan mahal di luar crypto."*
+2. **Tunjukkan Transparansi Dana:** Tunjukkan bahwa uang benar-benar dikunci di blockchain (bukan sekadar angka di database website).
+3. **Fokus pada Demo Cepat:** Karena batas waktu demo biasanya singkat, jelaskan bahwa untuk demo ini, *Challenge Window* (masa sanggah) UMA Oracle disingkat menjadi 1 menit (di dunia nyata biasanya beberapa jam/hari) agar uang USDC bisa cair langsung di depan mata juri.
+4. **Soroti Keunggulan Gasless:** Klik tombol klaim tanpa MetaMask meminta saldo ETH untuk gas fee, dan tunjukkan sertifikat EAS yang terbit setelahnya.
diff --git a/Planning.md b/Planning.md
new file mode 100644
index 0000000..f865d60
--- /dev/null
+++ b/Planning.md
@@ -0,0 +1,281 @@
+# PullPay x Optimism β Planning & Diferensiasi (Hackathon)
+
+
+
+## 1. Vision & masalah
+
+Ekosistem open source bergantung pada ratusan kontribusi kecil (bug fix, docs, translasi, tooling). Tapi reward $5β20 sering tidak ekonomis: fee Stripe/PayPal/bank memakan porsi besar, dan koordinasi payout manual (Discord, spreadsheet, DM) lebih mahal daripada reward-nya. Akibatnya banyak kontribusi berharga tidak pernah dihargai.
+
+**Solusi:** escrow on-chain + otomatisasi GitHub. Maintainer mengunci USDC di smart contract, menambahkan 1 file workflow, dan pembayaran keluar otomatis saat PR di-merge & terverifikasi.
+
+## 2. Lanskap kompetitor (ide ini SUDAH ada)
+
+Jujur: konsep "merge PR β bayar otomatis" bukan hal baru. Yang sudah ada:
+
+| Proyek | Chain | Catatan |
+| --- | --- | --- |
+| Octasol | Solana | Escrow, PR merged β bayar, refund kalau ditolak |
+| [Collaborators.build](http://Collaborators.build) | On-chain USDC | Bot lacak merge β rilis USDC |
+| [boss.dev](http://boss.dev) / boss-bounty | β | Bounty dari teks issue, bayar saat issue ditutup |
+| Opire | β | Platform bounty issue populer |
+| Gitpay | β | Fund issue, reward PR merged |
+| [tea.xyz](http://tea.xyz) | OP Stack L2 | L2 khusus reward OSS |
+| PullPay (referensi) | Stellar/Soroban | 1 file `pullpay.yml`, oracle Cloudflare Worker |
+
+**Kelemahan umum SEMUA kompetitor:** verifikasi bergantung pada **bot/server terpusat**, dan hanya cek `merged == true` (merge β kerja berkualitas). Inilah celah yang kita serang.
+
+## 3. Ide diferensiasi (bikin unik)
+
+### β Wedge utama β Verifikasi terdesentralisasi + lapisan sengketa (UMA)
+
+Ganti Cloudflare Worker terpusat dengan **UMA Optimistic Oracle**. Pernyataan *"PR #123 di-merge DAN benar-benar menyelesaikan issue sesuai kriteria"* di-assert dengan bond + challenge window. Siapa pun (maintainer lain, kontributor, sponsor) bisa menyanggah kalau merge-nya curang atau kerjanya tidak beres β sengketa diselesaikan lewat voting, bukan satu server. **Ini pembeda paling tajam:** kita pindah dari "apakah PR merged?" ke "apakah PR ini benar layak dibayar?".
+
+### β Reputasi on-chain via EAS (Ethereum Attestation Service)
+
+Setiap reward yang settle mencetak **attestation** di Optimism yang mencatat: repo, jenis kontribusi, nilai, tanggal. Hasilnya **CV developer yang portabel, verifiable, dan tidak bisa dipalsukan** β bisa dipakai lintas platform. Sangat Optimism/Superchain-native (EAS besar di OP & Base).
+
+### β Klaim gasless untuk kontributor (Account Abstraction / ERC-4337 + paymaster)
+
+Kontributor **tidak perlu punya ETH** untuk klaim. Paymaster mensponsori gas (bisa dipotong dari reward). Ini menghapus friksi terbesar untuk onboarding developer non-crypto β mereka merge PR, lalu USDC "muncul" tanpa perlu tahu soal gas.
+
+### Auto-split multi-kontributor
+
+Banyak PR punya co-author. Contract bisa **membagi reward otomatis** berdasarkan co-author trailer / kontribusi commit. Ini masalah nyata yang belum dipecahkan kompetitor (disebut eksplisit di diskusi GitHub Sponsors).
+
+### (Roadmap) Pool RetroPGF-style
+
+Pendanaan retroaktif ala Optimism: sponsor mengisi **pool per-repo**, lalu didistribusikan ke kontribusi yang sudah merged berdasarkan dampak. Sejalan langsung dengan etos Retroactive Public Goods Funding milik Optimism β cerita yang kuat untuk juri OP.
+
+### (Opsional) Escrow yield
+
+USDC yang terkunci menghasilkan yield (mis. Aave di Optimism) selama menunggu β mendanai gas/protokol tanpa memungut fee dari kontributor.
+
+
+
+## 4. Arsitektur (versi Optimism)
+
+```mermaid
+flowchart TD
+ subgraph S1[FASE 1 β Maintainer kunci dana]
+ A[Connect wallet MetaMask] --> B[Buat reward, link GitHub Issue, set USDC]
+ B --> C[approve USDC ke escrow]
+ C --> D[createReward -> USDC TERKUNCI di escrow Optimism]
+ D --> E[Tambah 1 file pullpay.yml ke repo]
+ end
+ E --> F[Reward terlihat on-chain: bounty asli & terdanai]
+ subgraph S2[FASE 2 β Kontributor kerja]
+ F --> G[Kontributor link GitHub username -> wallet]
+ G --> H[Buka Pull Request]
+ H --> I{Maintainer merge?}
+ end
+ I -->|Tutup / timeout| R[refund -> USDC balik ke maintainer]
+ I -->|MERGE| J[GitHub Action terpicu]
+ subgraph S3[FASE 3 β Verifikasi & payout]
+ J --> K[Assert PR ke UMA Optimistic Oracle + bond]
+ K --> L{Ada sanggahan dalam challenge window?}
+ L -->|Ya, disengketakan| V[Voting menyelesaikan sengketa]
+ L -->|Tidak| M[Klaim dianggap benar]
+ V --> M
+ M --> N[Contract release USDC gasless ke kontributor]
+ N --> O[EAS mencetak attestation reputasi]
+ end
+ O --> Q([Selesai: kontributor dibayar + reputasi on-chain])
+```
+
+## 5. Tech stack
+
+- **Smart contract:** Solidity + Foundry/Hardhat; deploy ke **Optimism Sepolia** (demo) / OP Mainnet (produksi)
+- **Aset:** USDC (ERC-20) di Optimism; pola `approve` + `transferFrom`
+- **Verifikasi:** UMA Optimistic Oracle V3 (`assertTruth`) + escalation manager
+- **Reputasi:** Ethereum Attestation Service (EAS) di Optimism
+- **Gasless:** ERC-4337 (bundler + paymaster)
+- **Otomatisasi:** GitHub Actions (`pullpay.yml`) + relayer/worker sebagai proposer assertion
+- **Frontend:** Next.js + **RainbowKit + wagmi v2 + viem** (+ `@tanstack/react-query`); wallet MetaMask
+
+## 6. Scope hackathon vs roadmap
+
+### MVP (target 2β3 hari)
+
+- [ ] `PullPayEscrow` contract: `createReward` / `release` / `refund`
+- [ ] Integrasi UMA OOv3 dengan **liveness pendek** (30β120 dtk) agar demo cepat, atau sandbox oracle
+- [ ] `pullpay.yml` + worker minimal (assert ke UMA saat merge)
+- [ ] Frontend: create reward (dengan `approve`) + dashboard status on-chain
+- [ ] 1 EAS attestation saat settle (versi sederhana)
+- [ ] Deploy di Optimism Sepolia
+- [ ] **Demo end-to-end:** buat reward β buka PR di repo demo β merge β (challenge window) β USDC masuk β attestation tercetak
+
+### Roadmap (slide)
+
+- Gasless penuh (paymaster produksi)
+- Auto-split multi-kontributor
+- Mode Timelock 24 jam + UI sengketa
+- Pool RetroPGF-style per-repo
+- Escrow yield (Aave)
+- Dukungan GitLab/Gitea
+
+## 7. Ekonomi & gas
+
+- Optimism: interaksi contract umumnya **beberapa sen** (transfer ERC-20 bisa < $0.000001 saat sepi). Erosi < 1% pada reward $5β20 β jauh di bawah Stripe (36% pada $5) atau Ethereum L1 ($15+ gas).
+- Fee = L2 execution (murah) + L1 data fee (fluktuatif, turun drastis pasca EIP-4844/blob).
+- Demo pakai testnet β gas gratis.
+
+## 8. Risiko & mitigasi
+
+| Risiko | Mitigasi |
+| --- | --- |
+| Ide sudah ramai | Diferensiasi via UMA + EAS + gasless (bukan sekadar "bayar saat merge") |
+| Resolusi UMA lambat (hari) untuk demo | Liveness pendek / sandbox oracle saat demo; jelaskan versi mainnet di slide |
+| Self-merge / sybil (maintainer bayar alt sendiri) | Bond + sengketa UMA; batas per-reward di contract |
+| Kontributor tak punya ETH | Klaim gasless (paymaster) |
+| Scope overrun | Kunci 1 alur end-to-end; sisanya jadi roadmap |
+
+## 9. Pitch (30 detik)
+
+> "Reward PR otomatis sudah ada di mana-mana β tapi semuanya percaya pada satu server dan cuma cek 'apakah di-merge'. PullPay di Optimism memverifikasi kelayakan reward secara **terdesentralisasi lewat UMA**, membayar kontributor **tanpa mereka perlu punya ETH**, dan mencetak **reputasi on-chain** yang portabel. Zero setup untuk maintainer, trust-minimized untuk semua."
+>
+
+## 10. Siapa yang bisa protes (dispute) & perlindungan contributor
+
+Bedakan dulu dua peran yang sering tercampur:
+
+- **Disputer (yang protes):** mengangkat "ini salah".
+- **Hakim / arbiter (yang memutuskan):** menentukan siapa yang benar saat ada selisih. **Peran netral inilah** alasan sebenarnya UMA dipakai β bukan supaya ada orang iseng protes.
+
+Kita tetap memakai **hakim netral (UMA)** bahkan untuk reward dari kantong maintainer sendiri, khusus untuk **melindungi contributor**.
+
+### Ya, contributor bisa protes
+
+Ada **dua arah kegagalan**, dan sistem harus adil untuk keduanya:
+
+| Mode gagal | Yang dirugikan | Yang protes | Melindungi |
+| --- | --- | --- | --- |
+| Bayar untuk kerja palsu / tidak layak | Pemberi dana | Maintainer / sponsor / watchdog | Pemberi dana |
+| Kerja sudah sesuai tapi tak dibayar | Contributor | **Contributor sendiri (klaim mandiri)** | Contributor |
+
+Jadi kalau pekerjaan sudah sesuai kriteria & PR merged tapi tidak dibayar (relayer mati, maintainer mengulur), **contributor bisa mengajukan klaim sendiri** ("PR #X sudah merged & sesuai kriteria β bayar saya"), memasang bond, lalu kalau maintainer tidak setuju dia yang harus dispute β diputuskan **hakim netral**. Bond dikembalikan kalau klaimnya jujur, jadi contributor yang jujur **tidak keluar biaya bersih**. Ini menghapus ketergantungan contributor pada niat baik maintainer.
+
+
+
+## 11. Instant vs Safeguarded β UMA sebagai jaring pengaman (bukan wajib)
+
+UMA **tidak selalu nyala**. Kalau maintainer mendanai dari kantong sendiri **dan** dia yang meng-approve (merge), maka **merge = persetujuan = bayar langsung** β tanpa oracle. UMA baru aktif sebagai *fallback*.
+
+| Situasi | Mode | Pakai UMA? |
+| --- | --- | --- |
+| Danai sendiri **& approve/merge** | Instant | Tidak β bayar langsung |
+| Maintainer diam / mengulur, kerja sudah sesuai | Instant β eskalasi | Ya β contributor klaim mandiri |
+| Dana dari pool / sponsor (pembayar β pemutus) | Safeguarded | Ya |
+| Ada yang menyanggah | Safeguarded | Ya |
+
+Default-nya jadi super simpel: **merge β USDC cair**. UMA berfungsi sebagai jaring pengaman yang baru aktif kalau (a) maintainer tidak bertindak sampai lewat *grace deadline* (contributor bisa eskalasi sendiri), (b) dananya milik bersama, atau (c) ada sanggahan.
+
+
+
+## 12. Bukti dana (Proof of Funding) β contributor bisa cek sendiri
+
+Contributor tidak boleh mengerjakan bounty yang ternyata kosong. Karena escrow-nya **on-chain**, status βterdanaiβ bisa **diverifikasi publik & mandiri** β tidak perlu percaya UI PullPay maupun kata-kata maintainer.
+
+Saat `createReward`, USDC ditarik ke escrow dan reward disimpan `status = Funded`, menghasilkan 3 jejak permanen & publik: event `RewardCreated`, event `Transfer` USDC ke alamat escrow, dan state `rewards[id]` yang bisa dibaca siapa pun.
+
+| Cara cek | Bagaimana | Tingkat kepercayaan |
+| --- | --- | --- |
+| Di UI PullPay | Badge ββ
Funded β $20 USDC lockedβ + link ke tx | Praktis |
+| Block explorer | Buka escrow di Optimistic Etherscan β lihat tx `RewardCreated` β’ transfer USDC masuk | Trustless |
+| Baca contract langsung | Panggil view `getReward(id)` / `isFunded(id)` via RPC/cast | Trustless |
+| Di issue GitHub | GitHub App komentar βπ° $20 USDC funded Β· verify on-chain β linkβ | Praktis |
+
+**Anti-spoofing:** `rewardId = keccak256(repo, issueNumber, nonce)` mengikat dana ke satu repo+issue tertentu, jadi contributor bisa menghitung ulang `id` dari issue-nya dan mencocokkannya 1:1 dengan bukti on-chain. Bounty Board (Β§27 PRD) pun hanya menampilkan reward berstatus `Funded`.
+
+
+
+## 13. Pilihan framework frontend β React atau Next.js?
+
+**Rekomendasi: Next.js 16 (App Router) β versi stable terbaru (16.2.x, React 19.2); scaffold via `npx create-next-app@latest`.** React biasa (Vite) memang lebih simpel, tapi PullPay butuh hal-hal yang bikin Next.js lebih unggul:
+
+1. **SEO Bounty Board** β bounty publik harus ke-index Google β mesin discovery. React SPA lemah di sini; Next.js kasih SSR/ISR.
+2. **Endpoint relayer/webhook GitHub** β pakai Route Handlers (`app/api`) tanpa backend terpisah.
+3. **Secret aman** β kunci RPC/relayer tetap di server, tidak bocor ke client.
+4. **Load awal ringan** β React Server Components + streaming (bundle JS lebih kecil).
+5. **Deploy 1 klik** di Vercel + optimasi font/image bawaan.
+
+React SPA murni hanya cocok untuk app internal tanpa SEO & yang sudah punya backend sendiri β bukan kasus kita.
+
+
+
+## 14. Design system (mau tampil seperti apa)
+
+**Prinsip:** *precise, editorial, engineered* β terkesan dibuat tim desain developer-tools (Linear / Vercel / Stripe / Rainbow), **bukan template AI**. Dark-first, whitespace lega, border tipis 1px, angka on-chain selalu monospace.
+
+### 14.1 Token warna
+
+| Token | Nilai | Pakai untuk |
+| --- | --- | --- |
+| Base (dark) | `#0B0B0C` | Background utama (bukan hitam murni) |
+| Surface | `#141416` | Kartu / panel |
+| Border hairline | `rgba(255,255,255,0.08)` | Garis pemisah 1px |
+| Paper (light) | `#FAFAF8` / ink `#16161A` | Tema terang (opsional) |
+| Aksen | Optimism red `#FF0420` | 1 aksi utama per layar (hemat, bukan gradient) |
+| Sinyal | green `#3FB950` Β· amber `#D29922` Β· red `#F85149` | Status: Funded/Paid Β· Verifying Β· Disputed |
+
+### 14.2 Tipografi
+
+- **UI + heading:** grotesque modern β **Geist** (alt: SΓΆhne, Neue Haas Grotesk). Heading besar, letter-spacing rapat; eyebrow uppercase 11β12px.
+- **Data on-chain** (alamat wallet, jumlah USDC, tx hash, rewardId): **monospace** β Geist Mono / IBM Plex Mono, aktifkan tabular numbers (`tnum`).
+- Uang selalu monospace + chip mata uang kecil.
+
+### 14.3 Bentuk, spacing & komponen
+
+- **Radius:** 8β10px kartu, 6px input/tombol (hindari serba-pill).
+- **Spacing:** skala 4px (4/8/12/16/24/40).
+- **Status:** pill badge + titik 6px berwarna.
+- **Tombol:** solid aksen untuk primary (1 per view), sisanya ghost/outline. Ikon garis **Lucide / Phosphor** (1 set saja).
+- **Layout:** left-aligned, asimetris, grid editorial, data-dense ala Linear.
+
+### 14.4 Motion (tenang, 150β250ms ease-out)
+
+Count-up angka statistik Β· marquee repo yang lagi didanai Β· fade/blur-up saat reveal Β· magnet hover-lift di tombol primary. Tidak bouncy, tidak flashy.
+
+### 14.5 Larangan (biar tak terkesan AI)
+
+Tanpa gradient ungu/biru, tanpa glassmorphism/blur panel, tanpa neon glow, tanpa emoji sebagai ikon, tanpa ilustrasi 3D generik, tanpa βInter di mana-mana + serba centerβ.
+
+### 14.6 Implementasi
+
+**Tailwind CSS + shadcn/ui** untuk komponen dasar, [**reactbits.dev**](http://reactbits.dev) (secukupnya) untuk motion. Font via `next/font` (Geist sudah bawaan Next.js).
+
+
\ No newline at end of file
diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx
index b0a0da4..3a81d02 100644
--- a/frontend/app/page.tsx
+++ b/frontend/app/page.tsx
@@ -26,6 +26,7 @@ import StarBorder from "@/components/reactbits/StarBorder";
const AURORA_STOPS = ["#2E1065", "#8B5CF6", "#F0ABFC"];
// tes tes
+// halo halo
const VALUE_CARDS = [
{
icon: ShieldCheck,
diff --git a/isiProject.md b/isiProject.md
new file mode 100644
index 0000000..48d94d5
--- /dev/null
+++ b/isiProject.md
@@ -0,0 +1,279 @@
+
+
+## 1. Vision & masalah
+
+Ekosistem open source bergantung pada ratusan kontribusi kecil (bug fix, docs, translasi, tooling). Tapi reward $5β20 sering tidak ekonomis: fee Stripe/PayPal/bank memakan porsi besar, dan koordinasi payout manual (Discord, spreadsheet, DM) lebih mahal daripada reward-nya. Akibatnya banyak kontribusi berharga tidak pernah dihargai.
+
+**Solusi:** escrow on-chain + otomatisasi GitHub. Maintainer mengunci USDC di smart contract, menambahkan 1 file workflow, dan pembayaran keluar otomatis saat PR di-merge & terverifikasi.
+
+## 2. Lanskap kompetitor (ide ini SUDAH ada)
+
+Jujur: konsep "merge PR β bayar otomatis" bukan hal baru. Yang sudah ada:
+
+| Proyek | Chain | Catatan |
+| --- | --- | --- |
+| Octasol | Solana | Escrow, PR merged β bayar, refund kalau ditolak |
+| Collaborators.build | On-chain USDC | Bot lacak merge β rilis USDC |
+| boss.dev / boss-bounty | β | Bounty dari teks issue, bayar saat issue ditutup |
+| Opire | β | Platform bounty issue populer |
+| Gitpay | β | Fund issue, reward PR merged |
+| tea.xyz | OP Stack L2 | L2 khusus reward OSS |
+| PullPay (referensi) | Stellar/Soroban | 1 file `pullpay.yml`, oracle Cloudflare Worker |
+
+**Kelemahan umum SEMUA kompetitor:** verifikasi bergantung pada **bot/server terpusat**, dan hanya cek `merged == true` (merge β kerja berkualitas). Inilah celah yang kita serang.
+
+## 3. Ide diferensiasi (bikin unik)
+
+### β Wedge utama β Verifikasi terdesentralisasi + lapisan sengketa (UMA)
+
+Ganti Cloudflare Worker terpusat dengan **UMA Optimistic Oracle**. Pernyataan *"PR #123 di-merge DAN benar-benar menyelesaikan issue sesuai kriteria"* di-assert dengan bond + challenge window. Siapa pun (maintainer lain, kontributor, sponsor) bisa menyanggah kalau merge-nya curang atau kerjanya tidak beres β sengketa diselesaikan lewat voting, bukan satu server. **Ini pembeda paling tajam:** kita pindah dari "apakah PR merged?" ke "apakah PR ini benar layak dibayar?".
+
+### β Reputasi on-chain via EAS (Ethereum Attestation Service)
+
+Setiap reward yang settle mencetak **attestation** di Optimism yang mencatat: repo, jenis kontribusi, nilai, tanggal. Hasilnya **CV developer yang portabel, verifiable, dan tidak bisa dipalsukan** β bisa dipakai lintas platform. Sangat Optimism/Superchain-native (EAS besar di OP & Base).
+
+### β Klaim gasless untuk kontributor (Account Abstraction / ERC-4337 + paymaster)
+
+Kontributor **tidak perlu punya ETH** untuk klaim. Paymaster mensponsori gas (bisa dipotong dari reward). Ini menghapus friksi terbesar untuk onboarding developer non-crypto β mereka merge PR, lalu USDC "muncul" tanpa perlu tahu soal gas.
+
+### Auto-split multi-kontributor
+
+Banyak PR punya co-author. Contract bisa **membagi reward otomatis** berdasarkan co-author trailer / kontribusi commit. Ini masalah nyata yang belum dipecahkan kompetitor (disebut eksplisit di diskusi GitHub Sponsors).
+
+### (Roadmap) Pool RetroPGF-style
+
+Pendanaan retroaktif ala Optimism: sponsor mengisi **pool per-repo**, lalu didistribusikan ke kontribusi yang sudah merged berdasarkan dampak. Sejalan langsung dengan etos Retroactive Public Goods Funding milik Optimism β cerita yang kuat untuk juri OP.
+
+### (Opsional) Escrow yield
+
+USDC yang terkunci menghasilkan yield (mis. Aave di Optimism) selama menunggu β mendanai gas/protokol tanpa memungut fee dari kontributor.
+
+
+
+## 4. Arsitektur (versi Optimism)
+
+```mermaid
+flowchart TD
+ subgraph S1[FASE 1 β Maintainer kunci dana]
+ A[Connect wallet MetaMask] --> B[Buat reward, link GitHub Issue, set USDC]
+ B --> C[approve USDC ke escrow]
+ C --> D[createReward -> USDC TERKUNCI di escrow Optimism]
+ D --> E[Tambah 1 file pullpay.yml ke repo]
+ end
+ E --> F[Reward terlihat on-chain: bounty asli & terdanai]
+ subgraph S2[FASE 2 β Kontributor kerja]
+ F --> G[Kontributor link GitHub username -> wallet]
+ G --> H[Buka Pull Request]
+ H --> I{Maintainer merge?}
+ end
+ I -->|Tutup / timeout| R[refund -> USDC balik ke maintainer]
+ I -->|MERGE| J[GitHub Action terpicu]
+ subgraph S3[FASE 3 β Verifikasi & payout]
+ J --> K[Assert PR ke UMA Optimistic Oracle + bond]
+ K --> L{Ada sanggahan dalam challenge window?}
+ L -->|Ya, disengketakan| V[Voting menyelesaikan sengketa]
+ L -->|Tidak| M[Klaim dianggap benar]
+ V --> M
+ M --> N[Contract release USDC gasless ke kontributor]
+ N --> O[EAS mencetak attestation reputasi]
+ end
+ O --> Q([Selesai: kontributor dibayar + reputasi on-chain])
+```
+
+## 5. Tech stack
+
+- **Smart contract:** Solidity + Foundry/Hardhat; deploy ke **Optimism Sepolia** (demo) / OP Mainnet (produksi)
+- **Aset:** USDC (ERC-20) di Optimism; pola `approve` + `transferFrom`
+- **Verifikasi:** UMA Optimistic Oracle V3 (`assertTruth`) + escalation manager
+- **Reputasi:** Ethereum Attestation Service (EAS) di Optimism
+- **Gasless:** ERC-4337 (bundler + paymaster)
+- **Otomatisasi:** GitHub Actions (`pullpay.yml`) + relayer/worker sebagai proposer assertion
+- **Frontend:** Next.js + **RainbowKit + wagmi v2 + viem** (+ `@tanstack/react-query`); wallet MetaMask
+
+## 6. Scope hackathon vs roadmap
+
+### MVP (target 2β3 hari)
+
+- [ ] `PullPayEscrow` contract: `createReward` / `release` / `refund`
+- [ ] Integrasi UMA OOv3 dengan **liveness pendek** (30β120 dtk) agar demo cepat, atau sandbox oracle
+- [ ] `pullpay.yml` + worker minimal (assert ke UMA saat merge)
+- [ ] Frontend: create reward (dengan `approve`) + dashboard status on-chain
+- [ ] 1 EAS attestation saat settle (versi sederhana)
+- [ ] Deploy di Optimism Sepolia
+- [ ] **Demo end-to-end:** buat reward β buka PR di repo demo β merge β (challenge window) β USDC masuk β attestation tercetak
+
+### Roadmap (slide)
+
+- Gasless penuh (paymaster produksi)
+- Auto-split multi-kontributor
+- Mode Timelock 24 jam + UI sengketa
+- Pool RetroPGF-style per-repo
+- Escrow yield (Aave)
+- Dukungan GitLab/Gitea
+
+## 7. Ekonomi & gas
+
+- Optimism: interaksi contract umumnya **beberapa sen** (transfer ERC-20 bisa < $0.000001 saat sepi). Erosi < 1% pada reward $5β20 β jauh di bawah Stripe (36% pada $5) atau Ethereum L1 ($15+ gas).
+- Fee = L2 execution (murah) + L1 data fee (fluktuatif, turun drastis pasca EIP-4844/blob).
+- Demo pakai testnet β gas gratis.
+
+## 8. Risiko & mitigasi
+
+| Risiko | Mitigasi |
+| --- | --- |
+| Ide sudah ramai | Diferensiasi via UMA + EAS + gasless (bukan sekadar "bayar saat merge") |
+| Resolusi UMA lambat (hari) untuk demo | Liveness pendek / sandbox oracle saat demo; jelaskan versi mainnet di slide |
+| Self-merge / sybil (maintainer bayar alt sendiri) | Bond + sengketa UMA; batas per-reward di contract |
+| Kontributor tak punya ETH | Klaim gasless (paymaster) |
+| Scope overrun | Kunci 1 alur end-to-end; sisanya jadi roadmap |
+
+## 9. Pitch (30 detik)
+
+> "Reward PR otomatis sudah ada di mana-mana β tapi semuanya percaya pada satu server dan cuma cek 'apakah di-merge'. PullPay di Optimism memverifikasi kelayakan reward secara **terdesentralisasi lewat UMA**, membayar kontributor **tanpa mereka perlu punya ETH**, dan mencetak **reputasi on-chain** yang portabel. Zero setup untuk maintainer, trust-minimized untuk semua."
+>
+
+## 10. Siapa yang bisa protes (dispute) & perlindungan contributor
+
+Bedakan dulu dua peran yang sering tercampur:
+
+- **Disputer (yang protes):** mengangkat "ini salah".
+- **Hakim / arbiter (yang memutuskan):** menentukan siapa yang benar saat ada selisih. **Peran netral inilah** alasan sebenarnya UMA dipakai β bukan supaya ada orang iseng protes.
+
+Kita tetap memakai **hakim netral (UMA)** bahkan untuk reward dari kantong maintainer sendiri, khusus untuk **melindungi contributor**.
+
+### Ya, contributor bisa protes
+
+Ada **dua arah kegagalan**, dan sistem harus adil untuk keduanya:
+
+| Mode gagal | Yang dirugikan | Yang protes | Melindungi |
+| --- | --- | --- | --- |
+| Bayar untuk kerja palsu / tidak layak | Pemberi dana | Maintainer / sponsor / watchdog | Pemberi dana |
+| Kerja sudah sesuai tapi tak dibayar | Contributor | **Contributor sendiri (klaim mandiri)** | Contributor |
+
+Jadi kalau pekerjaan sudah sesuai kriteria & PR merged tapi tidak dibayar (relayer mati, maintainer mengulur), **contributor bisa mengajukan klaim sendiri** ("PR #X sudah merged & sesuai kriteria β bayar saya"), memasang bond, lalu kalau maintainer tidak setuju dia yang harus dispute β diputuskan **hakim netral**. Bond dikembalikan kalau klaimnya jujur, jadi contributor yang jujur **tidak keluar biaya bersih**. Ini menghapus ketergantungan contributor pada niat baik maintainer.
+
+
+
+## 11. Instant vs Safeguarded β UMA sebagai jaring pengaman (bukan wajib)
+
+UMA **tidak selalu nyala**. Kalau maintainer mendanai dari kantong sendiri **dan** dia yang meng-approve (merge), maka **merge = persetujuan = bayar langsung** β tanpa oracle. UMA baru aktif sebagai *fallback*.
+
+| Situasi | Mode | Pakai UMA? |
+| --- | --- | --- |
+| Danai sendiri **& approve/merge** | Instant | Tidak β bayar langsung |
+| Maintainer diam / mengulur, kerja sudah sesuai | Instant β eskalasi | Ya β contributor klaim mandiri |
+| Dana dari pool / sponsor (pembayar β pemutus) | Safeguarded | Ya |
+| Ada yang menyanggah | Safeguarded | Ya |
+
+Default-nya jadi super simpel: **merge β USDC cair**. UMA berfungsi sebagai jaring pengaman yang baru aktif kalau (a) maintainer tidak bertindak sampai lewat *grace deadline* (contributor bisa eskalasi sendiri), (b) dananya milik bersama, atau (c) ada sanggahan.
+
+
+
+## 12. Bukti dana (Proof of Funding) β contributor bisa cek sendiri
+
+Contributor tidak boleh mengerjakan bounty yang ternyata kosong. Karena escrow-nya **on-chain**, status βterdanaiβ bisa **diverifikasi publik & mandiri** β tidak perlu percaya UI PullPay maupun kata-kata maintainer.
+
+Saat `createReward`, USDC ditarik ke escrow dan reward disimpan `status = Funded`, menghasilkan 3 jejak permanen & publik: event `RewardCreated`, event `Transfer` USDC ke alamat escrow, dan state `rewards[id]` yang bisa dibaca siapa pun.
+
+| Cara cek | Bagaimana | Tingkat kepercayaan |
+| --- | --- | --- |
+| Di UI PullPay | Badge ββ
Funded β $20 USDC lockedβ + link ke tx | Praktis |
+| Block explorer | Buka escrow di Optimistic Etherscan β lihat tx `RewardCreated` β’ transfer USDC masuk | Trustless |
+| Baca contract langsung | Panggil view `getReward(id)` / `isFunded(id)` via RPC/cast | Trustless |
+| Di issue GitHub | GitHub App komentar βπ° $20 USDC funded Β· verify on-chain β linkβ | Praktis |
+
+**Anti-spoofing:** `rewardId = keccak256(repo, issueNumber, nonce)` mengikat dana ke satu repo+issue tertentu, jadi contributor bisa menghitung ulang `id` dari issue-nya dan mencocokkannya 1:1 dengan bukti on-chain. Bounty Board (Β§27 PRD) pun hanya menampilkan reward berstatus `Funded`.
+
+
+
+## 13. Pilihan framework frontend β React atau Next.js?
+
+**Rekomendasi: Next.js 16 (App Router) β versi stable terbaru (16.2.x, React 19.2); scaffold via `npx create-next-app@latest`.** React biasa (Vite) memang lebih simpel, tapi PullPay butuh hal-hal yang bikin Next.js lebih unggul:
+
+1. **SEO Bounty Board** β bounty publik harus ke-index Google β mesin discovery. React SPA lemah di sini; Next.js kasih SSR/ISR.
+2. **Endpoint relayer/webhook GitHub** β pakai Route Handlers (`app/api`) tanpa backend terpisah.
+3. **Secret aman** β kunci RPC/relayer tetap di server, tidak bocor ke client.
+4. **Load awal ringan** β React Server Components + streaming (bundle JS lebih kecil).
+5. **Deploy 1 klik** di Vercel + optimasi font/image bawaan.
+
+React SPA murni hanya cocok untuk app internal tanpa SEO & yang sudah punya backend sendiri β bukan kasus kita.
+
+
+
+## 14. Design system (mau tampil seperti apa)
+
+**Prinsip:** *precise, editorial, engineered* β terkesan dibuat tim desain developer-tools (Linear / Vercel / Stripe / Rainbow), **bukan template AI**. Dark-first, whitespace lega, border tipis 1px, angka on-chain selalu monospace.
+
+### 14.1 Token warna
+
+| Token | Nilai | Pakai untuk |
+| --- | --- | --- |
+| Base (dark) | `#0B0B0C` | Background utama (bukan hitam murni) |
+| Surface | `#141416` | Kartu / panel |
+| Border hairline | `rgba(255,255,255,0.08)` | Garis pemisah 1px |
+| Paper (light) | `#FAFAF8` / ink `#16161A` | Tema terang (opsional) |
+| Aksen | Optimism red `#FF0420` | 1 aksi utama per layar (hemat, bukan gradient) |
+| Sinyal | green `#3FB950` Β· amber `#D29922` Β· red `#F85149` | Status: Funded/Paid Β· Verifying Β· Disputed |
+
+### 14.2 Tipografi
+
+- **UI + heading:** grotesque modern β **Geist** (alt: SΓΆhne, Neue Haas Grotesk). Heading besar, letter-spacing rapat; eyebrow uppercase 11β12px.
+- **Data on-chain** (alamat wallet, jumlah USDC, tx hash, rewardId): **monospace** β Geist Mono / IBM Plex Mono, aktifkan tabular numbers (`tnum`).
+- Uang selalu monospace + chip mata uang kecil.
+
+### 14.3 Bentuk, spacing & komponen
+
+- **Radius:** 8β10px kartu, 6px input/tombol (hindari serba-pill).
+- **Spacing:** skala 4px (4/8/12/16/24/40).
+- **Status:** pill badge + titik 6px berwarna.
+- **Tombol:** solid aksen untuk primary (1 per view), sisanya ghost/outline. Ikon garis **Lucide / Phosphor** (1 set saja).
+- **Layout:** left-aligned, asimetris, grid editorial, data-dense ala Linear.
+
+### 14.4 Motion (tenang, 150β250ms ease-out)
+
+Count-up angka statistik Β· marquee repo yang lagi didanai Β· fade/blur-up saat reveal Β· magnet hover-lift di tombol primary. Tidak bouncy, tidak flashy.
+
+### 14.5 Larangan (biar tak terkesan AI)
+
+Tanpa gradient ungu/biru, tanpa glassmorphism/blur panel, tanpa neon glow, tanpa emoji sebagai ikon, tanpa ilustrasi 3D generik, tanpa βInter di mana-mana + serba centerβ.
+
+### 14.6 Implementasi
+
+**Tailwind CSS + shadcn/ui** untuk komponen dasar, **reactbits.dev** (secukupnya) untuk motion. Font via `next/font` (Geist sudah bawaan Next.js).
+
+
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..fc5a521
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,6 @@
+{
+ "name": "pullpay",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {}
+}
diff --git a/penjelasan_pullpay.md b/penjelasan_pullpay.md
new file mode 100644
index 0000000..b0a4b10
--- /dev/null
+++ b/penjelasan_pullpay.md
@@ -0,0 +1,73 @@
+# πΈ PullPay: Panduan Singkat & Sederhana
+
+Selamat datang di **PullPay**! Dokumen ini dibuat khusus untuk membantu Anda memahami konsep, alur kerja, dan keunikan proyek ini dengan bahasa yang sangat mudah dimengerti (bebas dari istilah teknis yang memusingkan).
+
+---
+
+## π― Apa itu PullPay?
+Bayangkan Anda adalah pembuat aplikasi open-source (gratisan) dan ingin memberi imbalan uang (misalnya $10) kepada siapa saja di internet yang membantu memperbaiki *bug* di kode Anda.
+
+Biasanya, proses kirim uang receh antar-negara sangat merepotkan karena biaya admin bank/PayPal yang mahal dan rawan penipuan.
+
+**PullPay hadir sebagai solusi:** Begitu kontributor mengirimkan kode perbaikan (Pull Request) dan Anda menyetujuinya (*merge*), **kontributor tersebut langsung dibayar secara otomatis dengan uang digital (USDC) tanpa potongan biaya mahal, dan tanpa perlu saling percaya satu sama lain.**
+
+---
+
+## βοΈ Cara Kerja (Skenario Sederhana)
+
+Mari kita gunakan analogi **Budi (Maintainer/Pemilik Proyek)** dan **Andi (Kontributor/Developer)**:
+
+```
+[ Budi ] --- Mengunci uang $10 di PullPay ---> [ Dompet Aman / Smart Contract ]
+ |
+[ Andi ] --- Memperbaiki bug & kirim PR -------------------->| (PR di-merge!)
+ |
+[ Andi ] <--- Uang $10 cair + Dapat Sertifikat Digital -------+
+```
+
+### 1. Budi Mengunci Uang (Escrow)
+Budi membuat sayembara: *"Siapa yang bisa memperbaiki tombol login ini, saya bayar $10 USDC."* Budi memasukkan uang $10 tersebut ke dalam **dompet aman (smart contract)** di blockchain. Uang ini terkunci dan tidak bisa diambil kembali oleh Budi kecuali tidak ada yang berhasil menyelesaikan tugas tersebut.
+
+### 2. Andi Mengerjakan & Mengirim Kode
+Andi melihat sayembara tersebut, memperbaiki kodenya, dan mengirimkannya ke GitHub Budi. Budi memeriksa kode Andi, merasa cocok, lalu menyetujuinya (**Merge PR**).
+
+### 3. Pemeriksaan Otomatis (UMA Oracle)
+Setelah di-merge, sistem akan mendiamkan proses selama beberapa menit. Di sini, sistem memverifikasi: *"Apakah Andi benar-benar menyelesaikan tugas dengan baik, atau kodenya asal-asalan?"* Jika tidak ada yang protes, Andi dinyatakan menang.
+
+### 4. Uang Cair & Sertifikat Terbit (EAS & Gasless)
+* **Bebas Gas (Gasless):** Andi tidak perlu punya saldo crypto (ETH) untuk mencairkan uangnya. Uang $10 langsung masuk utuh ke dompet crypto Andi.
+* **Sertifikat Digital (EAS):** Andi mendapatkan sertifikat digital permanen di blockchain sebagai bukti nyata bahwa ia telah berhasil membantu proyek Budi. Ini bisa jadi portofolio lamaran kerja Andi di masa depan!
+
+---
+
+## π 4 Fitur Keren yang Bikin PullPay Unik
+
+Jika Anda ditanya oleh juri atau orang lain mengapa proyek ini hebat, ini adalah jawabannya:
+
+### 1. βοΈ Hakim Netral (UMA Oracle)
+Di platform lain, jika server penyedia robot rusak atau pemilik proyek curang setelah kode di-merge, kontributor tidak bisa berbuat apa-apa.
+* **Di PullPay:** Jika pemilik proyek mencoba menahan uang padahal tugas sudah selesai, atau jika ada yang mengirim kode asal-asalan tapi tetap di-merge demi mencuri uang, siapa saja bisa mengajukan protes. Sengketa ini akan diselesaikan secara adil oleh komunitas global (UMA).
+
+### 2. π CV Digital yang Tidak Bisa Dipalsukan (EAS)
+Setiap kali berhasil menyelesaikan tugas, kontributor mendapatkan sertifikat digital (attestation). Sertifikat ini mencatat reputasi developer (nama proyek, kontribusi, nilai bayaran) secara permanen. Ini adalah CV nyata berbasis blockchain yang anti-manipulasi.
+
+### 3. β½ Bebas Biaya Gas (Gasless / ERC-4337)
+Kendala utama orang awam menggunakan crypto adalah harus memiliki saldo koin asli (seperti ETH) untuk membayar biaya transaksi (*gas fee*). Di PullPay, kontributor tidak perlu pusing soal itu. Semua biaya transaksi sudah ditanggung oleh sistem atau dipotong langsung secara otomatis dari hadiah.
+
+### 4. π Bagi Hasil Otomatis (Auto-split)
+Jika tugas dikerjakan oleh 2 orang atau lebih secara bersama-sama, sistem PullPay bisa membagi hadiahnya secara adil (misal 50:50) secara otomatis langsung ke dompet masing-masing kontributor.
+
+---
+
+## π οΈ Teknologi yang Digunakan
+
+* **Optimism:** Jaringan blockchain lapis kedua (L2) yang digunakan agar biaya transaksi super murah (kurang dari Rp 10 per transaksi) dan cepat.
+* **USDC:** Uang digital yang nilainya stabil setara dengan Dollar AS ($1 USDC = $1 USD).
+* **Next.js & RainbowKit:** Teknologi web modern agar tampilan dashboard-nya terlihat sangat premium, cepat, dan mudah menghubungkan dompet crypto (seperti MetaMask).
+* **GitHub Actions:** Robot otomatis yang membaca apakah kode Anda sudah di-merge atau belum di GitHub.
+
+---
+
+## π Rencana Masa Depan (Roadmap)
+1. **Suku Bunga Otomatis (Escrow Yield):** Uang yang dikunci oleh pemilik proyek bisa menghasilkan bunga kecil secara otomatis selama disimpan di dompet aman, yang nantinya dipakai untuk mendanai operasional website.
+2. **Pool Donasi (RetroPGF):** Perusahaan besar bisa menaruh dana donasi besar di sebuah proyek, dan uang tersebut akan dibagi-bagikan otomatis kepada kontributor berprestasi secara berkala.