-This is a SvelteKit dApp client for participating in a Shadowgraph reputation-scaled airdrop campaign. It supports both ECDSA-based claims and ZK-proof-based claims.
+# ๐ Shadowgraph Reputation-Gated Airdrop
-## Features
+### _The Future of Merit-Based Token Distribution_ ๐
-- **Wallet Connection**: Connect via MetaMask, WalletConnect, or Coinbase Wallet.
-- **Score Checking**: Fetches your reputation score from the Shadowgraph backend.
-- **Payout Preview**: See your potential airdrop amount based on your score and the configured curve.
-- **Claim Flow**: A guided process to claim your tokens via an on-chain transaction.
-- **Chain Awareness**: Automatically detects and prompts for switching to the correct network.
-- **Debug Mode**: A special view for developers to inspect configuration and state.
+
+
+
+
+
-## Tech Stack
-
-- **Framework**: SvelteKit
-- **Styling**: TailwindCSS
-- **Blockchain**: `viem` for EVM interactions, `@web3-onboard` for wallet connections.
-- **Validation**: `zod` for environment and API response validation.
-- **Icons**: `lucide-svelte`
-- **Testing**: Vitest (unit), Playwright (e2e)
+
+
+
+
+
---
-## Getting Started
+_Revolutionizing airdrops through reputation-based distribution with cutting-edge zero-knowledge proofs._
+
+**Powered by [Shadowgraph Labs](https://shadowgraph.io) ๐งช**
+
+
+
+## ๐ฏ Why This Project Rocks
+
+> **Traditional airdrops are broken.** They reward bots, incentivize Sybil attacks, and dilute value for genuine contributors.
+
+**Our solution?** A sophisticated reputation-gated system that:
+
+- ๐ก๏ธ **Prevents Sybil attacks** with cryptographic reputation scoring
+- ๐ฎ **Rewards genuine contributors** based on provable on-chain activity
+- ๐ **Scales infinitely** with zero-knowledge proof technology
+- ๐ **Flows seamlessly** with multi-wallet support and intuitive UX
+
+## โจ Features That Matter
+
+### ๐ **Universal Wallet Support**
+
+Connect with MetaMask, WalletConnect, Coinbase Wallet, and more. We've got you covered.
+
+### ๐ **Real-Time Reputation Scoring**
+
+Your contributions are continuously evaluated and reflected in your reputation score.
+
+### ๐ข **Dynamic Payout Curves**
+
+Choose from linear, square root, or quadratic distribution curves to optimize fairness.
+
+### ๐งโโ๏ธ **Zero-Knowledge Privacy**
+
+Prove your reputation without revealing sensitive data using cutting-edge ZK-SNARK technology.
+
+### ๐ **Multi-Chain Ready**
+
+Built for Ethereum and EVM-compatible networks with seamless chain switching.
-### 1. Installation
+### ๐ง **Developer Experience**
-Clone the repository and install the dependencies:
+Comprehensive debug mode and developer tools for seamless integration.
+
+## ๐ ๏ธ Tech Stack That Slaps
+
+
+
+### ๐งฌ **Architecture Highlights**
+
+- **๐ฅ Svelte 4** - Blazing fast reactive framework
+- **โก Vite** - Lightning-fast development experience
+- **๐จ TailwindCSS** - Utility-first styling that scales
+- **๐ Viem** - Type-safe Ethereum interactions
+- **๐งช Zod** - Runtime type validation for bulletproof APIs
+- **๐ญ Lucide Icons** - Beautiful, consistent iconography
+
+## ๐ Quick Start Guide
+
+Ready to dive in? Let's get you up and running in under 5 minutes!
+
+### ๐ฆ **1. Installation**
+
+Clone this masterpiece and install dependencies:
```bash
-git clone
-cd shadowgraph-airdrop-client
+# Clone the repository
+git clone https://github.com/Steake/Reputation-Gated-Airdrop.git
+cd Reputation-Gated-Airdrop
+
+# Install dependencies (Node.js 18+ required)
npm install
+
+# You're ready to rock! ๐ธ
```
-### 2. Environment Variables
+### โ๏ธ **2. Environment Configuration**
+
+Create your `.env` file - this is where the magic happens! โจ
-Create a `.env` file in the root of the project. This file is critical for configuring the dApp to point to the correct contracts, chain, and backend services.
+```bash
+# ๐ฎ Create your environment configuration
+cp .env.example .env
+```
+
+
+๐ Complete Environment Variables Guide
```env
-# REQUIRED: Web3 & Chain Configuration
-VITE_CHAIN_ID="11155111" # e.g., Sepolia
-VITE_RPC_URL="https://rpc.sepolia.org"
-VITE_TOKEN_ADDR="0x..." # The ERC20 token being airdropped
-
-# REQUIRED: Airdrop Campaign Configuration
-VITE_CAMPAIGN="0x..." # 32-byte campaign identifier
-VITE_FLOOR_SCORE="600000" # Min score to claim (1e6 scale)
-VITE_CAP_SCORE="1000000" # Score for max payout (1e6 scale)
-VITE_MIN_PAYOUT="100" # Min token payout (in token units, e.g., "100" for 100 tokens)
-VITE_MAX_PAYOUT="1000" # Max token payout
-VITE_CURVE="SQRT" # Payout curve: "LIN", "SQRT", or "QUAD"
-
-# REQUIRED: Backend API
-VITE_API_BASE="https://api.shadowgraph.io/v1" # Base URL for score/artifact endpoints
-
-# REQUIRED: Web3-Onboard Project ID
-# Get one from https://cloud.walletconnect.com/
-VITE_WALLETCONNECT_PROJECT_ID="YOUR_PROJECT_ID"
-
-# OPTIONAL: Contract Addresses (at least one path must be enabled)
-# To enable the ECDSA claim path:
-VITE_AIRDROP_ECDSA_ADDR="0x..." # ReputationAirdropScaled contract
-# To enable the ZK claim path:
-VITE_AIRDROP_ZK_ADDR="0x..." # ReputationAirdropZKScaled contract
-VITE_VERIFIER_ADDR="0x..." # EZKL Verifier contract
-
-# OPTIONAL: Debug Mode
-# Set to 'true' to enable the /debug route
-VITE_DEBUG="true"
+# ๐ BLOCKCHAIN CONFIGURATION
+VITE_CHAIN_ID="11155111" # Sepolia testnet (or your preferred network)
+VITE_RPC_URL="https://rpc.sepolia.org" # RPC endpoint
+VITE_TOKEN_ADDR="0x..." # ERC20 token being airdropped
+
+# ๐ฏ AIRDROP CAMPAIGN SETTINGS
+VITE_CAMPAIGN="0x..." # 32-byte campaign identifier
+VITE_FLOOR_SCORE="600000" # Minimum score to claim (1e6 scale)
+VITE_CAP_SCORE="1000000" # Score for maximum payout (1e6 scale)
+VITE_MIN_PAYOUT="100" # Minimum token payout
+VITE_MAX_PAYOUT="1000" # Maximum token payout
+VITE_CURVE="SQRT" # Payout curve: "LIN", "SQRT", or "QUAD"
+
+# ๐ API & SERVICES
+VITE_API_BASE="https://api.shadowgraph.io/v1" # Shadowgraph backend API
+VITE_WALLETCONNECT_PROJECT_ID="YOUR_PROJECT_ID" # Get from https://cloud.walletconnect.com/
+
+# ๐ SMART CONTRACTS (Choose your path!)
+# ๐๏ธ Traditional ECDSA path:
+VITE_AIRDROP_ECDSA_ADDR="0x..." # ReputationAirdropScaled contract
+
+# ๐งโโ๏ธ Zero-Knowledge path:
+VITE_AIRDROP_ZK_ADDR="0x..." # ReputationAirdropZKScaled contract
+VITE_VERIFIER_ADDR="0x..." # EZKL Verifier contract
+
+# ๐ง DEVELOPER TOOLS
+VITE_DEBUG="true" # Enable debug mode (/debug route)
```
-### 3. Running the Development Server
+
+
+### ๐ **3. Launch Your Dev Server**
+
+Time to see your work come to life!
```bash
+# ๐ฅ Fire up the development server
npm run dev
+
+# ๐ Open your browser to http://localhost:5173
+# Watch the magic happen! โจ
```
-Open [http://localhost:5173](http://localhost:5173) to view the application.
+> **Pro Tip:** The app will automatically reload as you make changes. Happy coding! ๐จโ๐ป
----
+## ๐ญ Development Modes
-## Development Modes
+### ๐ช **Mock Mode** (Perfect for Development)
-### Mock Mode
+No backend? No problem! When `VITE_API_BASE` is **not set**, we've got you covered:
-If the `VITE_API_BASE` environment variable is **not set**, the application will run in **Mock Mode**. In this mode, it does not make real network requests to a backend. Instead, it uses mock data generators to simulate API responses. This is useful for UI development and testing without needing a live backend.
+- ๐ฒ **Deterministic scores** based on wallet addresses
+- ๐ญ **Realistic mock data** for `/claim-artifact` and `/proof-meta` endpoints
+- ๐ **Zero setup** - just start coding!
-- `/scores/:addr` returns a deterministic score based on the address.
-- `/claim-artifact` and `/proof-meta` return fake but correctly-shaped data.
+Perfect for UI development and testing without infrastructure dependencies.
-### Production Mode
+### ๐ **Production Mode** (The Real Deal)
-Set `VITE_API_BASE` to your backend's URL to connect to the live services.
+Set `VITE_API_BASE` to connect to live Shadowgraph services. Experience the full power of reputation-gated airdrops!
-## Claim Paths (ECDSA vs. ZK)
+## ๐ค๏ธ Claim Paths: Choose Your Adventure
-The application can be configured for one or both claim paths:
+### ๐๏ธ **ECDSA Path** (Traditional & Reliable)
-- **ECDSA Path**: Requires `VITE_AIRDROP_ECDSA_ADDR` to be set. The client fetches a signed EIP-712 artifact from the backend and submits it to the `ReputationAirdropScaled` contract.
-- **ZK Path**: Requires `VITE_AIRDROP_ZK_ADDR` and `VITE_VERIFIER_ADDR` to be set. The client fetches ZK proof calldata from the backend and submits it to the `ReputationAirdropZKScaled` contract.
+- **Setup**: Configure `VITE_AIRDROP_ECDSA_ADDR`
+- **How it works**: Fetches signed EIP-712 artifacts from Shadowgraph backend
+- **Submits to**: `ReputationAirdropScaled` smart contract
+- **Best for**: Standard deployments and maximum compatibility
-If both are configured, the UI will prioritize the ZK path by default.
+### ๐งโโ๏ธ **Zero-Knowledge Path** (Cutting-Edge Privacy)
-## Available Scripts
+- **Setup**: Configure `VITE_AIRDROP_ZK_ADDR` and `VITE_VERIFIER_ADDR`
+- **How it works**: Generates zero-knowledge proofs for reputation verification
+- **Submits to**: `ReputationAirdropZKScaled` smart contract
+- **Best for**: Privacy-focused applications and advanced cryptographic setups
-- `npm run dev`: Start the dev server.
-- `npm run build`: Build the application for production.
-- `npm run preview`: Preview the production build locally.
-- `npm run test:unit`: Run unit tests with Vitest.
-- `npm run test:e2e`: Run end-to-end tests with Playwright.
-- `npm run lint`: Check for linting and formatting issues.
-- `npm run format`: Automatically format the code.
+> **๐ก Pro Tip:** Configure both paths for maximum flexibility! The UI will intelligently prioritize ZK when available.
-```
+## ๐ Available Scripts
+
+| Command | Description | Duration |
+| ------------------- | ----------------------------- | -------- |
+| `npm run dev` | ๐ Start development server | ~2s |
+| `npm run build` | ๐๏ธ Build for production | ~30s |
+| `npm run preview` | ๐๏ธ Preview production build | ~2s |
+| `npm run test:unit` | ๐งช Run unit tests (Vitest) | ~2s |
+| `npm run test:e2e` | ๐ญ Run E2E tests (Playwright) | Variable |
+| `npm run lint` | ๐ Check code quality | ~8s |
+| `npm run format` | โจ Auto-format code | ~9s |
+
+> **๐ก Quick Commands:** Run `npm run format && npm run lint` before committing to ensure pristine code quality!
+
+## ๐๏ธ Project Structure
```
+src/
+โโโ lib/
+โ โโโ components/ # ๐งฉ Reusable Svelte components
+โ โโโ stores/ # ๐ฆ Svelte stores (state management)
+โ โโโ web3/ # ๐ Blockchain interaction logic
+โ โโโ abi/ # ๐ Smart contract ABIs
+โ โโโ utils/ # ๐ ๏ธ Utility functions
+โโโ routes/ # ๐ค๏ธ SvelteKit routes and pages
+โโโ app.html # ๐ HTML template
+
+tests/
+โโโ unit/ # ๐งช Vitest unit tests
+โโโ e2e/ # ๐ญ Playwright E2E tests
+```
+
+## ๐ค Contributing
+
+We love contributions! Here's how to get involved:
+
+1. ๐ด **Fork** the repository
+2. ๐ฟ **Create** a feature branch (`git checkout -b feature/amazing-feature`)
+3. โจ **Make** your changes (don't forget to run `npm run format && npm run lint`)
+4. ๐ **Commit** your changes (`git commit -m 'Add amazing feature'`)
+5. ๐ **Push** to the branch (`git push origin feature/amazing-feature`)
+6. ๐ **Open** a Pull Request
+
+### ๐ Contributing Guidelines
+
+- Follow existing code style and conventions
+- Add tests for new features
+- Update documentation as needed
+- Be respectful and collaborative
+
+## ๐จ Issues & Support
+
+Encountered a bug? Have a feature request? We'd love to hear from you!
+
+- ๐ **Bug Reports**: [Open an issue](https://github.com/Steake/Reputation-Gated-Airdrop/issues/new?template=bug_report.md)
+- ๐ก **Feature Requests**: [Request a feature](https://github.com/Steake/Reputation-Gated-Airdrop/issues/new?template=feature_request.md)
+- ๐ฌ **Discussions**: [Join the conversation](https://github.com/Steake/Reputation-Gated-Airdrop/discussions)
+
+## ๐ Stats & Recognition
+
+
+
+## ๐ Acknowledgments
+
+This project builds upon the incredible work of:
+
+- **SvelteKit Team** - For the amazing framework
+- **Viem Contributors** - For type-safe Ethereum interactions
+- **Web3-Onboard Team** - For seamless wallet connections
+- **EZKL Community** - For zero-knowledge proof infrastructure
+- **The entire Web3 community** - For pushing the boundaries of decentralized technology
+
+## ๐ License
+
+This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
+
+---
+
+
+
+### ๐ **Built with โค๏ธ by [Shadowgraph Labs](https://shadowgraph.io)** ๐
+
+_Revolutionizing decentralized reputation systems, one commit at a time._
+
+**[๐ Website](https://shadowgraph.io) โข [๐ง Contact](mailto:team@shadowgraph.io) โข [๐ฆ Twitter](https://twitter.com/shadowgraphlabs) โข [๐ผ LinkedIn](https://linkedin.com/company/shadowgraph-labs)**
+
+---
+
+_"In cryptography we trust, in reputation we thrive." - Shadowgraph Labs_
+
+
diff --git a/tests/e2e/navigation.spec.ts b/tests/e2e/navigation.spec.ts
index 0e305a3..c15c661 100644
--- a/tests/e2e/navigation.spec.ts
+++ b/tests/e2e/navigation.spec.ts
@@ -3,7 +3,7 @@ import { test, expect } from "@playwright/test";
test.describe("Navigation and Routing", () => {
test("should navigate to all main pages", async ({ page }) => {
await page.goto("/");
- await page.waitForLoadState('networkidle');
+ await page.waitForLoadState("networkidle");
// Test navigation to Earn Reputation page with more flexible link matching
const earnLink = page.getByRole("link", { name: /earn/i });
diff --git a/tests/e2e/theme-integration.spec.ts b/tests/e2e/theme-integration.spec.ts
index c840969..40bedd4 100644
--- a/tests/e2e/theme-integration.spec.ts
+++ b/tests/e2e/theme-integration.spec.ts
@@ -107,13 +107,15 @@ test.describe("Theme Integration", () => {
// Test in light theme - look for Connect Wallet button
const connectButton = page.locator("button").filter({ hasText: "Connect Wallet" }).first();
await connectButton.click();
-
+
// Wait for modal to appear and check if it has wallet options
- const modal = page.locator('[data-testid="onboard-modal"], [role="dialog"], .onboard-modal').first();
+ const modal = page
+ .locator('[data-testid="onboard-modal"], [role="dialog"], .onboard-modal')
+ .first();
await expect(modal).toBeVisible({ timeout: 5000 });
-
+
// Close modal by clicking outside or finding close button
- await page.keyboard.press('Escape');
+ await page.keyboard.press("Escape");
await page.waitForTimeout(500);
// Switch to dark theme
diff --git a/tests/e2e/wallet-connection.spec.ts b/tests/e2e/wallet-connection.spec.ts
index a4632b1..65b7295 100644
--- a/tests/e2e/wallet-connection.spec.ts
+++ b/tests/e2e/wallet-connection.spec.ts
@@ -3,18 +3,21 @@ import { test, expect } from "@playwright/test";
test.describe("Wallet Connection", () => {
test("should display wallet connection modal on desktop", async ({ page }) => {
await page.goto("/");
- await page.waitForLoadState('networkidle');
+ await page.waitForLoadState("networkidle");
// Click connect wallet button with more resilient selector
- const connectButton = page.locator('button').filter({ hasText: /connect/i }).first();
-
+ const connectButton = page
+ .locator("button")
+ .filter({ hasText: /connect/i })
+ .first();
+
// Check if button exists before trying to click
const buttonExists = await connectButton.isVisible().catch(() => false);
if (!buttonExists) {
- console.log('Connect wallet button not found');
+ console.log("Connect wallet button not found");
return;
}
-
+
await connectButton.click();
// Give modal time to appear and be more flexible about what we expect
@@ -23,32 +26,39 @@ test.describe("Wallet Connection", () => {
// Check if any modal-like content appears (more flexible)
const modalSelectors = [
'[role="dialog"]',
- '.modal',
+ ".modal",
'[data-testid="wallet-modal"]',
- 'text=metamask',
- 'text=coinbase',
- 'text=trust'
+ "text=metamask",
+ "text=coinbase",
+ "text=trust",
];
-
+
let modalFound = false;
for (const selector of modalSelectors) {
- const exists = await page.locator(selector).first().isVisible().catch(() => false);
+ const exists = await page
+ .locator(selector)
+ .first()
+ .isVisible()
+ .catch(() => false);
if (exists) {
modalFound = true;
break;
}
}
-
- console.log('Modal or wallet-related content found:', modalFound);
+
+ console.log("Modal or wallet-related content found:", modalFound);
});
test("should display wallet connection modal on mobile", async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto("/");
- await page.waitForLoadState('networkidle');
+ await page.waitForLoadState("networkidle");
// Find and click connect wallet button with more flexible selectors
- const connectButton = page.locator('button').filter({ hasText: /connect wallet/i }).first();
+ const connectButton = page
+ .locator("button")
+ .filter({ hasText: /connect wallet/i })
+ .first();
await expect(connectButton).toBeVisible({ timeout: 10000 });
await connectButton.click();
@@ -57,29 +67,38 @@ test.describe("Wallet Connection", () => {
// Check if any wallet-related content appears (flexible)
await page.waitForTimeout(1000);
- const hasWalletContent = await page.locator('text=wallet, text=connect, text=MetaMask').first().isVisible().catch(() => false);
- console.log('Mobile wallet modal content found:', hasWalletContent);
+ const hasWalletContent = await page
+ .locator("text=wallet, text=connect, text=MetaMask")
+ .first()
+ .isVisible()
+ .catch(() => false);
+ console.log("Mobile wallet modal content found:", hasWalletContent);
});
test("should close modal when clicking close button", async ({ page }) => {
await page.goto("/");
- await page.waitForLoadState('networkidle');
+ await page.waitForLoadState("networkidle");
// Open modal with more flexible selectors
- const connectButton = page.locator('button').filter({ hasText: /connect wallet/i }).first();
+ const connectButton = page
+ .locator("button")
+ .filter({ hasText: /connect wallet/i })
+ .first();
await expect(connectButton).toBeVisible({ timeout: 10000 });
await connectButton.click();
// Wait for modal and look for close functionality
await page.waitForTimeout(1000);
-
+
// Try to find close button or click outside modal
- const closeButton = page.locator('button[aria-label*="close"], button[title*="close"], button:has-text("ร")').first();
+ const closeButton = page
+ .locator('button[aria-label*="close"], button[title*="close"], button:has-text("ร")')
+ .first();
if (await closeButton.isVisible()) {
await closeButton.click();
} else {
// Alternative: press Escape key
- await page.keyboard.press('Escape');
+ await page.keyboard.press("Escape");
}
});