From dbfa971037389a65a9cd5cf79bda8017f1c61c32 Mon Sep 17 00:00:00 2001 From: HuiNeng <3650306360@qq.com> Date: Tue, 24 Mar 2026 23:40:20 +0800 Subject: [PATCH 1/2] feat: Add comprehensive Bug Bounty Program (Closes #94) - Define bounty tiers (Critical: \-\, High: \-\, etc.) - Specify in-scope assets (smart contracts, ZK circuits, SDK, frontend) - Establish rules and submission process - Include disclosure policy with 90-104 day timeline - Provide safe harbor legal protection - Add FAQ section for researchers Fixes #94 --- docs/bug-bounty-program.md | 431 +++++++++++++++++++++++++++++++++++++ 1 file changed, 431 insertions(+) create mode 100644 docs/bug-bounty-program.md diff --git a/docs/bug-bounty-program.md b/docs/bug-bounty-program.md new file mode 100644 index 0000000..a862318 --- /dev/null +++ b/docs/bug-bounty-program.md @@ -0,0 +1,431 @@ +# 🛡️ PrivacyLayer Bug Bounty Program + +> **Status:** 🟢 Active | **Launch Date:** TBD +> +> Help us secure the future of private transactions on Stellar. + +--- + +## Table of Contents + +1. [Program Overview](#1-program-overview) +2. [Rewards](#2-rewards) +3. [Scope](#3-scope) +4. [Rules](#4-rules) +5. [Submission Process](#5-submission-process) +6. [Disclosure Policy](#6-disclosure-policy) +7. [Safe Harbor](#7-safe-harbor) +8. [FAQ](#8-faq) + +--- + +## 1. Program Overview + +### About PrivacyLayer + +PrivacyLayer is the first ZK-proof shielded pool on Stellar Soroban, enabling private deposits and withdrawals using zero-knowledge proofs. Our protocol uses: +- Noir circuits for ZK proof generation +- Poseidon hash for commitment schemes +- BN254 pairing-friendly curve (Protocol 25 native primitives) +- Merkle tree for commitment storage + +### Program Goals + +- Identify and remediate security vulnerabilities before they can be exploited +- Engage the security research community +- Protect user funds and privacy +- Maintain the integrity of our ZK proof system + +### Platform + +**Primary Platform:** [Immunefi](https://immunefi.com/) (preferred) +**Alternative:** Direct submission via security@privacylayer.io + +--- + +## 2. Rewards + +### Bounty Tiers + +| Severity | Description | Reward Range | +|----------|-------------|--------------| +| 🔴 **Critical** | Direct loss of user funds, privacy breach, or protocol takeover | **$50,000 - $100,000** | +| 🟠 **High** | Bypass of security controls, potential fund exposure | **$10,000 - $50,000** | +| 🟡 **Medium** | Logic errors, state manipulation (no fund loss) | **$5,000 - $10,000** | +| 🟢 **Low** | Minor bugs, documentation issues | **$1,000 - $5,000** | + +### Critical Severity Examples + +| Vulnerability Type | Max Bounty | +|-------------------|------------| +| Direct theft of user funds | $100,000 | +| Privacy breach (deposit-withdrawal linkability) | $75,000 | +| Bypass of ZK proof verification | $100,000 | +| Nullifier collision / double-spend | $75,000 | +| Merkle tree manipulation | $75,000 | +| Admin key compromise | $50,000 | + +### High Severity Examples + +| Vulnerability Type | Max Bounty | +|-------------------|------------| +| Unauthorized pause/unpause | $25,000 | +| Front-running leading to user loss | $20,000 | +| DoS on deposit/withdrawal functions | $15,000 | +| Incorrect event emission (audit trail) | $10,000 | + +### Medium Severity Examples + +| Vulnerability Type | Max Bounty | +|-------------------|------------| +| State inconsistency (no fund loss) | $8,000 | +| Missing input validation | $7,000 | +| Gas optimization leading to failed txs | $5,000 | + +### Low Severity Examples + +| Vulnerability Type | Max Bounty | +|-------------------|------------| +| Documentation errors | $1,000 | +| UI/UX bugs affecting security | $2,000 | +| Information disclosure | $3,000 | + +### Payment Methods + +- **USDC** (preferred, on Stellar or Ethereum) +- **XLM** (at current market rate) +- **Bank Transfer** (for large bounties, arranged case-by-case) + +### Bonus Opportunities + +- **First to report:** +10% bonus for first valid report of a vulnerability type +- **Quality report:** +5% for exceptionally detailed reports with PoC +- **Circuit vulnerability:** +20% for ZK circuit bugs (due to complexity) + +--- + +## 3. Scope + +### ✅ In-Scope Assets + +#### Smart Contracts (Soroban) + +| Contract | Address | Network | +|----------|---------|---------| +| Privacy Pool | `TBD` | Mainnet | +| Privacy Pool | `TBD` | Testnet | + +**Files:** +- `contracts/privacy_pool/src/*.rs` +- `contracts/privacy_pool/src/**/*.rs` + +#### ZK Circuits (Noir) + +| Circuit | Location | +|---------|----------| +| Commitment Circuit | `circuits/commitment/src/main.nr` | +| Withdraw Circuit | `circuits/withdraw/src/main.nr` | +| Merkle Library | `circuits/lib/src/merkle/mod.nr` | +| Hash Library | `circuits/lib/src/hash/mod.nr` | + +#### Frontend & SDK + +| Component | Repository | +|-----------|------------| +| Frontend dApp | `frontend/src/**/*` | +| TypeScript SDK | `sdk/src/**/*` | + +#### Infrastructure + +| Asset | Scope | +|-------|-------| +| API Endpoints | `api.privacylayer.io` | +| Documentation | `docs.privacylayer.io` | + +### ❌ Out-of-Scope + +| Asset | Reason | +|-------|--------| +| Third-party dependencies | Report to upstream | +| Social engineering attacks | Not applicable | +| DDoS attacks | Handled by infrastructure | +| UI/UX issues without security impact | Non-security | +| Already known vulnerabilities | Check disclosed list | +| Vulnerabilities on testnet only | Use mainnet for testing | + +### Testing Guidelines + +#### ✅ Allowed + +- View functions on mainnet +- All operations on testnet +- Local fork testing +- Static analysis +- Code review + +#### ❌ Prohibited + +- Depositing real funds on mainnet for testing +- Exploiting vulnerabilities for profit +- Testing on other users' transactions +- Any action that could harm users + +--- + +## 4. Rules + +### ✅ DO + +1. **Report promptly** - Submit vulnerabilities as soon as discovered +2. **Provide details** - Include steps to reproduce, PoC code +3. **Stay in scope** - Only test in-scope assets +4. **Be patient** - We'll respond within 48 hours +5. **Keep confidential** - Don't disclose until fixed + +### ❌ DON'T + +1. **Don't exploit** - Never exploit for personal gain +2. **Don't harm users** - Never test with real user data/funds +3. **Don't spam** - No automated scanning without permission +4. **Don't threaten** - This is cooperation, not extortion +5. **Don't publicize** - Wait for our go-ahead before disclosure + +### Qualifying Vulnerabilities + +To qualify for a bounty, the vulnerability must: + +1. Be previously unreported +2. Be reproducible +3. Have a security impact +4. Be submitted through proper channels +5. Follow all program rules + +### Non-Qualifying Findings + +- Theoretical vulnerabilities without PoC +- Issues already fixed in latest version +- Vulnerabilities requiring compromised keys +- Spam reports +- Duplicate reports + +--- + +## 5. Submission Process + +### Step 1: Prepare Your Report + +Include the following: + +```markdown +## Summary +Brief description of the vulnerability + +## Impact +What could an attacker achieve? + +## Steps to Reproduce +1. Step one +2. Step two +3. ... + +## Proof of Concept +Code or screenshots demonstrating the issue + +## Suggested Fix +Optional: How would you fix this? + +## Your Contact Info +Preferred method for follow-up +``` + +### Step 2: Submit + +**Option A: Immunefi (Preferred)** +1. Visit [Immunefi](https://immunefi.com/bounty/privacylayer) +2. Create an account +3. Submit detailed report + +**Option B: Email** +1. Send to: security@privacylayer.io +2. Subject: `[BUG BOUNTY] ` +3. Encrypt with PGP if sensitive (key: `TBD`) + +### Step 3: Response Timeline + +| Stage | Timeline | +|-------|----------| +| Initial Response | Within 48 hours | +| Triage & Severity | Within 5 business days | +| Investigation | 1-2 weeks | +| Fix Development | 2-4 weeks | +| Bounty Payment | Within 30 days of fix | + +### Step 4: Communication + +- All updates via Immunefi or email +- We may request additional information +- You'll be notified when fix is deployed +- Payment details confirmed before deployment + +--- + +## 6. Disclosure Policy + +### Coordinated Disclosure + +We follow **responsible disclosure**: + +1. **Report submitted** - Vulnerability details shared +2. **Triage** - Severity assessed, investigation begins +3. **Fix developed** - Patch created and tested +4. **Fix deployed** - Update pushed to production +5. **Cool-down period** - Wait 7 days for adoption +6. **Public disclosure** - Full details published + +### Timeline + +| Phase | Duration | +|-------|----------| +| Fix Development | Up to 90 days | +| Deployment + Cool-down | 7-14 days | +| Total | Up to 104 days | + +### Extensions + +We may extend the timeline for: +- Complex vulnerabilities requiring significant changes +- Coordination with external parties +- Dependent libraries requiring updates + +### Early Disclosure + +Researchers may disclose after: +- 104 days with no response, OR +- 90 days after fix with no deployment + +### Credit & Recognition + +We publicly credit researchers who: +- Follow the disclosure policy +- Allow us to fix the issue first +- Provide quality reports + +Recognition includes: +- Hall of Fame listing +- Social media acknowledgment +- Conference presentation opportunities +- Referrals and recommendations + +--- + +## 7. Safe Harbor + +### Legal Protection + +We commit to **NOT** pursuing legal action against researchers who: + +1. Make a good faith effort to comply with this program +2. Do not intentionally cause harm +3. Do not access or exfiltrate user data +4. Do not publicly disclose before we fix +5. Report in accordance with our rules + +### Scope of Protection + +This safe harbor applies to: +- Testing of in-scope assets only +- Research conducted in good faith +- Actions that don't violate applicable laws + +### Exceptions + +Safe harbor does NOT apply to: +- Accessing user data without authorization +- Actions that violate applicable law +- Testing outside of scope +- Ransomware or extortion attempts +- Disruption of services + +--- + +## 8. FAQ + +### General Questions + +**Q: Who can participate?** +> Anyone 18 years or older, except where prohibited by law. Employees, contractors, and their immediate family members are ineligible. + +**Q: How long does it take to get paid?** +> Typically within 30 days of the fix being deployed. Complex cases may take longer. + +**Q: Can I remain anonymous?** +> Yes, but we need a way to contact you for payment. Consider using a pseudonymous identity. + +**Q: What if I find multiple vulnerabilities?** +> Submit each as a separate report. You'll be rewarded for each valid finding. + +### Technical Questions + +**Q: What should I focus on?** +> Priority areas: ZK circuit soundness, double-spend vectors, privacy breaches, fund theft. + +**Q: Is testnet in scope?** +> Yes, for testing. But vulnerabilities that only exist on testnet are not bounty-eligible. + +**Q: Can I use automated tools?** +> Manual testing is preferred. If using automated tools, limit rate to avoid disruption. + +**Q: Do I need to provide a PoC?** +> Not required but highly recommended. PoCs significantly speed up triage and increase bounty. + +### Payment Questions + +**Q: What currencies do you pay in?** +> USDC (preferred), XLM, or bank transfer for large bounties. + +**Q: Are there tax implications?** +> We'll collect necessary tax forms. Consult a tax professional for your jurisdiction. + +**Q: Can I donate my bounty?** +> Yes! We can donate to a charity of your choice or to our privacy education fund. + +--- + +## Contact + +| Channel | Purpose | +|---------|---------| +| 🛡️ **Immunefi** | Bug submissions (preferred) | +| 📧 **security@privacylayer.io** | Alternative submission | +| 💬 **Discord** | #security channel for questions | +| 🐦 **Twitter** | @PrivacyLayer for updates | + +--- + +## Hall of Fame + +### 2026 + +| Researcher | Vulnerability | Severity | Date | +|------------|---------------|----------|------| +| *Be the first!* | - | - | - | + +--- + +## Program Updates + +| Date | Change | +|------|--------| +| 2026-03-24 | Initial program launch | + +--- + +## Legal + +This bug bounty program is governed by the terms and conditions available at [privacylayer.io/security/bounty-terms](https://privacylayer.io/security/bounty-terms). + +Participation constitutes agreement to these terms. + +--- + +*We appreciate your help in making PrivacyLayer secure. Together, we're building the future of private finance.* 🛡️ \ No newline at end of file From 1828dee8cfe5ff2f988f7a19d21d2ed9c26bcbba Mon Sep 17 00:00:00 2001 From: HuiNeng <3650306360@qq.com> Date: Wed, 25 Mar 2026 04:34:45 +0800 Subject: [PATCH 2/2] feat: implement Freighter wallet integration - Add wallet connection/disconnection functions (lib/wallet.ts) - Add Zustand state management for wallet state (lib/store.ts) - Create wallet UI components: - ConnectButton: Connect/disconnect wallet - WalletInfo: Display connected wallet info - NetworkSelector: Network selection dropdown - InstallPrompt: Prompt to install Freighter - Set up Next.js 14 with TypeScript and Tailwind CSS - Add comprehensive README with usage examples Closes #24 --- frontend/.eslintrc.json | 3 + frontend/README.md | 156 +++++++++++++ frontend/app/globals.css | 27 +++ frontend/app/layout.tsx | 22 ++ frontend/app/page.tsx | 22 ++ frontend/components/wallet/ConnectButton.tsx | 67 ++++++ frontend/components/wallet/InstallPrompt.tsx | 23 ++ .../components/wallet/NetworkSelector.tsx | 36 +++ frontend/components/wallet/WalletInfo.tsx | 45 ++++ frontend/components/wallet/index.ts | 5 + frontend/lib/store.ts | 125 +++++++++++ frontend/lib/wallet.ts | 210 ++++++++++++++++++ frontend/next-env.d.ts | 5 + frontend/next.config.js | 6 + frontend/package.json | 32 +++ frontend/postcss.config.js | 6 + frontend/tailwind.config.ts | 29 +++ frontend/tsconfig.json | 26 +++ 18 files changed, 845 insertions(+) create mode 100644 frontend/.eslintrc.json create mode 100644 frontend/README.md create mode 100644 frontend/app/globals.css create mode 100644 frontend/app/layout.tsx create mode 100644 frontend/app/page.tsx create mode 100644 frontend/components/wallet/ConnectButton.tsx create mode 100644 frontend/components/wallet/InstallPrompt.tsx create mode 100644 frontend/components/wallet/NetworkSelector.tsx create mode 100644 frontend/components/wallet/WalletInfo.tsx create mode 100644 frontend/components/wallet/index.ts create mode 100644 frontend/lib/store.ts create mode 100644 frontend/lib/wallet.ts create mode 100644 frontend/next-env.d.ts create mode 100644 frontend/next.config.js create mode 100644 frontend/package.json create mode 100644 frontend/postcss.config.js create mode 100644 frontend/tailwind.config.ts create mode 100644 frontend/tsconfig.json diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json new file mode 100644 index 0000000..0e81f9b --- /dev/null +++ b/frontend/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..95c34e9 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,156 @@ +# PrivacyLayer Frontend + +Next.js dApp for PrivacyLayer - the first ZK-proof shielded pool on Stellar Soroban. + +## Features + +- 🔐 Freighter wallet integration +- 🎨 Tailwind CSS styling +- 📦 Zustand state management +- 🔧 TypeScript support + +## Getting Started + +### Prerequisites + +- Node.js 18+ +- npm or yarn or pnpm +- [Freighter Wallet](https://www.freighter.app/) browser extension + +### Installation + +```bash +# Install dependencies +npm install + +# Run development server +npm run dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +## Project Structure + +``` +frontend/ +├── app/ # Next.js App Router pages +│ ├── layout.tsx # Root layout +│ ├── page.tsx # Home page +│ └── globals.css # Global styles +├── components/ # React components +│ └── wallet/ # Wallet-related components +│ ├── ConnectButton.tsx # Connect/disconnect button +│ ├── WalletInfo.tsx # Display connected wallet info +│ ├── NetworkSelector.tsx # Network switcher +│ └── InstallPrompt.tsx # Prompt to install Freighter +├── lib/ # Utility libraries +│ ├── wallet.ts # Freighter wallet functions +│ └── store.ts # Zustand state management +├── package.json +├── tsconfig.json +├── tailwind.config.ts +└── next.config.js +``` + +## Wallet Integration + +### Using Wallet Functions + +```typescript +import { + connectWallet, + disconnectWallet, + signTransactionWithWallet +} from '@/lib/wallet'; + +// Connect wallet +const result = await connectWallet(); +if (result.error) { + console.error(result.error); +} else { + console.log('Connected:', result.publicKey); +} + +// Sign a transaction +const { signedTxXdr, error } = await signTransactionWithWallet(transactionXdr); +``` + +### Using Wallet Store + +```typescript +import { useWalletStore, useWalletPublicKey } from '@/lib/store'; + +function MyComponent() { + const publicKey = useWalletPublicKey(); + const connect = useWalletStore((state) => state.connect); + const disconnect = useWalletStore((state) => state.disconnect); + + return ( +
+ {publicKey ? ( + + ) : ( + + )} +
+ ); +} +``` + +## Components + +### ConnectButton + +A button that handles wallet connection/disconnection. + +```tsx +import ConnectButton from '@/components/wallet/ConnectButton'; + + +``` + +### WalletInfo + +Displays connected wallet information. + +```tsx +import WalletInfo from '@/components/wallet/WalletInfo'; + + +``` + +### NetworkSelector + +Network selection dropdown (info only, actual network change must be done in Freighter). + +```tsx +import NetworkSelector from '@/components/wallet/NetworkSelector'; + + +``` + +### InstallPrompt + +Prompts users to install Freighter if not detected. + +```tsx +import InstallPrompt from '@/components/wallet/InstallPrompt'; + + +``` + +## Environment Variables + +Create a `.env.local` file for environment-specific configuration: + +```env +NEXT_PUBLIC_NETWORK=TESTNET +NEXT_PUBLIC_CONTRACT_ID=your_contract_id +``` + +## Learn More + +- [Next.js Documentation](https://nextjs.org/docs) +- [Freighter API Documentation](https://github.com/stellar/freighter) +- [Stellar SDK Documentation](https://developers.stellar.org/docs) +- [Zustand Documentation](https://github.com/pmndrs/zustand) \ No newline at end of file diff --git a/frontend/app/globals.css b/frontend/app/globals.css new file mode 100644 index 0000000..6e5f93b --- /dev/null +++ b/frontend/app/globals.css @@ -0,0 +1,27 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + } +} + +body { + color: rgb(var(--foreground-rgb)); + background: linear-gradient( + to bottom, + transparent, + rgb(var(--background-end-rgb)) + ) + rgb(var(--background-start-rgb)); +} \ No newline at end of file diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx new file mode 100644 index 0000000..1546717 --- /dev/null +++ b/frontend/app/layout.tsx @@ -0,0 +1,22 @@ +import type { Metadata } from 'next' +import { Inter } from 'next/font/google' +import './globals.css' + +const inter = Inter({ subsets: ['latin'] }) + +export const metadata: Metadata = { + title: 'PrivacyLayer - ZK Shielded Pool on Stellar', + description: 'The first ZK-proof shielded pool on Stellar Soroban', +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx new file mode 100644 index 0000000..1e9d450 --- /dev/null +++ b/frontend/app/page.tsx @@ -0,0 +1,22 @@ +import ConnectButton from '@/components/wallet/ConnectButton' +import WalletInfo from '@/components/wallet/WalletInfo' + +export default function Home() { + return ( +
+
+

+ 🔐 PrivacyLayer +

+

+ The first ZK-proof shielded pool on Stellar Soroban +

+ +
+ + +
+
+
+ ) +} \ No newline at end of file diff --git a/frontend/components/wallet/ConnectButton.tsx b/frontend/components/wallet/ConnectButton.tsx new file mode 100644 index 0000000..3bb37b5 --- /dev/null +++ b/frontend/components/wallet/ConnectButton.tsx @@ -0,0 +1,67 @@ +'use client'; + +import { useWalletStore, useWalletLoading, useWalletConnected, useWalletError } from '@/lib/store'; + +export default function ConnectButton() { + const connect = useWalletStore((state) => state.connect); + const disconnect = useWalletStore((state) => state.disconnect); + const isLoading = useWalletLoading(); + const isConnected = useWalletConnected(); + const error = useWalletError(); + + const handleClick = async () => { + if (isConnected) { + disconnect(); + } else { + await connect(); + } + }; + + return ( +
+ + + {error && ( +

+ {error} +

+ )} +
+ ); +} \ No newline at end of file diff --git a/frontend/components/wallet/InstallPrompt.tsx b/frontend/components/wallet/InstallPrompt.tsx new file mode 100644 index 0000000..d3debd3 --- /dev/null +++ b/frontend/components/wallet/InstallPrompt.tsx @@ -0,0 +1,23 @@ +'use client'; + +export default function InstallPrompt() { + return ( +
+

+ ⚠️ Freighter Not Detected +

+

+ Freighter wallet is required to interact with PrivacyLayer. + Please install it to continue. +

+ + Install Freighter + +
+ ); +} \ No newline at end of file diff --git a/frontend/components/wallet/NetworkSelector.tsx b/frontend/components/wallet/NetworkSelector.tsx new file mode 100644 index 0000000..d2d7fc6 --- /dev/null +++ b/frontend/components/wallet/NetworkSelector.tsx @@ -0,0 +1,36 @@ +'use client'; + +import { useWalletNetwork } from '@/lib/store'; + +interface NetworkSelectorProps { + onNetworkChange?: (network: string) => void; +} + +const NETWORKS = [ + { id: 'TESTNET', name: 'Testnet', passphrase: 'Test SDF Network ; September 2015' }, + { id: 'PUBLIC', name: 'Mainnet', passphrase: 'Public Global Stellar Network ; September 2015' }, +]; + +export default function NetworkSelector({ onNetworkChange }: NetworkSelectorProps) { + const currentNetwork = useWalletNetwork(); + + return ( +
+ Network: + + + (Change in Freighter) + +
+ ); +} \ No newline at end of file diff --git a/frontend/components/wallet/WalletInfo.tsx b/frontend/components/wallet/WalletInfo.tsx new file mode 100644 index 0000000..e38fb44 --- /dev/null +++ b/frontend/components/wallet/WalletInfo.tsx @@ -0,0 +1,45 @@ +'use client'; + +import { useWalletConnected, useWalletPublicKey, useWalletNetwork } from '@/lib/store'; + +export default function WalletInfo() { + const isConnected = useWalletConnected(); + const publicKey = useWalletPublicKey(); + const network = useWalletNetwork(); + + if (!isConnected || !publicKey) { + return null; + } + + // Truncate public key for display + const truncatedKey = `${publicKey.slice(0, 8)}...${publicKey.slice(-8)}`; + + return ( +
+

+ Connected Wallet +

+
+
+ Address: + + {truncatedKey} + + +
+
+ Network: + + {network || 'Unknown'} + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/components/wallet/index.ts b/frontend/components/wallet/index.ts new file mode 100644 index 0000000..ae7eec5 --- /dev/null +++ b/frontend/components/wallet/index.ts @@ -0,0 +1,5 @@ +// Wallet components +export { default as ConnectButton } from './ConnectButton'; +export { default as WalletInfo } from './WalletInfo'; +export { default as NetworkSelector } from './NetworkSelector'; +export { default as InstallPrompt } from './InstallPrompt'; \ No newline at end of file diff --git a/frontend/lib/store.ts b/frontend/lib/store.ts new file mode 100644 index 0000000..9bf6a9a --- /dev/null +++ b/frontend/lib/store.ts @@ -0,0 +1,125 @@ +/** + * Wallet State Management with Zustand + * + * Provides centralized state management for wallet connection, + * user public key, network selection, and loading states. + */ + +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; +import { + connectWallet as connectWalletApi, + disconnectWallet as disconnectWalletApi, + checkWalletInstalled, + type WalletState +} from './wallet'; + +interface WalletStore { + // State + connected: boolean; + publicKey: string | null; + network: string | null; + isLoading: boolean; + error: string | null; + + // Actions + connect: () => Promise; + disconnect: () => void; + setLoading: (loading: boolean) => void; + setError: (error: string | null) => void; + reset: () => void; +} + +const initialState = { + connected: false, + publicKey: null, + network: null, + isLoading: false, + error: null, +}; + +export const useWalletStore = create()( + persist( + (set) => ({ + ...initialState, + + connect: async () => { + set({ isLoading: true, error: null }); + + try { + // Check if wallet is installed + const isInstalled = await checkWalletInstalled(); + if (!isInstalled) { + set({ + isLoading: false, + error: 'Freighter wallet is not installed. Please install it from https://www.freighter.app/' + }); + return; + } + + // Connect to wallet + const result = await connectWalletApi(); + + if (result.error) { + set({ + isLoading: false, + error: result.error, + connected: false, + publicKey: null, + network: null, + }); + return; + } + + set({ + isLoading: false, + connected: result.connected, + publicKey: result.publicKey, + network: result.network, + error: null, + }); + } catch (error) { + set({ + isLoading: false, + error: error instanceof Error ? error.message : 'Failed to connect wallet', + connected: false, + publicKey: null, + network: null, + }); + } + }, + + disconnect: () => { + const result = disconnectWalletApi(); + set({ + connected: result.connected, + publicKey: result.publicKey, + network: result.network, + error: null, + }); + }, + + setLoading: (loading: boolean) => set({ isLoading: loading }), + + setError: (error: string | null) => set({ error }), + + reset: () => set(initialState), + }), + { + name: 'privacylayer-wallet', + // Only persist these fields + partialize: (state) => ({ + connected: state.connected, + publicKey: state.publicKey, + network: state.network, + }), + } + ) +); + +// Selector hooks for better performance +export const useWalletConnected = () => useWalletStore((state) => state.connected); +export const useWalletPublicKey = () => useWalletStore((state) => state.publicKey); +export const useWalletNetwork = () => useWalletStore((state) => state.network); +export const useWalletLoading = () => useWalletStore((state) => state.isLoading); +export const useWalletError = () => useWalletStore((state) => state.error); \ No newline at end of file diff --git a/frontend/lib/wallet.ts b/frontend/lib/wallet.ts new file mode 100644 index 0000000..08068d6 --- /dev/null +++ b/frontend/lib/wallet.ts @@ -0,0 +1,210 @@ +/** + * Freighter Wallet Integration + * + * Provides functions for connecting to Freighter wallet, + * signing transactions, and managing wallet state. + */ + +import { + isConnected, + requestAccess, + getPublicKey, + signTransaction, + setAllowed, + getAllowedStatus, + getNetwork, + type AccountBalances +} from '@stellar/freighter-api'; +import { Transaction } from '@stellar/stellar-sdk'; + +export interface WalletState { + connected: boolean; + publicKey: string | null; + network: string | null; + error: string | null; +} + +/** + * Check if Freighter wallet is installed + */ +export async function checkWalletInstalled(): Promise { + try { + const connected = await isConnected(); + return connected.isConnected; + } catch { + return false; + } +} + +/** + * Connect to Freighter wallet + * Requests access from the user if not already connected + */ +export async function connectWallet(): Promise { + try { + // Check if wallet is installed + const isInstalled = await checkWalletInstalled(); + if (!isInstalled) { + return { + connected: false, + publicKey: null, + network: null, + error: 'Freighter wallet is not installed. Please install it from https://www.freighter.app/' + }; + } + + // Request access + const accessResult = await requestAccess(); + + if (accessResult.error) { + return { + connected: false, + publicKey: null, + network: null, + error: accessResult.error + }; + } + + // Get network info + const networkResult = await getNetwork(); + + return { + connected: true, + publicKey: accessResult.publicKey, + network: networkResult.network, + error: null + }; + } catch (error) { + return { + connected: false, + publicKey: null, + network: null, + error: error instanceof Error ? error.message : 'Failed to connect wallet' + }; + } +} + +/** + * Disconnect wallet (clear local state) + * Note: Freighter doesn't have a disconnect API, so we just clear local state + */ +export function disconnectWallet(): WalletState { + return { + connected: false, + publicKey: null, + network: null, + error: null + }; +} + +/** + * Get the currently connected public key + */ +export async function getPublicKeyFromWallet(): Promise { + try { + const result = await getPublicKey(); + return result.publicKey || null; + } catch { + return null; + } +} + +/** + * Sign a transaction using Freighter + * @param transactionXdr - The XDR string of the transaction to sign + * @param networkPassphrase - The network passphrase (optional, defaults to current network) + */ +export async function signTransactionWithWallet( + transactionXdr: string, + networkPassphrase?: string +): Promise<{ signedTxXdr: string | null; error: string | null }> { + try { + // Check if connected + const isInstalled = await checkWalletInstalled(); + if (!isInstalled) { + return { + signedTxXdr: null, + error: 'Freighter wallet is not installed' + }; + } + + // Get network if not provided + let passphrase = networkPassphrase; + if (!passphrase) { + const networkResult = await getNetwork(); + passphrase = networkResult.networkPassphrase; + } + + // Sign the transaction + const result = await signTransaction(transactionXdr, { + networkPassphrase: passphrase + }); + + if (result.error) { + return { + signedTxXdr: null, + error: result.error + }; + } + + return { + signedTxXdr: result.signedTxXdr || null, + error: null + }; + } catch (error) { + return { + signedTxXdr: null, + error: error instanceof Error ? error.message : 'Failed to sign transaction' + }; + } +} + +/** + * Set whether the app is allowed to access the wallet + */ +export async function setWalletAllowed(allowed: boolean): Promise { + try { + const result = await setAllowed(allowed); + return result.isAllowed; + } catch { + return false; + } +} + +/** + * Check if the app is allowed to access the wallet + */ +export async function getWalletAllowedStatus(): Promise { + try { + const result = await getAllowedStatus(); + return result.isAllowed; + } catch { + return false; + } +} + +/** + * Get the current network from Freighter + */ +export async function getWalletNetwork(): Promise<{ network: string; networkPassphrase: string } | null> { + try { + const result = await getNetwork(); + return { + network: result.network, + networkPassphrase: result.networkPassphrase + }; + } catch { + return null; + } +} + +/** + * Error messages for common wallet errors + */ +export const WALLET_ERRORS = { + NOT_INSTALLED: 'Freighter wallet is not installed. Please install it from https://www.freighter.app/', + CONNECTION_REJECTED: 'Connection request was rejected by user', + NETWORK_MISMATCH: 'Network mismatch. Please switch to the correct network in Freighter.', + SIGNING_FAILED: 'Transaction signing failed', + NOT_CONNECTED: 'Wallet is not connected' +} as const; \ No newline at end of file diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts new file mode 100644 index 0000000..4f11a03 --- /dev/null +++ b/frontend/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/frontend/next.config.js b/frontend/next.config.js new file mode 100644 index 0000000..cf97dc6 --- /dev/null +++ b/frontend/next.config.js @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, +} + +module.exports = nextConfig \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..cf37a48 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,32 @@ +{ + "name": "privacylayer-frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@stellar/freighter-api": "^4.1.0", + "@stellar/stellar-sdk": "^12.0.0", + "lucide-react": "^0.263.1", + "next": "14.0.4", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "tailwind-merge": "^2.2.0", + "zustand": "^4.4.7" + }, + "devDependencies": { + "@types/node": "^20.10.5", + "@types/react": "^18.2.45", + "@types/react-dom": "^18.2.18", + "autoprefixer": "^10.4.16", + "eslint": "^8.56.0", + "eslint-config-next": "14.0.4", + "postcss": "^8.4.32", + "tailwindcss": "^3.4.0", + "typescript": "^5.3.3" + } +} \ No newline at end of file diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..96bb01e --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts new file mode 100644 index 0000000..6eb732f --- /dev/null +++ b/frontend/tailwind.config.ts @@ -0,0 +1,29 @@ +import type { Config } from 'tailwindcss' + +const config: Config = { + content: [ + './pages/**/*.{js,ts,jsx,tsx,mdx}', + './components/**/*.{js,ts,jsx,tsx,mdx}', + './app/**/*.{js,ts,jsx,tsx,mdx}', + ], + theme: { + extend: { + colors: { + primary: { + 50: '#f0f9ff', + 100: '#e0f2fe', + 200: '#bae6fd', + 300: '#7dd3fc', + 400: '#38bdf8', + 500: '#0ea5e9', + 600: '#0284c7', + 700: '#0369a1', + 800: '#075985', + 900: '#0c4a6e', + }, + }, + }, + }, + plugins: [], +} +export default config \ No newline at end of file diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..01d5c48 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} \ No newline at end of file