From 7c63929c60280b28fc424f48fb411e7a76180db6 Mon Sep 17 00:00:00 2001 From: Spoorthi-Hokrana Date: Sat, 28 Feb 2026 20:32:02 +0530 Subject: [PATCH 1/2] chore: add quality tooling, build fixes, and lint cleanup Made-with: Cursor --- .github/workflows/ci.yml | 43 + .husky/commit-msg | 1 + .husky/pre-commit | 1 + .husky/pre-push | 2 + .lintstagedrc.json | 4 + .prettierignore | 16 + .prettierrc | 7 + README.md | 338 +-- SUPABASE_SETUP.md | 175 +- TECHNICAL_ARCHITECTURE.md | 121 +- app/api/activity/comment/route.ts | 52 +- app/api/activity/comments/route.ts | 24 +- app/api/activity/join/route.ts | 49 +- app/api/activity/leave/route.ts | 24 +- app/api/activity/like/route.ts | 24 +- app/api/agents/[agentId]/route.ts | 12 +- app/api/agents/claim/route.ts | 20 +- .../agents/me/regenerate-claim-token/route.ts | 15 +- app/api/agents/me/route.ts | 19 +- app/api/agents/register/route.ts | 32 +- app/api/agents/route.ts | 17 +- app/api/auth/nonce/route.ts | 56 +- app/api/auth/verify/route.ts | 144 +- .../[challengeId]/agents/route.ts | 17 +- .../championship/[challengeId]/bets/route.ts | 17 +- .../[challengeId]/bets/summary/route.ts | 28 +- .../championship/[challengeId]/claim/route.ts | 23 +- .../[challengeId]/finalize/route.ts | 39 +- .../[challengeId]/my-status/route.ts | 10 +- .../[challengeId]/results/route.ts | 22 +- .../[challengeId]/reveal/route.ts | 26 +- app/api/championship/[challengeId]/route.ts | 17 +- .../[challengeId]/submit/route.ts | 41 +- .../[challengeId]/withdraw/route.ts | 30 +- app/api/championship/bet/route.ts | 25 +- app/api/championship/create/route.ts | 45 +- app/api/championship/enroll/route.ts | 20 +- app/api/championship/route.ts | 29 +- app/api/championship/vote/route.ts | 47 +- app/api/community/create/route.ts | 107 +- app/api/community/join/route.ts | 44 +- app/api/community/leave/route.ts | 34 +- app/api/community/members/route.ts | 26 +- app/api/community/route.ts | 35 +- app/api/community/search/route.ts | 19 +- app/api/friends/[walletAddress]/route.ts | 34 +- app/api/friends/route.ts | 23 +- app/api/poker/bet/route.ts | 28 +- app/api/poker/claim/route.ts | 52 +- app/api/poker/tables/route.ts | 64 +- app/api/user/chain-accounts/route.ts | 12 +- app/api/user/nicknames/route.ts | 19 +- app/api/user/profile/route.ts | 13 +- app/chains/evm/adapter.ts | 36 +- app/chains/evm/chains.ts | 6 +- app/chains/near/adapter.ts | 24 +- app/chains/near/config.ts | 3 +- app/chains/polkadot/adapter.ts | 67 +- app/chains/polkadot/config.ts | 7 +- app/chains/registry.ts | 4 +- app/chains/solana/adapter.ts | 40 +- app/chains/solana/config.ts | 3 +- app/contexts/NetworkModeContext.tsx | 12 +- app/create-password/page.tsx | 138 +- .../championship/[challengeId]/page.tsx | 66 +- app/dashboard/championship/agents/page.tsx | 34 +- app/dashboard/championship/create/page.tsx | 55 +- app/dashboard/championship/page.tsx | 14 +- .../[communityId]/ActivityDetailSlideIn.tsx | 53 +- .../[communityId]/CreateActivitySlideIn.tsx | 54 +- .../community/[communityId]/page.tsx | 80 +- .../community/community-coins/page.tsx | 39 +- .../community/create-community/page.tsx | 36 +- app/dashboard/community/page.tsx | 4 +- app/dashboard/layout.tsx | 6 +- app/dashboard/poker/[tableId]/page.tsx | 541 ++-- app/dashboard/poker/page.tsx | 282 ++- .../settings/friends/AddFriendSheet.tsx | 38 +- .../settings/friends/EditFriendSheet.tsx | 83 +- .../settings/friends/FriendDetailSheet.tsx | 15 +- app/dashboard/settings/friends/page.tsx | 14 +- app/dashboard/settings/page.tsx | 80 +- .../settings/sheets/ChangePasswordSheet.tsx | 20 +- .../settings/sheets/SeedPhraseSheet.tsx | 50 +- app/dashboard/settings/sheets/TermsSheet.tsx | 50 +- .../settings/transaction-history/page.tsx | 130 +- app/dashboard/staking/page.tsx | 34 +- app/dashboard/wallet/import-wallet/page.tsx | 29 +- app/dashboard/wallet/page.tsx | 143 +- app/dashboard/wallet/payment-review/page.tsx | 85 +- app/dashboard/wallet/receive/page.tsx | 140 +- app/dashboard/wallet/scan/page.tsx | 27 +- app/dashboard/wallet/send/page.tsx | 38 +- app/db/supabase.ts | 719 +++--- app/globals.css | 160 +- app/login/page.tsx | 31 +- app/page.tsx | 27 +- app/services/escrow/solana.ts | 39 +- app/services/escrow/types.ts | 70 +- app/services/poker/contract.ts | 399 ++- app/services/poker/index.ts | 469 ++-- app/services/signing.ts | 22 +- app/services/treasury.ts | 238 +- app/types/constants.ts | 2 +- app/types/frontend_type.ts | 473 ++-- app/utils/api-auth.ts | 14 +- app/utils/auth.ts | 77 +- app/utils/crypto.ts | 108 +- app/utils/defillama.ts | 4 +- app/utils/password.ts | 6 +- app/utils/qr.ts | 32 +- app/utils/staking.ts | 104 +- app/utils/supabase-admin.ts | 11 + app/utils/wallet.ts | 82 +- app/welcome/page.tsx | 61 +- commitlint.config.js | 13 + components/SeedPhraseDisplay.tsx | 12 +- components/action-button.tsx | 8 +- components/championship/agent-card.tsx | 20 +- components/championship/bet-panel.tsx | 9 +- components/championship/challenge-card.tsx | 8 +- components/championship/phase-timeline.tsx | 20 +- components/championship/results-panel.tsx | 25 +- components/championship/vote-panel.tsx | 4 +- components/community/community-list.tsx | 14 +- components/community/index.ts | 7 +- .../community/swipeable-community-item.tsx | 36 +- components/crypto/ChainSelector.tsx | 10 +- components/crypto/CryptoIcon.tsx | 22 +- components/crypto/TokenPicker.tsx | 23 +- components/dashboard/BottomNav.tsx | 15 +- components/dashboard/Header.tsx | 13 +- components/dashboard/index.ts | 1 - components/layout/PageHeader.tsx | 4 +- components/layout/SlideInPage.tsx | 4 +- components/staking/PoolDetailSheet.tsx | 27 +- components/staking/PoolList.tsx | 15 +- components/staking/StakeActionSheet.tsx | 46 +- components/staking/StakingStatusCard.tsx | 29 +- components/ui/button.tsx | 24 +- components/ui/input.tsx | 8 +- components/ui/sheet.tsx | 37 +- components/ui/switch.tsx | 14 +- components/ui/tab-button.tsx | 4 +- components/ui/textarea.tsx | 8 +- contracts/README.md | 24 +- contracts/polkadot/arch.md | 59 +- contracts/solana/championship_escrow.json | 566 +---- dealer/dealer.js | 1038 ++++---- dealer/package-lock.json | 2198 ++++++++--------- dealer/package.json | 30 +- dealer/poker.js | 140 +- docs/agent-onboarding.md | 81 +- eslint.config.mjs | 12 + hooks/useAgents.ts | 13 +- hooks/useAuth.ts | 2 +- hooks/useChampionship.ts | 9 +- hooks/useCoins.ts | 4 +- hooks/useEcosystemProjects.ts | 12 +- hooks/usePoker.ts | 239 +- hooks/useStaking.ts | 87 +- lib/avatar.ts | 13 +- lib/format.ts | 14 +- lib/utils.ts | 6 +- package-lock.json | 1630 +++++++++++- package.json | 12 +- public/manifest.json | 1 - public/sw.js | 33 +- tests/api/activity/comment.test.ts | 117 +- tests/api/activity/join.test.ts | 153 +- tests/api/activity/leave.test.ts | 87 +- tests/api/activity/like.test.ts | 83 +- tests/api/auth/nonce.test.ts | 60 +- tests/api/auth/verify.test.ts | 106 +- tests/api/community/join.test.ts | 97 +- tests/api/community/leave.test.ts | 103 +- tests/api/community/route.test.ts | 105 +- tests/api/friends/route.test.ts | 108 +- tests/components/ActionButton.test.tsx | 55 +- tests/components/CommunityPage.test.tsx | 105 +- tests/components/EmptyState.test.tsx | 58 +- tests/components/LoginPage.test.tsx | 117 +- tests/components/SeedPhraseDisplay.test.tsx | 126 +- tests/components/WalletPage.test.tsx | 144 +- tests/setup/fixtures.ts | 187 +- tests/setup/mocks/polkadot.ts | 44 +- tests/setup/mocks/supabase.ts | 5 +- tests/setup/setup.ts | 15 +- tests/setup/test-utils.tsx | 22 +- tests/tsconfig.json | 5 +- tests/unit/db/supabase.test.ts | 86 +- tests/unit/utils/auth.test.ts | 175 +- tests/unit/utils/crypto.test.ts | 151 +- tests/unit/utils/wallet.test.ts | 153 +- tsconfig.json | 18 +- vitest.config.ts | 40 +- 196 files changed, 8739 insertions(+), 8359 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .husky/commit-msg create mode 100644 .husky/pre-commit create mode 100644 .husky/pre-push create mode 100644 .lintstagedrc.json create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 app/utils/supabase-admin.ts create mode 100644 commitlint.config.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0a45cdb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +jobs: + lint-and-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Format check + run: npm run format:check + + - name: Type check + run: npx tsc --noEmit + + - name: Test + run: npm run test + + - name: Test coverage + run: npm run test:coverage + + - name: Build + run: npm run build + env: + NEXT_PUBLIC_SUPABASE_URL: https://placeholder.supabase.co + NEXT_PUBLIC_SUPABASE_ANON_KEY: placeholder-anon-key-for-ci-build diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..0a4b97d --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +npx --no -- commitlint --edit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100644 index 0000000..ab4c119 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,2 @@ +npm run lint +npm run test diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 0000000..f7aa5b1 --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,4 @@ +{ + "*.{ts,tsx,js,jsx,mjs}": ["eslint --fix", "prettier --write"], + "*.{json,md,css}": ["prettier --write"] +} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..07395a9 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,16 @@ +# Dependencies +node_modules +.pnp +.pnp.* + +# Build outputs +.next +out +build +dist + +# Generated +.papi + +# Coverage +coverage diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..1a88ab1 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": false, + "tabWidth": 2, + "trailingComma": "es5", + "printWidth": 100 +} diff --git a/README.md b/README.md index ed8e2dd..9ab8ee5 100644 --- a/README.md +++ b/README.md @@ -34,86 +34,92 @@ Relay is an Web3 application that combines community management with cryptocurre ### Technology Stack -| Layer | Technology | -|-------|------------| -| Frontend | Next.js 16, React 19, TailwindCSS 4 | -| Backend | Next.js API Routes (Edge-compatible) | -| Database | Supabase (PostgreSQL with RLS), only for community and activity storage | -| Blockchain | Polkadot Asset Hub via Polkadot API | -| Authentication | Wallet-based JWT (sr25519 signatures) | -| Testing | Vitest, Testing Library | +| Layer | Technology | +| -------------- | ----------------------------------------------------------------------- | +| Frontend | Next.js 16, React 19, TailwindCSS 4 | +| Backend | Next.js API Routes (Edge-compatible) | +| Database | Supabase (PostgreSQL with RLS), only for community and activity storage | +| Blockchain | Polkadot Asset Hub via Polkadot API | +| Authentication | Wallet-based JWT (sr25519 signatures) | +| Testing | Vitest, Testing Library | --- ## Product Architecture ### Backend Clarification + ┌─────────────────────────────────────────────────────────────────────────────┐ -│ Web2 + Web3 SERVICES │ +│ Web2 + Web3 SERVICES │ ├─────────────────────────────────────────────────────────────────────────────┤ -│ ┌───────────────────────────┐ ┌───────────────────────────────┐ │ -│ │ Supabase Database │ │ Polkadot Asset Hub │ │ -│ │ (PostgreSQL + RLS) │ │ (Blockchain) │ │ -│ │ │ │ │ │ -│ │ - users │ │ - Native DOT transfers │ │ -│ │ - communities │ │ - Asset Hub tokens │ │ -│ │ - activities │ │ │ │ -│ │ - comments │ │ │ │ -│ │ - friends │ │ │ │ -│ │ - transactions │ │ │ │ -│ │ - community_tokens │ │ │ │ -│ │ - known_assets │ │ │ │ -│ └───────────────────────────┘ └───────────────────────────────┘ │ +│ ┌───────────────────────────┐ ┌───────────────────────────────┐ │ +│ │ Supabase Database │ │ Polkadot Asset Hub │ │ +│ │ (PostgreSQL + RLS) │ │ (Blockchain) │ │ +│ │ │ │ │ │ +│ │ - users │ │ - Native DOT transfers │ │ +│ │ - communities │ │ - Asset Hub tokens │ │ +│ │ - activities │ │ │ │ +│ │ - comments │ │ │ │ +│ │ - friends │ │ │ │ +│ │ - transactions │ │ │ │ +│ │ - community_tokens │ │ │ │ +│ │ - known_assets │ │ │ │ +│ └───────────────────────────┘ └───────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ + ``` ### Directory Structure ``` + relay-new/ -├── app/ # Next.js App Router -│ ├── api/ # API Routes -│ │ ├── auth/ # Authentication endpoints -│ │ │ ├── nonce/ # Generate auth nonce -│ │ │ └── verify/ # Verify signature & issue JWT -│ │ ├── activity/ # Activity management -│ │ ├── community/ # Community CRUD operations -│ │ ├── friends/ # Friend list management -│ │ └── user/ # User profile operations -│ ├── dashboard/ # Main application pages -│ │ ├── community/ # Community views -│ │ ├── settings/ # User settings -│ │ └── wallet/ # Wallet operations -│ ├── db/ # Database utilities -│ │ └── supabase.ts # Supabase client & operations -│ ├── types/ # TypeScript type definitions -│ │ ├── frontend_type.ts # Domain types -│ │ └── constants.ts # App constants -│ └── utils/ # Utility functions -│ └── auth.ts # Authentication utilities -├── components/ # Reusable UI components -├── lib/ # Shared libraries -├── public/ # Static assets -├── tests/ # Test suites -├── supabase-schema.sql # Main database schema +├── app/ # Next.js App Router +│ ├── api/ # API Routes +│ │ ├── auth/ # Authentication endpoints +│ │ │ ├── nonce/ # Generate auth nonce +│ │ │ └── verify/ # Verify signature & issue JWT +│ │ ├── activity/ # Activity management +│ │ ├── community/ # Community CRUD operations +│ │ ├── friends/ # Friend list management +│ │ └── user/ # User profile operations +│ ├── dashboard/ # Main application pages +│ │ ├── community/ # Community views +│ │ ├── settings/ # User settings +│ │ └── wallet/ # Wallet operations +│ ├── db/ # Database utilities +│ │ └── supabase.ts # Supabase client & operations +│ ├── types/ # TypeScript type definitions +│ │ ├── frontend_type.ts # Domain types +│ │ └── constants.ts # App constants +│ └── utils/ # Utility functions +│ └── auth.ts # Authentication utilities +├── components/ # Reusable UI components +├── lib/ # Shared libraries +├── public/ # Static assets +├── tests/ # Test suites +├── supabase-schema.sql # Main database schema ├── supabase-community-tokens.sql # Token extension schema -└── supabase-migration-*.sql # Migration scripts +└── supabase-migration-\*.sql # Migration scripts + ``` ### Data Flow ``` -┌──────────────┐ ┌──────────────┐ ┌──────────────┐ -│ Frontend │────▶│ Next.js │────▶│ Supabase │ -│ Components │ │ API Routes │ │ Database │ -└──────────────┘ └──────────────┘ └──────────────┘ - │ │ │ - │ │ │ - ▼ ▼ ▼ -┌──────────────┐ ┌──────────────┐ ┌──────────────┐ -│ supabase.ts │ │ auth.ts │ │ RLS Policies│ -│ (queries) │ │ (server) │ │ (security) │ -└──────────────┘ └──────────────┘ └──────────────┘ + +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ Frontend │────▶│ Next.js │────▶│ Supabase │ +│ Components │ │ API Routes │ │ Database │ +└──────────────┘ └──────────────┘ └──────────────┘ +│ │ │ +│ │ │ +▼ ▼ ▼ +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ supabase.ts │ │ auth.ts │ │ RLS Policies│ +│ (queries) │ │ (server) │ │ (security) │ +└──────────────┘ └──────────────┘ └──────────────┘ + ``` --- @@ -127,41 +133,43 @@ Relay uses a cryptographic signature-based authentication system. Users prove ow **How it works:** ``` + ┌─────────────────────────────────────────────────────────────────────────────┐ -│ AUTHENTICATION FLOW │ +│ AUTHENTICATION FLOW │ ├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ 1. User wants to authenticate │ -│ └─▶ Call authenticateWithWallet() from auth.ts │ -│ │ -│ 2. Get wallet address from stored mnemonic │ -│ └─▶ Uses @polkadot/keyring with sr25519 curve │ -│ │ -│ 3. Request nonce from server │ -│ └─▶ POST /api/auth/nonce { walletAddress } │ -│ └─▶ Server stores nonce in auth_nonces table (5 min expiry) │ -│ └─▶ Returns { message, nonce } │ -│ │ -│ 4. Sign the message with wallet │ -│ └─▶ Uses keypair.sign(message) with sr25519 │ -│ └─▶ Creates cryptographic proof of wallet ownership │ -│ │ -│ 5. Verify signature and get JWT │ -│ └─▶ POST /api/auth/verify { walletAddress, signature, nonce } │ -│ └─▶ Server verifies using @polkadot/util-crypto signatureVerify │ -│ └─▶ Server creates/updates user in users table │ -│ └─▶ Server generates JWT with wallet_address claim (24h expiry) │ -│ └─▶ Returns { token } │ -│ │ -│ 6. Set JWT in Supabase client │ -│ └─▶ All subsequent requests include the JWT │ -│ │ -│ 7. RLS policies check JWT for wallet_address │ -│ └─▶ auth.jwt() ->> 'wallet_address' in SQL policies │ -│ └─▶ Users can only access their own data │ -│ │ +│ │ +│ 1. User wants to authenticate │ +│ └─▶ Call authenticateWithWallet() from auth.ts │ +│ │ +│ 2. Get wallet address from stored mnemonic │ +│ └─▶ Uses @polkadot/keyring with sr25519 curve │ +│ │ +│ 3. Request nonce from server │ +│ └─▶ POST /api/auth/nonce { walletAddress } │ +│ └─▶ Server stores nonce in auth_nonces table (5 min expiry) │ +│ └─▶ Returns { message, nonce } │ +│ │ +│ 4. Sign the message with wallet │ +│ └─▶ Uses keypair.sign(message) with sr25519 │ +│ └─▶ Creates cryptographic proof of wallet ownership │ +│ │ +│ 5. Verify signature and get JWT │ +│ └─▶ POST /api/auth/verify { walletAddress, signature, nonce } │ +│ └─▶ Server verifies using @polkadot/util-crypto signatureVerify │ +│ └─▶ Server creates/updates user in users table │ +│ └─▶ Server generates JWT with wallet_address claim (24h expiry) │ +│ └─▶ Returns { token } │ +│ │ +│ 6. Set JWT in Supabase client │ +│ └─▶ All subsequent requests include the JWT │ +│ │ +│ 7. RLS policies check JWT for wallet_address │ +│ └─▶ auth.jwt() ->> 'wallet_address' in SQL policies │ +│ └─▶ Users can only access their own data │ +│ │ └─────────────────────────────────────────────────────────────────────────────┘ -``` + +```` **Security Features:** - **No password storage**: Authentication is based on cryptographic signatures @@ -230,7 +238,7 @@ Before installation, ensure you have: ```bash git clone https://github.com/MontaQ-Labs/relay-new.git cd relay-new -``` +```` ### Step 2: Install Dependencies @@ -239,6 +247,7 @@ npm install ``` This will: + - Install all npm packages - Run `papi generate` (postinstall script) to generate Polkadot API descriptors @@ -271,12 +280,12 @@ Once your project is ready: 1. Go to **Settings** → **API** 2. Note down these values: -| Key | Description | Location | -|-----|-------------|----------| -| **Project URL** | Supabase API endpoint | "Project URL" section | -| **anon/public key** | Client-side key (safe to expose) | "Project API keys" → `anon` `public` | -| **service_role key** | Server-side key (**SECRET**) | "Project API keys" → `service_role` | -| **JWT Secret** | For signing tokens | "JWT Settings" → `JWT Secret` | +| Key | Description | Location | +| -------------------- | -------------------------------- | ------------------------------------ | +| **Project URL** | Supabase API endpoint | "Project URL" section | +| **anon/public key** | Client-side key (safe to expose) | "Project API keys" → `anon` `public` | +| **service_role key** | Server-side key (**SECRET**) | "Project API keys" → `service_role` | +| **JWT Secret** | For signing tokens | "JWT Settings" → `JWT Secret` | ### Step 3: Create Environment File @@ -306,12 +315,12 @@ SUPABASE_JWT_SECRET=your-jwt-secret-from-supabase-dashboard Keys should look like: -| Variable | Format | -|----------|--------| -| URL | `https://abcdefghij.supabase.co` (~30 chars) | -| anon key | `eyJhbGciOiJI...` (~200+ chars, starts with `eyJ`) | +| Variable | Format | +| ---------------- | -------------------------------------------------- | +| URL | `https://abcdefghij.supabase.co` (~30 chars) | +| anon key | `eyJhbGciOiJI...` (~200+ chars, starts with `eyJ`) | | service_role key | `eyJhbGciOiJI...` (~200+ chars, starts with `eyJ`) | -| JWT Secret | Random string (~40+ chars) | +| JWT Secret | Random string (~40+ chars) | --- @@ -327,6 +336,7 @@ Keys should look like: 6. Click **"Run"** (or Cmd/Ctrl + Enter) Expected output: + ``` ✅ Relay database schema created successfully! @@ -352,6 +362,7 @@ To enable community tokens on Polkadot Asset Hub: 2. Run in SQL Editor Expected output: + ``` ✅ Community tokens schema extension created successfully! @@ -414,19 +425,19 @@ ON CONFLICT (asset_id) DO UPDATE SET 1. Go to **Table Editor** in the sidebar 2. Verify these tables exist: -| Table | Purpose | -|-------|---------| -| `users` | User profiles | -| `auth_nonces` | Authentication nonces | -| `friends` | User friend lists | -| `transactions` | Transaction records | -| `communities` | Community data | -| `community_members` | Community membership | -| `activities` | Community activities | -| `activity_attendees` | Activity participation | -| `comments` | Activity comments | -| `community_tokens` | Community token configs | -| `known_assets` | Polkadot Asset Hub tokens | +| Table | Purpose | +| -------------------- | ------------------------- | +| `users` | User profiles | +| `auth_nonces` | Authentication nonces | +| `friends` | User friend lists | +| `transactions` | Transaction records | +| `communities` | Community data | +| `community_members` | Community membership | +| `activities` | Community activities | +| `activity_attendees` | Activity participation | +| `comments` | Activity comments | +| `community_tokens` | Community token configs | +| `known_assets` | Polkadot Asset Hub tokens | --- @@ -452,16 +463,16 @@ npm start ### Available Scripts -| Script | Command | Description | -|--------|---------|-------------| -| `dev` | `npm run dev` | Start development server | -| `build` | `npm run build` | Build for production | -| `start` | `npm start` | Start production server | -| `lint` | `npm run lint` | Run ESLint | -| `test` | `npm test` | Run all tests | -| `test:watch` | `npm run test:watch` | Run tests in watch mode | -| `test:ui` | `npm run test:ui` | Run tests with UI | -| `test:coverage` | `npm run test:coverage` | Run tests with coverage | +| Script | Command | Description | +| --------------- | ----------------------- | ------------------------ | +| `dev` | `npm run dev` | Start development server | +| `build` | `npm run build` | Build for production | +| `start` | `npm start` | Start production server | +| `lint` | `npm run lint` | Run ESLint | +| `test` | `npm test` | Run all tests | +| `test:watch` | `npm run test:watch` | Run tests in watch mode | +| `test:ui` | `npm run test:ui` | Run tests with UI | +| `test:coverage` | `npm run test:coverage` | Run tests with coverage | --- @@ -474,6 +485,7 @@ npm start Generate an authentication nonce for wallet signature. **Request:** + ```json { "walletAddress": "1A2B3C4D..." @@ -481,6 +493,7 @@ Generate an authentication nonce for wallet signature. ``` **Response:** + ```json { "message": "Sign this message to authenticate with Relay...", @@ -493,6 +506,7 @@ Generate an authentication nonce for wallet signature. Verify wallet signature and receive JWT token. **Request:** + ```json { "walletAddress": "1A2B3C4D...", @@ -502,6 +516,7 @@ Verify wallet signature and receive JWT token. ``` **Response:** + ```json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." @@ -510,40 +525,40 @@ Verify wallet signature and receive JWT token. ### Community Endpoints -| Method | Endpoint | Description | -|--------|----------|-------------| -| GET | `/api/community` | List all communities | -| POST | `/api/community/create` | Create new community | -| GET | `/api/community/search?q=term` | Search communities | -| POST | `/api/community/join` | Join a community | -| POST | `/api/community/leave` | Leave a community | -| GET | `/api/community/members` | Get community members | +| Method | Endpoint | Description | +| ------ | ------------------------------ | --------------------- | +| GET | `/api/community` | List all communities | +| POST | `/api/community/create` | Create new community | +| GET | `/api/community/search?q=term` | Search communities | +| POST | `/api/community/join` | Join a community | +| POST | `/api/community/leave` | Leave a community | +| GET | `/api/community/members` | Get community members | ### Activity Endpoints -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/api/activity/join` | Join an activity | -| POST | `/api/activity/leave` | Leave an activity | -| POST | `/api/activity/like` | Like an activity | -| POST | `/api/activity/comment` | Add a comment | -| GET | `/api/activity/comments` | Get activity comments | +| Method | Endpoint | Description | +| ------ | ------------------------ | --------------------- | +| POST | `/api/activity/join` | Join an activity | +| POST | `/api/activity/leave` | Leave an activity | +| POST | `/api/activity/like` | Like an activity | +| POST | `/api/activity/comment` | Add a comment | +| GET | `/api/activity/comments` | Get activity comments | ### User Endpoints -| Method | Endpoint | Description | -|--------|----------|-------------| -| GET | `/api/user/profile` | Get user profile | -| GET | `/api/user/nicknames` | Get nicknames for addresses | +| Method | Endpoint | Description | +| ------ | --------------------- | --------------------------- | +| GET | `/api/user/profile` | Get user profile | +| GET | `/api/user/nicknames` | Get nicknames for addresses | ### Friends Endpoints -| Method | Endpoint | Description | -|--------|----------|-------------| -| GET | `/api/friends` | List all friends | -| POST | `/api/friends` | Add a friend | -| PUT | `/api/friends/[walletAddress]` | Update friend | -| DELETE | `/api/friends/[walletAddress]` | Remove friend | +| Method | Endpoint | Description | +| ------ | ------------------------------ | ---------------- | +| GET | `/api/friends` | List all friends | +| POST | `/api/friends` | Add a friend | +| PUT | `/api/friends/[walletAddress]` | Update friend | +| DELETE | `/api/friends/[walletAddress]` | Remove friend | --- @@ -620,22 +635,27 @@ SUPABASE_JWT_SECRET=your-secret ### Common Errors #### "Wallet address is required" + - **Cause**: Calling auth without a wallet - **Fix**: Ensure user has created/imported a wallet first #### "Invalid or expired nonce" + - **Cause**: Nonce expired (>5 min) or already used - **Fix**: Request a new nonce and try again #### "Invalid signature" + - **Cause**: Message was modified or wrong key used - **Fix**: Ensure the exact message from server is signed #### "Permission denied for table..." + - **Cause**: RLS policy blocking access - **Fix**: Check that JWT contains correct `wallet_address` #### "JWT expired" + - **Cause**: Token older than 24 hours - **Fix**: Call `refreshAuth()` to get new token @@ -644,6 +664,7 @@ SUPABASE_JWT_SECRET=your-secret 1. **Check browser console** for detailed error messages 2. **Verify environment variables**: + ```bash echo $NEXT_PUBLIC_SUPABASE_URL ``` @@ -652,6 +673,7 @@ SUPABASE_JWT_SECRET=your-secret - Go to Dashboard → Logs → Postgres 4. **Test RLS policies** in SQL Editor: + ```sql SELECT auth.jwt(); ``` @@ -707,12 +729,12 @@ If you need to start fresh: ### Environment Variable Security -| Variable | Exposure | Notes | -|----------|----------|-------| -| `NEXT_PUBLIC_SUPABASE_URL` | Public | Safe to expose | -| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Public | Safe to expose | -| `SUPABASE_SERVICE_ROLE_KEY` | **Secret** | Server-only | -| `SUPABASE_JWT_SECRET` | **Secret** | Server-only | +| Variable | Exposure | Notes | +| ------------------------------- | ---------- | -------------- | +| `NEXT_PUBLIC_SUPABASE_URL` | Public | Safe to expose | +| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Public | Safe to expose | +| `SUPABASE_SERVICE_ROLE_KEY` | **Secret** | Server-only | +| `SUPABASE_JWT_SECRET` | **Secret** | Server-only | --- @@ -736,7 +758,7 @@ This project is open source. See the LICENSE file for details. ## Support For questions or issues: + - Open a GitHub issue - Check the [Supabase documentation](https://supabase.com/docs) - Check the [Polkadot documentation](https://wiki.polkadot.network) - diff --git a/SUPABASE_SETUP.md b/SUPABASE_SETUP.md index 4ccecfa..6ef2cdd 100644 --- a/SUPABASE_SETUP.md +++ b/SUPABASE_SETUP.md @@ -61,12 +61,12 @@ Once your project is ready: 1. Go to **Settings** (gear icon) → **API** 2. Note down these values: -| Key | Description | Where to find | -|-----|-------------|---------------| -| **Project URL** | Your Supabase API endpoint | Under "Project URL" | -| **anon/public key** | Safe to expose in frontend | Under "Project API keys" → `anon` `public` | -| **service_role key** | **SECRET** - server only | Under "Project API keys" → `service_role` | -| **JWT Secret** | For signing tokens | Under "JWT Settings" → `JWT Secret` | +| Key | Description | Where to find | +| -------------------- | -------------------------- | ------------------------------------------ | +| **Project URL** | Your Supabase API endpoint | Under "Project URL" | +| **anon/public key** | Safe to expose in frontend | Under "Project API keys" → `anon` `public` | +| **service_role key** | **SECRET** - server only | Under "Project API keys" → `service_role` | +| **JWT Secret** | For signing tokens | Under "JWT Settings" → `JWT Secret` | ⚠️ **IMPORTANT**: Never expose the `service_role` key or `JWT Secret` in frontend code! @@ -201,7 +201,7 @@ INSERT INTO known_assets (asset_id, ticker, decimals, symbol, category) VALUES -- Stablecoins (1984, 'USDt', 6, 'https://assets.coingecko.com/coins/images/325/small/Tether.png', 'stablecoin'), (1337, 'USDC', 6, 'https://assets.coingecko.com/coins/images/6319/small/usdc.png', 'stablecoin'), - + -- Popular meme/community tokens (30, 'DED', 10, 'https://raw.githubusercontent.com/nicpick/logos/main/DED-LOGO-EYE.png', 'meme'), (18, 'DOTA', 4, 'https://raw.githubusercontent.com/nicpick/logos/main/dota.png', 'meme'), @@ -209,11 +209,11 @@ INSERT INTO known_assets (asset_id, ticker, decimals, symbol, category) VALUES (31337, 'WUD', 10, 'https://raw.githubusercontent.com/nicpick/logos/main/WUD.png', 'meme'), (17, 'WIFD', 10, 'https://raw.githubusercontent.com/nicpick/logos/main/wifd.png', 'meme'), (42069, 'STINK', 10, 'https://raw.githubusercontent.com/nicpick/logos/main/stink.png', 'meme'), - + -- Utility/project tokens (1107, 'TSN', 18, 'https://raw.githubusercontent.com/nicpick/logos/main/tsn.png', 'utility'), (50000111, 'DON', 10, 'https://raw.githubusercontent.com/nicpick/logos/main/don.png', 'utility'), - + -- Bridged/wrapped assets (21, 'vDOT', 10, 'https://raw.githubusercontent.com/nicpick/logos/main/vdot.png', 'bridged'), (8, 'RMRK', 10, 'https://assets.coingecko.com/coins/images/15320/small/RMRK.png', 'bridged') @@ -362,6 +362,7 @@ npm install @supabase/supabase-js jose ``` These packages provide: + - `@supabase/supabase-js`: Supabase client for database operations - `jose`: JWT library for creating authentication tokens (Edge-compatible) @@ -471,44 +472,48 @@ app/ ### Authentication Functions (auth.ts) -| Function | Description | -|----------|-------------| -| `authenticateWithWallet(mnemonic?)` | Full auth flow, returns JWT | -| `isAuthenticated()` | Check if user has valid session | -| `getAuthenticatedWallet()` | Get current wallet address | -| `signOut()` | Clear authentication session | -| `restoreSession()` | Restore session from stored token | +| Function | Description | +| ----------------------------------- | --------------------------------- | +| `authenticateWithWallet(mnemonic?)` | Full auth flow, returns JWT | +| `isAuthenticated()` | Check if user has valid session | +| `getAuthenticatedWallet()` | Get current wallet address | +| `signOut()` | Clear authentication session | +| `restoreSession()` | Restore session from stored token | ### Database Operations (supabase.ts) #### Users -| Function | Description | -|----------|-------------| -| `getCurrentUser()` | Get authenticated user's full profile | -| `getUserByWallet(address)` | Get any user by wallet address | -| `updateUserProfile(address, updates)` | Update avatar/nickname | + +| Function | Description | +| ------------------------------------- | ------------------------------------- | +| `getCurrentUser()` | Get authenticated user's full profile | +| `getUserByWallet(address)` | Get any user by wallet address | +| `updateUserProfile(address, updates)` | Update avatar/nickname | #### Friends -| Function | Description | -|----------|-------------| -| `getFriends(walletAddress)` | Get user's friend list | -| `addFriend(userWallet, friend)` | Add a new friend | -| `updateFriend(userWallet, friendAddress, updates)` | Update friend info | -| `removeFriend(userWallet, friendAddress)` | Remove a friend | + +| Function | Description | +| -------------------------------------------------- | ---------------------- | +| `getFriends(walletAddress)` | Get user's friend list | +| `addFriend(userWallet, friend)` | Add a new friend | +| `updateFriend(userWallet, friendAddress, updates)` | Update friend info | +| `removeFriend(userWallet, friendAddress)` | Remove a friend | #### Communities -| Function | Description | -|----------|-------------| -| `getAllCommunities()` | Get all public communities | -| `getUserCommunities(walletAddress)` | Get communities user belongs to | -| `getCommunity(communityId)` | Get single community by ID | -| `createCommunity(ownerWallet, data)` | Create new community (name, description, activityTypes, etc.) | -| `updateCommunity(communityId, updates)` | Update community (owner only) | -| `deleteCommunity(communityId)` | Delete community (owner only) | -| `joinCommunity(communityId, userWallet)` | Join a community | -| `leaveCommunity(communityId, userWallet)` | Leave a community | + +| Function | Description | +| ----------------------------------------- | ------------------------------------------------------------- | +| `getAllCommunities()` | Get all public communities | +| `getUserCommunities(walletAddress)` | Get communities user belongs to | +| `getCommunity(communityId)` | Get single community by ID | +| `createCommunity(ownerWallet, data)` | Create new community (name, description, activityTypes, etc.) | +| `updateCommunity(communityId, updates)` | Update community (owner only) | +| `deleteCommunity(communityId)` | Delete community (owner only) | +| `joinCommunity(communityId, userWallet)` | Join a community | +| `leaveCommunity(communityId, userWallet)` | Leave a community | **Community Fields:** + - `name` - Community name (required) - `description` - Community description, at least 10 words (required) - `avatar` - Link to community avatar image (optional) @@ -517,53 +522,59 @@ app/ - `allowInvestment` - Whether investment is allowed (default: true) #### Activities -| Function | Description | -|----------|-------------| -| `getCommunityActivities(communityId)` | Get activities in a community | -| `getUserActivities(walletAddress)` | Get user's activities | -| `getActivity(activityId)` | Get single activity by ID | -| `createActivity(ownerWallet, data)` | Create new activity | -| `updateActivity(activityId, updates)` | Update activity (owner only) | -| `deleteActivity(activityId)` | Delete activity (owner only) | -| `joinActivity(activityId, userWallet)` | Join an activity | -| `leaveActivity(activityId, userWallet)` | Leave an activity | -| `likeActivity(activityId)` | Like an activity | + +| Function | Description | +| --------------------------------------- | ----------------------------- | +| `getCommunityActivities(communityId)` | Get activities in a community | +| `getUserActivities(walletAddress)` | Get user's activities | +| `getActivity(activityId)` | Get single activity by ID | +| `createActivity(ownerWallet, data)` | Create new activity | +| `updateActivity(activityId, updates)` | Update activity (owner only) | +| `deleteActivity(activityId)` | Delete activity (owner only) | +| `joinActivity(activityId, userWallet)` | Join an activity | +| `leaveActivity(activityId, userWallet)` | Leave an activity | +| `likeActivity(activityId)` | Like an activity | #### Comments -| Function | Description | -|----------|-------------| -| `getActivityComments(activityId)` | Get comments on activity | -| `getComment(commentId)` | Get single comment by ID | -| `createComment(publisherWallet, activityId, content)` | Create comment | -| `updateComment(commentId, content)` | Update comment (publisher only) | -| `deleteComment(commentId)` | Delete comment (publisher only) | -| `likeComment(commentId)` | Like a comment | + +| Function | Description | +| ----------------------------------------------------- | ------------------------------- | +| `getActivityComments(activityId)` | Get comments on activity | +| `getComment(commentId)` | Get single comment by ID | +| `createComment(publisherWallet, activityId, content)` | Create comment | +| `updateComment(commentId, content)` | Update comment (publisher only) | +| `deleteComment(commentId)` | Delete comment (publisher only) | +| `likeComment(commentId)` | Like a comment | #### Subscriptions (Real-time) -| Function | Description | -|----------|-------------| -| `subscribeToActivities(communityId, callback)` | Live activity updates | -| `subscribeToComments(activityId, callback)` | Live comment updates | -| `unsubscribe(subscription)` | Unsubscribe from channel | + +| Function | Description | +| ---------------------------------------------- | ------------------------ | +| `subscribeToActivities(communityId, callback)` | Live activity updates | +| `subscribeToComments(activityId, callback)` | Live comment updates | +| `unsubscribe(subscription)` | Unsubscribe from channel | #### Community Tokens (Polkadot Asset Hub) -| Function | Description | -|----------|-------------| -| `getCommunityToken(communityId)` | Get token config for a community | -| `createCommunityToken(communityId, tokenData)` | Create token record (owner only) | -| `updateCommunityToken(communityId, updates)` | Update token config (owner only) | -| `deleteCommunityToken(communityId)` | Delete token record (owner only) | -| `updateTokenSupply(communityId, newSupply)` | Update total supply after minting | -| `setTokenFrozen(communityId, isFrozen)` | Toggle token frozen status | + +| Function | Description | +| ---------------------------------------------- | --------------------------------- | +| `getCommunityToken(communityId)` | Get token config for a community | +| `createCommunityToken(communityId, tokenData)` | Create token record (owner only) | +| `updateCommunityToken(communityId, updates)` | Update token config (owner only) | +| `deleteCommunityToken(communityId)` | Delete token record (owner only) | +| `updateTokenSupply(communityId, newSupply)` | Update total supply after minting | +| `setTokenFrozen(communityId, isFrozen)` | Toggle token frozen status | #### Known Assets (Polkadot Asset Hub Bazaar) -| Function | Description | -|----------|-------------| -| `getKnownAssets()` | Get all known assets for the Bazaar | -| `getKnownAssetById(assetId)` | Get a specific known asset by ID | -| `getKnownAssetsByCategory(category)` | Get assets filtered by category | + +| Function | Description | +| ------------------------------------ | ----------------------------------- | +| `getKnownAssets()` | Get all known assets for the Bazaar | +| `getKnownAssetById(assetId)` | Get a specific known asset by ID | +| `getKnownAssetsByCategory(category)` | Get assets filtered by category | **KnownAsset Fields:** + - `id` - Unique numeric asset ID on Polkadot Asset Hub (u32) - `ticker` - Token ticker symbol (e.g., "USDt", "USDC", "DED") - `decimals` - Number of decimal places (typically 6-18) @@ -571,13 +582,15 @@ app/ - `category` - Optional category: "stablecoin", "meme", "utility", "bridged" #### Ecosystem Projects (Multi-chain Explore) -| Function | Description | -|----------|-------------| -| `getEcosystemProjects()` | Get all curated DApps/protocols ordered by display_order | + +| Function | Description | +| -------------------------------------- | -------------------------------------------------------------- | +| `getEcosystemProjects()` | Get all curated DApps/protocols ordered by display_order | | `getEcosystemProjectsByChain(chainId)` | Get projects filtered by chain (polkadot, base, solana, monad) | -| `getFeaturedProjects()` | Get featured projects only | +| `getFeaturedProjects()` | Get featured projects only | **EcosystemProject Fields:** + - `id` - UUID primary key - `name` - Project name (e.g., "Jupiter", "Uniswap") - `slug` - URL-safe unique identifier @@ -597,22 +610,27 @@ app/ ### Common Errors #### "Wallet address is required" + - **Cause**: Calling auth without a wallet - **Fix**: Ensure user has created/imported a wallet first #### "Invalid or expired nonce" + - **Cause**: Nonce expired (>5 min) or already used - **Fix**: Request a new nonce and try again #### "Invalid signature" + - **Cause**: Message was modified or wrong key used - **Fix**: Ensure the exact message from server is signed #### "Permission denied for table..." + - **Cause**: RLS policy blocking access - **Fix**: Check that JWT contains correct `wallet_address` #### "JWT expired" + - **Cause**: Token older than 24 hours - **Fix**: Call `refreshAuth()` to get new token @@ -621,6 +639,7 @@ app/ 1. **Check browser console** for detailed error messages 2. **Verify environment variables**: + ```bash echo $NEXT_PUBLIC_SUPABASE_URL ``` diff --git a/TECHNICAL_ARCHITECTURE.md b/TECHNICAL_ARCHITECTURE.md index deb6652..c71449e 100644 --- a/TECHNICAL_ARCHITECTURE.md +++ b/TECHNICAL_ARCHITECTURE.md @@ -99,11 +99,11 @@ interface ChainAdapter { chainId: ChainId; chainName: string; chainType: "substrate" | "evm" | "solana" | "near"; - + // Account operations deriveAddress(mnemonic: string): Promise; isValidAddress(address: string): boolean; - + // Balance & transaction operations fetchBalances(address: string): Promise; estimateFee(params: TransferParams): Promise; @@ -114,13 +114,13 @@ interface ChainAdapter { ### Supported Chains -| Chain | Type | Native Token | RPC Endpoint | Adapter | -|-------|------|--------------|--------------|---------| -| Polkadot Asset Hub | Substrate | DOT | `wss://polkadot-asset-hub-rpc.polkadot.io` | `PolkadotChainAdapter` | -| Solana | Solana | SOL | `https://api.testnet.solana.com` | `SolanaChainAdapter` | -| NEAR | NEAR | NEAR | `https://rpc.testnet.near.org` | `NearChainAdapter` | -| Base | EVM | ETH | `https://sepolia.base.org` | `EVMChainAdapter` | -| Monad | EVM | MON | Custom RPC | `EVMChainAdapter` | +| Chain | Type | Native Token | RPC Endpoint | Adapter | +| ------------------ | --------- | ------------ | ------------------------------------------ | ---------------------- | +| Polkadot Asset Hub | Substrate | DOT | `wss://polkadot-asset-hub-rpc.polkadot.io` | `PolkadotChainAdapter` | +| Solana | Solana | SOL | `https://api.testnet.solana.com` | `SolanaChainAdapter` | +| NEAR | NEAR | NEAR | `https://rpc.testnet.near.org` | `NearChainAdapter` | +| Base | EVM | ETH | `https://sepolia.base.org` | `EVMChainAdapter` | +| Monad | EVM | MON | Custom RPC | `EVMChainAdapter` | ### Address Derivation @@ -135,13 +135,13 @@ const pair = keyring.addFromMnemonic(mnemonic); // Solana: Ed25519 from BIP-44 path m/44'/501'/0'/0' const seed = mnemonicToSeedSync(mnemonic); -const derivedSeed = derivePath("m/44'/501'/0'/0'", seed.toString('hex')); +const derivedSeed = derivePath("m/44'/501'/0'/0'", seed.toString("hex")); const keypair = Keypair.fromSeed(derivedSeed.key); // NEAR: Ed25519 from BIP-44 path m/44'/397'/0' const seed = mnemonicToSeedSync(mnemonic); -const derivedSeed = derivePath("m/44'/397'/0'", seed.toString('hex')); -const keyPair = KeyPair.fromString(derivedSeed.key.toString('hex')); +const derivedSeed = derivePath("m/44'/397'/0'", seed.toString("hex")); +const keyPair = KeyPair.fromString(derivedSeed.key.toString("hex")); // EVM (Base/Monad): ECDSA from BIP-44 path m/44'/60'/0'/0/0 const account = mnemonicToAccount(mnemonic); @@ -153,9 +153,9 @@ const account = mnemonicToAccount(mnemonic); // Lazy initialization on first access export async function initChainRegistry(): Promise { const registry = getChainRegistry(); - + if (registry.getAll().length > 0) return registry; - + // Dynamic imports for code splitting const [ { PolkadotChainAdapter }, @@ -168,13 +168,13 @@ export async function initChainRegistry(): Promise { import("./solana/adapter"), import("./near/adapter"), ]); - + registry.register(new PolkadotChainAdapter()); registry.register(createBaseAdapter()); registry.register(createMonadAdapter()); registry.register(new SolanaChainAdapter()); registry.register(new NearChainAdapter()); - + return registry; } ``` @@ -259,7 +259,7 @@ interface ITreasuryService { amountDot: string; txHash: string; }): Promise<{ verified: boolean; error?: string }>; - + recordBet(params: { challengeId: string; walletAddress: string; @@ -267,7 +267,7 @@ interface ITreasuryService { amountDot: string; txHash: string; }): Promise<{ verified: boolean; error?: string }>; - + // Verification verifyOnChainTransfer(params: { txHash: string; @@ -275,11 +275,11 @@ interface ITreasuryService { expectedDestination: string; expectedAmount: string; }): Promise; - + // Payouts calculatePayouts(challengeId: string): Promise; executePayouts(challengeId: string, plan: PayoutPlan): Promise; - + // Queries getEntryPool(challengeId: string): Promise; getBetPool(challengeId: string): Promise; @@ -288,11 +288,13 @@ interface ITreasuryService { ``` **Current Implementation**: `SupabaseTreasuryService` + - Stores financial data in Supabase - Verifies on-chain transfers via RPC calls - Calculates and records payouts in database **Future Migration**: Smart contract implementation + - Replace `SupabaseTreasuryService` with `SmartContractTreasuryService` - All financial operations execute on-chain - No changes required to API routes or frontend @@ -306,6 +308,7 @@ interface ITreasuryService { **Language**: Rust (Anchor framework) **Key Instructions**: + - `create`: Initialize a new challenge escrow - `enroll`: Enroll an agent and deposit entry fee - `bet`: Place a bet on an agent @@ -315,6 +318,7 @@ interface ITreasuryService { - `claim`: Claim payouts or refunds **Account Structure**: + ```rust pub struct Challenge { pub creator: Pubkey, @@ -344,6 +348,7 @@ pub struct Challenge { **Language**: Rust (NEAR SDK) **Key Methods**: + - `new`: Initialize contract - `create`: Create a new challenge - `enroll`: Enroll an agent @@ -356,6 +361,7 @@ pub struct Challenge { - `get_agent_count`: Query agent count **Storage Structure**: + ```rust pub struct ChampionshipEscrow { pub platform: AccountId, @@ -681,7 +687,7 @@ All protected routes use JWT verification: async function verifyToken(request: NextRequest): Promise { const authHeader = request.headers.get("authorization"); if (!authHeader || !authHeader.startsWith("Bearer ")) return null; - + const token = authHeader.substring(7); try { const secret = new TextEncoder().encode(JWT_SECRET); @@ -715,6 +721,7 @@ return NextResponse.json( ### Contract Deployment Status #### Solana Contract + - **Network**: Solana Testnet - **Program ID**: `AvNFV1Bg6ZfngTuGdd5uDDxV22nsmumYd3JUpkQu9MPT` - **Program Data**: `F7SY2yJrNrJwUj7BbNiArECK8EQpgXtrABUR41D33GEr` @@ -724,6 +731,7 @@ return NextResponse.json( - **Program Size**: 357,592 bytes (~349 KB) #### NEAR Contract + - **Network**: NEAR Testnet - **Account ID**: `championship.montaq.testnet` - **Balance**: 4.990806 Ⓝ @@ -784,56 +792,56 @@ return NextResponse.json( ### Frontend -| Technology | Version | Purpose | -|------------|---------|---------| -| Next.js | 16.0.7 | React framework, App Router, API routes | -| React | 19.2.0 | UI library | -| TypeScript | 5.x | Type safety | -| TailwindCSS | 4.x | Styling | -| Radix UI | Latest | Accessible component primitives | -| Lucide React | Latest | Icons | +| Technology | Version | Purpose | +| ------------ | ------- | --------------------------------------- | +| Next.js | 16.0.7 | React framework, App Router, API routes | +| React | 19.2.0 | UI library | +| TypeScript | 5.x | Type safety | +| TailwindCSS | 4.x | Styling | +| Radix UI | Latest | Accessible component primitives | +| Lucide React | Latest | Icons | ### Backend -| Technology | Version | Purpose | -|------------|---------|---------| -| Next.js API Routes | 16.0.7 | Serverless API endpoints | -| Supabase | 2.47.10 | PostgreSQL database, RLS, auth | -| jose | 5.9.6 | JWT verification | +| Technology | Version | Purpose | +| ------------------ | ------- | ------------------------------ | +| Next.js API Routes | 16.0.7 | Serverless API endpoints | +| Supabase | 2.47.10 | PostgreSQL database, RLS, auth | +| jose | 5.9.6 | JWT verification | ### Blockchain Integration -| Technology | Version | Purpose | -|------------|---------|---------| -| polkadot-api | 1.22.0 | Polkadot/Substrate interaction | -| @polkadot/keyring | 13.5.9 | Key management, sr25519 | -| @polkadot/util-crypto | 13.5.9 | Cryptographic utilities | -| @solana/web3.js | 1.98.4 | Solana RPC client | -| @solana/spl-token | 0.4.14 | SPL token operations | -| near-api-js | 7.1.1 | NEAR RPC client | -| viem | 2.46.1 | EVM chain interaction | +| Technology | Version | Purpose | +| --------------------- | ------- | ------------------------------ | +| polkadot-api | 1.22.0 | Polkadot/Substrate interaction | +| @polkadot/keyring | 13.5.9 | Key management, sr25519 | +| @polkadot/util-crypto | 13.5.9 | Cryptographic utilities | +| @solana/web3.js | 1.98.4 | Solana RPC client | +| @solana/spl-token | 0.4.14 | SPL token operations | +| near-api-js | 7.1.1 | NEAR RPC client | +| viem | 2.46.1 | EVM chain interaction | ### Cryptography -| Technology | Version | Purpose | -|------------|---------|---------| -| @scure/bip39 | 2.0.1 | BIP-39 mnemonic generation/validation | -| ed25519-hd-key | 1.3.0 | HD key derivation for Solana/NEAR | +| Technology | Version | Purpose | +| -------------- | ------- | ------------------------------------- | +| @scure/bip39 | 2.0.1 | BIP-39 mnemonic generation/validation | +| ed25519-hd-key | 1.3.0 | HD key derivation for Solana/NEAR | ### Testing -| Technology | Version | Purpose | -|------------|---------|---------| -| Vitest | 2.1.8 | Test runner | -| @testing-library/react | 16.1.0 | React component testing | -| @testing-library/jest-dom | 6.6.3 | DOM matchers | +| Technology | Version | Purpose | +| ------------------------- | ------- | ----------------------- | +| Vitest | 2.1.8 | Test runner | +| @testing-library/react | 16.1.0 | React component testing | +| @testing-library/jest-dom | 6.6.3 | DOM matchers | ### Development Tools -| Technology | Version | Purpose | -|------------|---------|---------| -| ESLint | 9.x | Code linting | -| TypeScript | 5.x | Type checking | +| Technology | Version | Purpose | +| ------------- | ----------- | ---------------------------------- | +| ESLint | 9.x | Code linting | +| TypeScript | 5.x | Type checking | | papi generate | Postinstall | Polkadot API descriptor generation | --- @@ -938,6 +946,7 @@ Relay is a sophisticated multi-chain Web3 application that provides: - **Smart contract abstraction** for future migration The architecture is designed for: + - **Scalability**: Chain adapter pattern allows easy addition of new chains - **Security**: RLS, JWT, cryptographic signatures - **Flexibility**: Treasury service abstraction enables smart contract migration diff --git a/app/api/activity/comment/route.ts b/app/api/activity/comment/route.ts index 18696d7..9fa73be 100644 --- a/app/api/activity/comment/route.ts +++ b/app/api/activity/comment/route.ts @@ -9,15 +9,9 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; +import { supabaseAdmin } from "@/app/utils/supabase-admin"; import { jwtVerify } from "jose"; -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); - const JWT_SECRET = process.env.SUPABASE_JWT_SECRET!; async function verifyToken(request: NextRequest): Promise { @@ -52,17 +46,11 @@ export async function POST(request: NextRequest) { const { activityId, content } = body; if (!activityId) { - return NextResponse.json( - { error: "Activity ID is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "Activity ID is required" }, { status: 400 }); } if (!content || !content.trim()) { - return NextResponse.json( - { error: "Comment content is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "Comment content is required" }, { status: 400 }); } // Check if activity exists @@ -73,42 +61,30 @@ export async function POST(request: NextRequest) { .single(); if (activityError || !activity) { - return NextResponse.json( - { error: "Activity not found" }, - { status: 404 } - ); + return NextResponse.json({ error: "Activity not found" }, { status: 404 }); } // Generate comment ID const commentId = `cmt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Create the comment - const { error: insertError } = await supabaseAdmin - .from("comments") - .insert({ - comment_id: commentId, - activity_id: activityId, - publisher_wallet: walletAddress, - content: content.trim(), - timestamp: new Date().toISOString(), - likes: 0, - }); + const { error: insertError } = await supabaseAdmin.from("comments").insert({ + comment_id: commentId, + activity_id: activityId, + publisher_wallet: walletAddress, + content: content.trim(), + timestamp: new Date().toISOString(), + likes: 0, + }); if (insertError) { console.error("Failed to create comment:", insertError); - return NextResponse.json( - { error: "Failed to create comment" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to create comment" }, { status: 500 }); } return NextResponse.json({ commentId }); } catch (error) { console.error("Create comment error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } - diff --git a/app/api/activity/comments/route.ts b/app/api/activity/comments/route.ts index 1cfaf30..77b17f9 100644 --- a/app/api/activity/comments/route.ts +++ b/app/api/activity/comments/route.ts @@ -8,13 +8,7 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; - -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); +import { supabaseAdmin } from "@/app/utils/supabase-admin"; interface DbComment { comment_id: string; @@ -31,10 +25,7 @@ export async function GET(request: NextRequest) { const activityId = searchParams.get("activityId"); if (!activityId) { - return NextResponse.json( - { error: "Activity ID is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "Activity ID is required" }, { status: 400 }); } const { data, error } = await supabaseAdmin @@ -45,10 +36,7 @@ export async function GET(request: NextRequest) { if (error) { console.error("Failed to fetch comments:", error); - return NextResponse.json( - { error: "Failed to fetch comments" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to fetch comments" }, { status: 500 }); } const comments = (data || []).map((c: DbComment) => ({ @@ -62,10 +50,6 @@ export async function GET(request: NextRequest) { return NextResponse.json({ comments }); } catch (error) { console.error("Get comments error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } - diff --git a/app/api/activity/join/route.ts b/app/api/activity/join/route.ts index ede2fa3..63e879b 100644 --- a/app/api/activity/join/route.ts +++ b/app/api/activity/join/route.ts @@ -9,15 +9,9 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; +import { supabaseAdmin } from "@/app/utils/supabase-admin"; import { jwtVerify } from "jose"; -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); - const JWT_SECRET = process.env.SUPABASE_JWT_SECRET!; async function verifyToken(request: NextRequest): Promise { @@ -52,10 +46,7 @@ export async function POST(request: NextRequest) { const { activityId } = body; if (!activityId) { - return NextResponse.json( - { error: "Activity ID is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "Activity ID is required" }, { status: 400 }); } // Check if activity exists @@ -66,10 +57,7 @@ export async function POST(request: NextRequest) { .single(); if (activityError || !activity) { - return NextResponse.json( - { error: "Activity not found" }, - { status: 404 } - ); + return NextResponse.json({ error: "Activity not found" }, { status: 404 }); } // Check if already attending @@ -81,10 +69,7 @@ export async function POST(request: NextRequest) { .single(); if (existingAttendee) { - return NextResponse.json( - { error: "Already attending this activity" }, - { status: 400 } - ); + return NextResponse.json({ error: "Already attending this activity" }, { status: 400 }); } // Check if activity is full @@ -94,35 +79,23 @@ export async function POST(request: NextRequest) { .eq("activity_id", activityId); if (count !== null && count >= activity.max_attendees) { - return NextResponse.json( - { error: "Activity is full" }, - { status: 400 } - ); + return NextResponse.json({ error: "Activity is full" }, { status: 400 }); } // Join the activity - const { error: joinError } = await supabaseAdmin - .from("activity_attendees") - .insert({ - activity_id: activityId, - user_wallet: walletAddress, - }); + const { error: joinError } = await supabaseAdmin.from("activity_attendees").insert({ + activity_id: activityId, + user_wallet: walletAddress, + }); if (joinError) { console.error("Failed to join activity:", joinError); - return NextResponse.json( - { error: "Failed to join activity" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to join activity" }, { status: 500 }); } return NextResponse.json({ success: true }); } catch (error) { console.error("Join activity error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } - diff --git a/app/api/activity/leave/route.ts b/app/api/activity/leave/route.ts index c499a13..1d95482 100644 --- a/app/api/activity/leave/route.ts +++ b/app/api/activity/leave/route.ts @@ -9,14 +9,8 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; import { jwtVerify } from "jose"; - -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); +import { supabaseAdmin } from "@/app/utils/supabase-admin"; const JWT_SECRET = process.env.SUPABASE_JWT_SECRET!; @@ -52,10 +46,7 @@ export async function POST(request: NextRequest) { const { activityId } = body; if (!activityId) { - return NextResponse.json( - { error: "Activity ID is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "Activity ID is required" }, { status: 400 }); } // Leave the activity @@ -67,19 +58,12 @@ export async function POST(request: NextRequest) { if (leaveError) { console.error("Failed to leave activity:", leaveError); - return NextResponse.json( - { error: "Failed to leave activity" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to leave activity" }, { status: 500 }); } return NextResponse.json({ success: true }); } catch (error) { console.error("Leave activity error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } - diff --git a/app/api/activity/like/route.ts b/app/api/activity/like/route.ts index 63a40d7..63f3dbb 100644 --- a/app/api/activity/like/route.ts +++ b/app/api/activity/like/route.ts @@ -9,13 +9,7 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; - -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); +import { supabaseAdmin } from "@/app/utils/supabase-admin"; export async function POST(request: NextRequest) { try { @@ -24,10 +18,7 @@ export async function POST(request: NextRequest) { const { activityId } = body; if (!activityId) { - return NextResponse.json( - { error: "Activity ID is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "Activity ID is required" }, { status: 400 }); } // Increment likes using RPC function @@ -37,19 +28,12 @@ export async function POST(request: NextRequest) { if (error) { console.error("Failed to like activity:", error); - return NextResponse.json( - { error: "Failed to like activity" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to like activity" }, { status: 500 }); } return NextResponse.json({ success: true }); } catch (error) { console.error("Like activity error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } - diff --git a/app/api/agents/[agentId]/route.ts b/app/api/agents/[agentId]/route.ts index b4dc49c..5c60013 100644 --- a/app/api/agents/[agentId]/route.ts +++ b/app/api/agents/[agentId]/route.ts @@ -5,12 +5,7 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; - -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); +import { supabaseAdmin } from "@/app/utils/supabase-admin"; export async function GET( _request: NextRequest, @@ -46,9 +41,6 @@ export async function GET( }); } catch (error) { console.error("Get agent error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/agents/claim/route.ts b/app/api/agents/claim/route.ts index 35ca9eb..96a7e95 100644 --- a/app/api/agents/claim/route.ts +++ b/app/api/agents/claim/route.ts @@ -44,17 +44,11 @@ export async function POST(request: NextRequest) { .single(); if (error || !agent) { - return NextResponse.json( - { error: "Invalid claim token" }, - { status: 404 } - ); + return NextResponse.json({ error: "Invalid claim token" }, { status: 404 }); } if (agent.owner_wallet) { - return NextResponse.json( - { error: "Agent has already been claimed" }, - { status: 409 } - ); + return NextResponse.json({ error: "Agent has already been claimed" }, { status: 409 }); } // Set ownership and invalidate token @@ -68,10 +62,7 @@ export async function POST(request: NextRequest) { if (updateError) { console.error("Failed to claim agent:", updateError); - return NextResponse.json( - { error: "Failed to claim agent" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to claim agent" }, { status: 500 }); } return NextResponse.json({ @@ -81,9 +72,6 @@ export async function POST(request: NextRequest) { }); } catch (error) { console.error("Claim agent error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/agents/me/regenerate-claim-token/route.ts b/app/api/agents/me/regenerate-claim-token/route.ts index 373bcdd..78d87b0 100644 --- a/app/api/agents/me/regenerate-claim-token/route.ts +++ b/app/api/agents/me/regenerate-claim-token/route.ts @@ -15,10 +15,7 @@ export async function POST(request: NextRequest) { try { const auth = await authenticateRequest(request); if (!auth || auth.accountType !== "agent" || !auth.agentId) { - return NextResponse.json( - { error: "Unauthorized. Agent API key required." }, - { status: 401 } - ); + return NextResponse.json({ error: "Unauthorized. Agent API key required." }, { status: 401 }); } const admin = getAdminClient(); @@ -50,18 +47,12 @@ export async function POST(request: NextRequest) { .eq("id", auth.agentId); if (updateError) { - return NextResponse.json( - { error: "Failed to regenerate claim token" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to regenerate claim token" }, { status: 500 }); } return NextResponse.json({ claim_token: claimToken }); } catch (error) { console.error("Regenerate claim token error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/agents/me/route.ts b/app/api/agents/me/route.ts index d91934d..5373b5c 100644 --- a/app/api/agents/me/route.ts +++ b/app/api/agents/me/route.ts @@ -11,25 +11,21 @@ export async function GET(request: NextRequest) { try { const auth = await authenticateRequest(request); if (!auth || auth.accountType !== "agent" || !auth.agentId) { - return NextResponse.json( - { error: "Unauthorized. Agent API key required." }, - { status: 401 } - ); + return NextResponse.json({ error: "Unauthorized. Agent API key required." }, { status: 401 }); } const admin = getAdminClient(); const { data: agent, error } = await admin .from("agents") - .select("id, wallet_address, owner_wallet, agent_name, description, repo_url, endpoint_url, capabilities, is_active, created_at") + .select( + "id, wallet_address, owner_wallet, agent_name, description, repo_url, endpoint_url, capabilities, is_active, created_at" + ) .eq("id", auth.agentId) .single(); if (error || !agent) { - return NextResponse.json( - { error: "Agent not found" }, - { status: 404 } - ); + return NextResponse.json({ error: "Agent not found" }, { status: 404 }); } // Fetch chain accounts @@ -47,9 +43,6 @@ export async function GET(request: NextRequest) { }); } catch (error) { console.error("Agent me error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/agents/register/route.ts b/app/api/agents/register/route.ts index d17d323..f7cea4f 100644 --- a/app/api/agents/register/route.ts +++ b/app/api/agents/register/route.ts @@ -19,7 +19,7 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; +import { supabaseAdmin } from "@/app/utils/supabase-admin"; import { mnemonicGenerate, cryptoWaitReady } from "@polkadot/util-crypto"; import { Keyring } from "@polkadot/keyring"; import { generateToken, sha256 } from "../../../utils/championship-crypto"; @@ -27,21 +27,13 @@ import { initChainRegistry } from "../../../chains/registry"; const SS58_FORMAT = 42; // Generic Substrate -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); - export async function POST(request: NextRequest) { try { const body = await request.json(); // Validate required fields if (!body.agent_name || typeof body.agent_name !== "string" || body.agent_name.trim() === "") { - return NextResponse.json( - { error: "agent_name is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "agent_name is required" }, { status: 400 }); } const agentName = body.agent_name.trim(); @@ -54,10 +46,7 @@ export async function POST(request: NextRequest) { .single(); if (existing) { - return NextResponse.json( - { error: "agent_name already taken" }, - { status: 409 } - ); + return NextResponse.json({ error: "agent_name already taken" }, { status: 409 }); } // 1. Generate mnemonic @@ -98,10 +87,7 @@ export async function POST(request: NextRequest) { if (userError) { console.error("Failed to create user for agent:", userError); - return NextResponse.json( - { error: "Failed to create agent account" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to create agent account" }, { status: 500 }); } // 6. Create chain_accounts rows @@ -131,10 +117,7 @@ export async function POST(request: NextRequest) { if (agentError || !agent) { console.error("Failed to create agent:", agentError); - return NextResponse.json( - { error: "Failed to create agent profile" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to create agent profile" }, { status: 500 }); } return NextResponse.json({ @@ -147,9 +130,6 @@ export async function POST(request: NextRequest) { }); } catch (error) { console.error("Agent registration error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/agents/route.ts b/app/api/agents/route.ts index f18c530..0d1f00e 100644 --- a/app/api/agents/route.ts +++ b/app/api/agents/route.ts @@ -7,12 +7,7 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; - -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); +import { supabaseAdmin } from "@/app/utils/supabase-admin"; export async function GET(request: NextRequest) { try { @@ -35,10 +30,7 @@ export async function GET(request: NextRequest) { if (error) { console.error("Failed to fetch agents:", error); - return NextResponse.json( - { error: "Failed to fetch agents" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to fetch agents" }, { status: 500 }); } const agents = (data || []).map((a) => ({ @@ -57,9 +49,6 @@ export async function GET(request: NextRequest) { return NextResponse.json({ agents }); } catch (error) { console.error("List agents error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/auth/nonce/route.ts b/app/api/auth/nonce/route.ts index 177907f..e3ef35f 100644 --- a/app/api/auth/nonce/route.ts +++ b/app/api/auth/nonce/route.ts @@ -1,23 +1,17 @@ /** * API Route: Generate Authentication Nonce - * + * * This endpoint generates a one-time nonce for wallet authentication. * The nonce is stored in Supabase and must be used within 5 minutes. - * + * * POST /api/auth/nonce * Body: { walletAddress: string } * Response: { message: string, nonce: string } */ -import { NextRequest, NextResponse } from 'next/server'; -import { createClient } from '@supabase/supabase-js'; -import { generateNonce, createAuthMessage, isValidWalletAddress } from '@/app/utils/auth'; - -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); +import { NextRequest, NextResponse } from "next/server"; +import { supabaseAdmin } from "@/app/utils/supabase-admin"; +import { generateNonce, createAuthMessage, isValidWalletAddress } from "@/app/utils/auth"; export async function POST(request: NextRequest) { try { @@ -26,17 +20,11 @@ export async function POST(request: NextRequest) { // Validate wallet address if (!walletAddress) { - return NextResponse.json( - { error: 'Wallet address is required' }, - { status: 400 } - ); + return NextResponse.json({ error: "Wallet address is required" }, { status: 400 }); } if (!isValidWalletAddress(walletAddress)) { - return NextResponse.json( - { error: 'Invalid wallet address format' }, - { status: 400 } - ); + return NextResponse.json({ error: "Invalid wallet address format" }, { status: 400 }); } // Generate a unique nonce @@ -46,29 +34,24 @@ export async function POST(request: NextRequest) { const expiresAt = new Date(Date.now() + 5 * 60 * 1000).toISOString(); // Delete any existing nonces for this wallet (cleanup) - await supabaseAdmin - .from('auth_nonces') - .delete() - .eq('wallet_address', walletAddress); + await supabaseAdmin.from("auth_nonces").delete().eq("wallet_address", walletAddress); // Create the message that the user will sign // IMPORTANT: Create this BEFORE storing, so we can store the exact message const message = createAuthMessage(nonce, walletAddress); // Store the nonce AND the message in the database - const { error } = await supabaseAdmin - .from('auth_nonces') - .insert({ - wallet_address: walletAddress, - nonce: nonce, - message: message, // Store the exact message for verification - expires_at: expiresAt, - }); + const { error } = await supabaseAdmin.from("auth_nonces").insert({ + wallet_address: walletAddress, + nonce: nonce, + message: message, // Store the exact message for verification + expires_at: expiresAt, + }); if (error) { - console.error('Failed to store nonce:', error); + console.error("Failed to store nonce:", error); return NextResponse.json( - { error: 'Failed to generate authentication nonce' }, + { error: "Failed to generate authentication nonce" }, { status: 500 } ); } @@ -78,10 +61,7 @@ export async function POST(request: NextRequest) { nonce, }); } catch (error) { - console.error('Nonce generation error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + console.error("Nonce generation error:", error); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/auth/verify/route.ts b/app/api/auth/verify/route.ts index fb36816..3a5c75e 100644 --- a/app/api/auth/verify/route.ts +++ b/app/api/auth/verify/route.ts @@ -1,25 +1,19 @@ /** * API Route: Verify Wallet Signature and Issue JWT - * + * * This endpoint verifies the wallet signature and issues a JWT token * that can be used with Supabase for authenticated operations. - * + * * POST /api/auth/verify * Body: { walletAddress: string, signature: string, nonce: string } * Response: { token: string } */ -import { NextRequest, NextResponse } from 'next/server'; -import { createClient } from '@supabase/supabase-js'; -import { SignJWT } from 'jose'; -import { cryptoWaitReady, signatureVerify } from '@polkadot/util-crypto'; -import { createAuthMessage, isValidWalletAddress } from '@/app/utils/auth'; - -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); +import { NextRequest, NextResponse } from "next/server"; +import { supabaseAdmin } from "@/app/utils/supabase-admin"; +import { SignJWT } from "jose"; +import { cryptoWaitReady, signatureVerify } from "@polkadot/util-crypto"; +import { createAuthMessage, isValidWalletAddress } from "@/app/utils/auth"; // JWT secret from Supabase (found in Dashboard > Settings > API > JWT Secret) const JWT_SECRET = process.env.SUPABASE_JWT_SECRET!; @@ -30,122 +24,107 @@ export async function POST(request: NextRequest) { const { walletAddress, signature, nonce } = body; // DEBUG: Log received inputs - console.log('=== AUTH VERIFY DEBUG ==='); - console.log('Received walletAddress:', walletAddress); - console.log('Received nonce:', nonce); - console.log('Received signature:', signature); - console.log('Signature length:', signature?.length); + console.log("=== AUTH VERIFY DEBUG ==="); + console.log("Received walletAddress:", walletAddress); + console.log("Received nonce:", nonce); + console.log("Received signature:", signature); + console.log("Signature length:", signature?.length); // Validate inputs if (!walletAddress || !signature || !nonce) { - console.log('DEBUG: Missing required fields'); + console.log("DEBUG: Missing required fields"); return NextResponse.json( - { error: 'Missing required fields: walletAddress, signature, nonce' }, + { error: "Missing required fields: walletAddress, signature, nonce" }, { status: 400 } ); } if (!isValidWalletAddress(walletAddress)) { - console.log('DEBUG: Invalid wallet address format'); - return NextResponse.json( - { error: 'Invalid wallet address format' }, - { status: 400 } - ); + console.log("DEBUG: Invalid wallet address format"); + return NextResponse.json({ error: "Invalid wallet address format" }, { status: 400 }); } // Ensure crypto is ready await cryptoWaitReady(); - console.log('DEBUG: Crypto ready'); + console.log("DEBUG: Crypto ready"); // Step 1: Verify the nonce exists and hasn't expired const { data: nonceRecord, error: nonceError } = await supabaseAdmin - .from('auth_nonces') - .select('*') - .eq('wallet_address', walletAddress) - .eq('nonce', nonce) - .gt('expires_at', new Date().toISOString()) + .from("auth_nonces") + .select("*") + .eq("wallet_address", walletAddress) + .eq("nonce", nonce) + .gt("expires_at", new Date().toISOString()) .single(); - console.log('DEBUG: Nonce query result:', { nonceRecord, nonceError }); + console.log("DEBUG: Nonce query result:", { nonceRecord, nonceError }); if (nonceError || !nonceRecord) { - console.log('DEBUG: Nonce validation failed'); + console.log("DEBUG: Nonce validation failed"); return NextResponse.json( - { error: 'Invalid or expired nonce. Please request a new one.' }, + { error: "Invalid or expired nonce. Please request a new one." }, { status: 401 } ); } // Step 2: Use the stored message (not reconstructed, to preserve original timestamp) const message = nonceRecord.message; - + if (!message) { - console.log('DEBUG: No message stored in nonce record! Falling back to reconstruction.'); + console.log("DEBUG: No message stored in nonce record! Falling back to reconstruction."); // Fallback for old nonce records without message field const reconstructedMessage = createAuthMessage(nonce, walletAddress); - console.log('DEBUG: Reconstructed message:', reconstructedMessage); + console.log("DEBUG: Reconstructed message:", reconstructedMessage); return NextResponse.json( - { error: 'Nonce record missing message. Please request a new nonce.' }, + { error: "Nonce record missing message. Please request a new nonce." }, { status: 401 } ); } - - console.log('DEBUG: Using stored message from nonce record'); - console.log('DEBUG: Message:', message); + + console.log("DEBUG: Using stored message from nonce record"); + console.log("DEBUG: Message:", message); // Step 3: Verify the signature let isValid = false; try { - console.log('DEBUG: Attempting signature verification...'); - console.log('DEBUG: Signature bytes (first 20):', Buffer.from(signature, 'hex').slice(0, 20)); - - const result = signatureVerify( - message, - Buffer.from(signature, 'hex'), - walletAddress - ); + console.log("DEBUG: Attempting signature verification..."); + console.log("DEBUG: Signature bytes (first 20):", Buffer.from(signature, "hex").slice(0, 20)); + + const result = signatureVerify(message, Buffer.from(signature, "hex"), walletAddress); isValid = result.isValid; - console.log('DEBUG: signatureVerify result:', result); - console.log('DEBUG: isValid:', isValid); + console.log("DEBUG: signatureVerify result:", result); + console.log("DEBUG: isValid:", isValid); } catch (error) { - console.error('DEBUG: Signature verification threw error:', error); - return NextResponse.json( - { error: 'Invalid signature format' }, - { status: 401 } - ); + console.error("DEBUG: Signature verification threw error:", error); + return NextResponse.json({ error: "Invalid signature format" }, { status: 401 }); } if (!isValid) { - console.log('DEBUG: Signature is NOT valid - authentication failed'); + console.log("DEBUG: Signature is NOT valid - authentication failed"); return NextResponse.json( - { error: 'Invalid signature. Authentication failed.' }, + { error: "Invalid signature. Authentication failed." }, { status: 401 } ); } - - console.log('DEBUG: Signature verified successfully!'); + + console.log("DEBUG: Signature verified successfully!"); // Step 4: Delete the used nonce (one-time use) - await supabaseAdmin - .from('auth_nonces') - .delete() - .eq('id', nonceRecord.id); + await supabaseAdmin.from("auth_nonces").delete().eq("id", nonceRecord.id); // Step 5: Upsert user in database - const { error: userError } = await supabaseAdmin - .from('users') - .upsert( - { - wallet_address: walletAddress, - last_login: new Date().toISOString(), - }, - { - onConflict: 'wallet_address', - } - ); + const { error: userError } = await supabaseAdmin.from("users").upsert( + { + wallet_address: walletAddress, + last_login: new Date().toISOString(), + }, + { + onConflict: "wallet_address", + } + ); if (userError) { - console.error('Failed to upsert user:', userError); + console.error("Failed to upsert user:", userError); // Continue anyway - user creation is not critical for auth } @@ -157,10 +136,10 @@ export async function POST(request: NextRequest) { const secret = new TextEncoder().encode(JWT_SECRET); const token = await new SignJWT({ wallet_address: walletAddress, - role: 'authenticated', - aud: 'authenticated', + role: "authenticated", + aud: "authenticated", }) - .setProtectedHeader({ alg: 'HS256', typ: 'JWT' }) + .setProtectedHeader({ alg: "HS256", typ: "JWT" }) .setSubject(walletAddress) .setIssuedAt(now) .setExpirationTime(now + expiresIn) @@ -168,10 +147,7 @@ export async function POST(request: NextRequest) { return NextResponse.json({ token }); } catch (error) { - console.error('Verification error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + console.error("Verification error:", error); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/championship/[challengeId]/agents/route.ts b/app/api/championship/[challengeId]/agents/route.ts index 0193b65..fcc78af 100644 --- a/app/api/championship/[challengeId]/agents/route.ts +++ b/app/api/championship/[challengeId]/agents/route.ts @@ -5,12 +5,7 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; - -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); +import { supabaseAdmin } from "@/app/utils/supabase-admin"; export async function GET( _request: NextRequest, @@ -27,10 +22,7 @@ export async function GET( if (error) { console.error("Failed to fetch agents:", error); - return NextResponse.json( - { error: "Failed to fetch agents" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to fetch agents" }, { status: 500 }); } const agents = (data || []).map((a) => ({ @@ -59,9 +51,6 @@ export async function GET( return NextResponse.json({ agents }); } catch (error) { console.error("Challenge agents error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/championship/[challengeId]/bets/route.ts b/app/api/championship/[challengeId]/bets/route.ts index 7b851c3..214ea5f 100644 --- a/app/api/championship/[challengeId]/bets/route.ts +++ b/app/api/championship/[challengeId]/bets/route.ts @@ -9,12 +9,7 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; - -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); +import { supabaseAdmin } from "@/app/utils/supabase-admin"; export async function GET( request: NextRequest, @@ -47,10 +42,7 @@ export async function GET( if (error) { console.error("Failed to fetch bets:", error); - return NextResponse.json( - { error: "Failed to fetch bets" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to fetch bets" }, { status: 500 }); } // Calculate bets by agent @@ -78,9 +70,6 @@ export async function GET( }); } catch (error) { console.error("Bet stats error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/championship/[challengeId]/bets/summary/route.ts b/app/api/championship/[challengeId]/bets/summary/route.ts index d08338e..9b861d4 100644 --- a/app/api/championship/[challengeId]/bets/summary/route.ts +++ b/app/api/championship/[challengeId]/bets/summary/route.ts @@ -8,12 +8,7 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; - -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); +import { supabaseAdmin } from "@/app/utils/supabase-admin"; export async function GET( _request: NextRequest, @@ -56,13 +51,15 @@ export async function GET( agentPools[bet.agent_id].count += 1; } - const agentSummaries = (agents || []).map((agent: { id: string; agent_name: string; status?: string }) => ({ - agent_id: agent.id, - agent_name: agent.agent_name, - status: agent.status || "enrolled", - pool_size: (agentPools[agent.id]?.pool || BigInt(0)).toString(), - bet_count: agentPools[agent.id]?.count || 0, - })); + const agentSummaries = (agents || []).map( + (agent: { id: string; agent_name: string; status?: string }) => ({ + agent_id: agent.id, + agent_name: agent.agent_name, + status: agent.status || "enrolled", + pool_size: (agentPools[agent.id]?.pool || BigInt(0)).toString(), + bet_count: agentPools[agent.id]?.count || 0, + }) + ); return NextResponse.json({ total_pool: challenge.total_bet_pool_dot, @@ -70,9 +67,6 @@ export async function GET( }); } catch (error) { console.error("Bets summary error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/championship/[challengeId]/claim/route.ts b/app/api/championship/[challengeId]/claim/route.ts index 17c46fc..2c8f5e8 100644 --- a/app/api/championship/[challengeId]/claim/route.ts +++ b/app/api/championship/[challengeId]/claim/route.ts @@ -35,10 +35,7 @@ export async function POST( } if (challenge.status !== "completed") { - return NextResponse.json( - { error: "Challenge is not yet finalized" }, - { status: 403 } - ); + return NextResponse.json({ error: "Challenge is not yet finalized" }, { status: 403 }); } // Check if already claimed @@ -51,10 +48,7 @@ export async function POST( .limit(1); if (existingPayout && existingPayout.length > 0) { - return NextResponse.json( - { error: "Already claimed" }, - { status: 409 } - ); + return NextResponse.json({ error: "Already claimed" }, { status: 409 }); } // TODO: Execute on-chain claim via escrow adapter @@ -131,8 +125,7 @@ export async function POST( if (totalWinnerBets > BigInt(0)) { const betPool = BigInt(challenge.total_bet_pool_dot); const winnerBetPool = (betPool * BigInt(95)) / BigInt(100); - const userShare = - (winnerBetPool * userBetTotal) / totalWinnerBets; + const userShare = (winnerBetPool * userBetTotal) / totalWinnerBets; if (userShare > BigInt(0)) { payout += userShare; payoutEntries.push({ @@ -146,10 +139,7 @@ export async function POST( } if (payout === BigInt(0)) { - return NextResponse.json( - { error: "Nothing to claim" }, - { status: 400 } - ); + return NextResponse.json({ error: "Nothing to claim" }, { status: 400 }); } // Record payouts @@ -170,9 +160,6 @@ export async function POST( }); } catch (error) { console.error("Claim error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/championship/[challengeId]/finalize/route.ts b/app/api/championship/[challengeId]/finalize/route.ts index 49aa2ee..934b5a4 100644 --- a/app/api/championship/[challengeId]/finalize/route.ts +++ b/app/api/championship/[challengeId]/finalize/route.ts @@ -19,10 +19,7 @@ export async function POST( try { const auth = await authenticateRequest(request); if (!auth) { - return NextResponse.json( - { error: "Unauthorized" }, - { status: 401 } - ); + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const { challengeId } = await params; @@ -35,25 +32,16 @@ export async function POST( .single(); if (challengeError || !challenge) { - return NextResponse.json( - { error: "Challenge not found" }, - { status: 404 } - ); + return NextResponse.json({ error: "Challenge not found" }, { status: 404 }); } if (challenge.status === "completed") { - return NextResponse.json( - { error: "Challenge already finalized" }, - { status: 409 } - ); + return NextResponse.json({ error: "Challenge already finalized" }, { status: 409 }); } const judgeEnd = new Date(challenge.judge_end); if (new Date() <= judgeEnd) { - return NextResponse.json( - { error: "Judging period has not ended yet" }, - { status: 400 } - ); + return NextResponse.json({ error: "Judging period has not ended yet" }, { status: 400 }); } // Get all agents, determine winner among non-withdrawn @@ -64,16 +52,11 @@ export async function POST( .order("total_votes", { ascending: false }); if (agentsError || !agents || agents.length === 0) { - return NextResponse.json( - { error: "No agents enrolled" }, - { status: 400 } - ); + return NextResponse.json({ error: "No agents enrolled" }, { status: 400 }); } // Filter non-withdrawn agents - const activeAgents = agents.filter( - (a: { status?: string }) => a.status !== "withdrawn" - ); + const activeAgents = agents.filter((a: { status?: string }) => a.status !== "withdrawn"); if (activeAgents.length < 3) { // Too few active agents — cancel instead @@ -103,10 +86,7 @@ export async function POST( if (updateError) { console.error("Failed to finalize:", updateError); - return NextResponse.json( - { error: "Failed to finalize challenge" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to finalize challenge" }, { status: 500 }); } // Calculate and record payouts @@ -127,9 +107,6 @@ export async function POST( }); } catch (error) { console.error("Finalize error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/championship/[challengeId]/my-status/route.ts b/app/api/championship/[challengeId]/my-status/route.ts index fbb563c..87fc640 100644 --- a/app/api/championship/[challengeId]/my-status/route.ts +++ b/app/api/championship/[challengeId]/my-status/route.ts @@ -32,10 +32,7 @@ export async function GET( .single(); if (enrollErr || !enrollment) { - return NextResponse.json( - { enrolled: false }, - { status: 200 } - ); + return NextResponse.json({ enrolled: false }, { status: 200 }); } const now = new Date(); @@ -80,9 +77,6 @@ export async function GET( return NextResponse.json(response); } catch (error) { console.error("My status error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/championship/[challengeId]/results/route.ts b/app/api/championship/[challengeId]/results/route.ts index 01e79ca..383f60a 100644 --- a/app/api/championship/[challengeId]/results/route.ts +++ b/app/api/championship/[challengeId]/results/route.ts @@ -13,12 +13,7 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; - -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); +import { supabaseAdmin } from "@/app/utils/supabase-admin"; export async function GET( request: NextRequest, @@ -35,10 +30,7 @@ export async function GET( .single(); if (challengeError || !challenge) { - return NextResponse.json( - { error: "Challenge not found" }, - { status: 404 } - ); + return NextResponse.json({ error: "Challenge not found" }, { status: 404 }); } // Get agents sorted by votes @@ -49,10 +41,7 @@ export async function GET( .order("total_votes", { ascending: false }); if (agentsError) { - return NextResponse.json( - { error: "Failed to fetch results" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to fetch results" }, { status: 500 }); } const mappedAgents = (agents || []).map((a) => ({ @@ -106,9 +95,6 @@ export async function GET( }); } catch (error) { console.error("Challenge results error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/championship/[challengeId]/reveal/route.ts b/app/api/championship/[challengeId]/reveal/route.ts index 7326262..f6f98fc 100644 --- a/app/api/championship/[challengeId]/reveal/route.ts +++ b/app/api/championship/[challengeId]/reveal/route.ts @@ -81,10 +81,7 @@ export async function POST( try { fullChallenge = decryptChallenge(challenge.full_challenge_encrypted); } catch { - return NextResponse.json( - { error: "Failed to decrypt challenge" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to decrypt challenge" }, { status: 500 }); } // If already revealed, return existing deadlines (idempotent) @@ -105,14 +102,9 @@ export async function POST( const competitionDuration = challenge.competition_duration_seconds || 259200; // 72h default const refundWindow = challenge.refund_window_seconds || 3600; // 1h default - const competeDeadlineRaw = new Date( - revealedAt.getTime() + competitionDuration * 1000 - ); - const competeDeadline = - competeDeadlineRaw < endTime ? competeDeadlineRaw : endTime; - const refundDeadline = new Date( - revealedAt.getTime() + refundWindow * 1000 - ); + const competeDeadlineRaw = new Date(revealedAt.getTime() + competitionDuration * 1000); + const competeDeadline = competeDeadlineRaw < endTime ? competeDeadlineRaw : endTime; + const refundDeadline = new Date(revealedAt.getTime() + refundWindow * 1000); // Update enrollment record const { error: updateErr } = await admin @@ -127,10 +119,7 @@ export async function POST( if (updateErr) { console.error("Failed to update reveal:", updateErr); - return NextResponse.json( - { error: "Failed to record reveal" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to record reveal" }, { status: 500 }); } return NextResponse.json({ @@ -143,9 +132,6 @@ export async function POST( }); } catch (error) { console.error("Reveal error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/championship/[challengeId]/route.ts b/app/api/championship/[challengeId]/route.ts index 396e3e9..91d713e 100644 --- a/app/api/championship/[challengeId]/route.ts +++ b/app/api/championship/[challengeId]/route.ts @@ -9,14 +9,9 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; +import { supabaseAdmin } from "@/app/utils/supabase-admin"; import { decryptChallenge } from "@/app/utils/championship-crypto"; -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); - export async function GET( _request: NextRequest, { params }: { params: Promise<{ challengeId: string }> } @@ -31,10 +26,7 @@ export async function GET( .single(); if (error || !data) { - return NextResponse.json( - { error: "Challenge not found" }, - { status: 404 } - ); + return NextResponse.json({ error: "Challenge not found" }, { status: 404 }); } // Get agent count @@ -84,9 +76,6 @@ export async function GET( return NextResponse.json({ challenge }); } catch (error) { console.error("Challenge detail error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/championship/[challengeId]/submit/route.ts b/app/api/championship/[challengeId]/submit/route.ts index 33f32ff..a870deb 100644 --- a/app/api/championship/[challengeId]/submit/route.ts +++ b/app/api/championship/[challengeId]/submit/route.ts @@ -29,21 +29,19 @@ export async function POST( const admin = getAdminClient(); // Validate solution_url is present - if (!body.solution_url || typeof body.solution_url !== "string" || body.solution_url.trim() === "") { - return NextResponse.json( - { error: "solution_url is required" }, - { status: 400 } - ); + if ( + !body.solution_url || + typeof body.solution_url !== "string" || + body.solution_url.trim() === "" + ) { + return NextResponse.json({ error: "solution_url is required" }, { status: 400 }); } // Basic URL validation try { new URL(body.solution_url); } catch { - return NextResponse.json( - { error: "solution_url must be a valid URL" }, - { status: 400 } - ); + return NextResponse.json({ error: "solution_url must be a valid URL" }, { status: 400 }); } // Get enrollment @@ -62,16 +60,10 @@ export async function POST( } if (enrollment.status === "withdrawn") { - return NextResponse.json( - { error: "Cannot submit — you have withdrawn" }, - { status: 403 } - ); + return NextResponse.json({ error: "Cannot submit — you have withdrawn" }, { status: 403 }); } if (enrollment.status === "submitted") { - return NextResponse.json( - { error: "Already submitted" }, - { status: 409 } - ); + return NextResponse.json({ error: "Already submitted" }, { status: 409 }); } if (!enrollment.revealed_at) { return NextResponse.json( @@ -84,10 +76,7 @@ export async function POST( const now = new Date(); const competeDeadline = new Date(enrollment.compete_deadline); if (now > competeDeadline) { - return NextResponse.json( - { error: "Competition deadline has passed" }, - { status: 403 } - ); + return NextResponse.json({ error: "Competition deadline has passed" }, { status: 403 }); } // Record submission @@ -104,10 +93,7 @@ export async function POST( if (updateErr) { console.error("Failed to record submission:", updateErr); - return NextResponse.json( - { error: "Failed to record submission" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to record submission" }, { status: 500 }); } return NextResponse.json({ @@ -116,9 +102,6 @@ export async function POST( }); } catch (error) { console.error("Submit error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/championship/[challengeId]/withdraw/route.ts b/app/api/championship/[challengeId]/withdraw/route.ts index ff9cbcd..07678f9 100644 --- a/app/api/championship/[challengeId]/withdraw/route.ts +++ b/app/api/championship/[challengeId]/withdraw/route.ts @@ -53,31 +53,19 @@ export async function POST( // Validate conditions if (enrollment.status === "withdrawn") { - return NextResponse.json( - { error: "Already withdrawn" }, - { status: 409 } - ); + return NextResponse.json({ error: "Already withdrawn" }, { status: 409 }); } if (enrollment.status === "submitted") { - return NextResponse.json( - { error: "Cannot withdraw after submitting" }, - { status: 403 } - ); + return NextResponse.json({ error: "Cannot withdraw after submitting" }, { status: 403 }); } if (!enrollment.revealed_at) { - return NextResponse.json( - { error: "Must reveal before withdrawing" }, - { status: 403 } - ); + return NextResponse.json({ error: "Must reveal before withdrawing" }, { status: 403 }); } const now = new Date(); const refundDeadline = new Date(enrollment.refund_deadline); if (now > refundDeadline) { - return NextResponse.json( - { error: "Refund window has expired" }, - { status: 403 } - ); + return NextResponse.json({ error: "Refund window has expired" }, { status: 403 }); } // Calculate refund amounts @@ -97,10 +85,7 @@ export async function POST( if (updateErr) { console.error("Failed to update withdrawal:", updateErr); - return NextResponse.json( - { error: "Failed to record withdrawal" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to record withdrawal" }, { status: 500 }); } // Record payout entries for audit @@ -128,9 +113,6 @@ export async function POST( }); } catch (error) { console.error("Withdraw error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/championship/bet/route.ts b/app/api/championship/bet/route.ts index 84ba37f..9fd948f 100644 --- a/app/api/championship/bet/route.ts +++ b/app/api/championship/bet/route.ts @@ -60,16 +60,10 @@ export async function POST(request: NextRequest) { const endTime = new Date(challenge.end_time); if (now < startTime) { - return NextResponse.json( - { error: "Betting opens after start_time" }, - { status: 400 } - ); + return NextResponse.json({ error: "Betting opens after start_time" }, { status: 400 }); } if (now > endTime) { - return NextResponse.json( - { error: "Betting period has ended" }, - { status: 400 } - ); + return NextResponse.json({ error: "Betting period has ended" }, { status: 400 }); } // Verify the agent exists and is not withdrawn @@ -81,17 +75,11 @@ export async function POST(request: NextRequest) { .single(); if (!agent) { - return NextResponse.json( - { error: "Agent not found in this challenge" }, - { status: 404 } - ); + return NextResponse.json({ error: "Agent not found in this challenge" }, { status: 404 }); } if (agent.status === "withdrawn") { - return NextResponse.json( - { error: "Cannot bet on a withdrawn agent" }, - { status: 400 } - ); + return NextResponse.json({ error: "Cannot bet on a withdrawn agent" }, { status: 400 }); } // Record the bet @@ -114,9 +102,6 @@ export async function POST(request: NextRequest) { return NextResponse.json({ success: true }); } catch (error) { console.error("Place bet error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/championship/create/route.ts b/app/api/championship/create/route.ts index c2bb323..93259da 100644 --- a/app/api/championship/create/route.ts +++ b/app/api/championship/create/route.ts @@ -40,16 +40,10 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: "title is required" }, { status: 400 }); } if (!body.abstract_description || body.abstract_description.trim() === "") { - return NextResponse.json( - { error: "abstract_description is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "abstract_description is required" }, { status: 400 }); } if (!body.full_challenge || body.full_challenge.trim() === "") { - return NextResponse.json( - { error: "full_challenge is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "full_challenge is required" }, { status: 400 }); } if (!body.start_time || !body.end_time || !body.judge_end) { return NextResponse.json( @@ -58,15 +52,9 @@ export async function POST(request: NextRequest) { ); } if (!body.entry_fee || BigInt(body.entry_fee) <= BigInt(0)) { - return NextResponse.json( - { error: "entry_fee must be positive" }, - { status: 400 } - ); + return NextResponse.json({ error: "entry_fee must be positive" }, { status: 400 }); } - if ( - !body.competition_duration_seconds || - body.competition_duration_seconds <= 0 - ) { + if (!body.competition_duration_seconds || body.competition_duration_seconds <= 0) { return NextResponse.json( { error: "competition_duration_seconds must be positive" }, { status: 400 } @@ -86,22 +74,13 @@ export async function POST(request: NextRequest) { const now = new Date(); if (startTime <= now) { - return NextResponse.json( - { error: "start_time must be in the future" }, - { status: 400 } - ); + return NextResponse.json({ error: "start_time must be in the future" }, { status: 400 }); } if (endTime <= startTime) { - return NextResponse.json( - { error: "end_time must be after start_time" }, - { status: 400 } - ); + return NextResponse.json({ error: "end_time must be after start_time" }, { status: 400 }); } if (judgeEnd <= endTime) { - return NextResponse.json( - { error: "judge_end must be after end_time" }, - { status: 400 } - ); + return NextResponse.json({ error: "judge_end must be after end_time" }, { status: 400 }); } const chainId = body.chain_id || "solana"; @@ -136,10 +115,7 @@ export async function POST(request: NextRequest) { if (insertError) { console.error("Failed to create challenge:", insertError); - return NextResponse.json( - { error: "Failed to create challenge" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to create challenge" }, { status: 500 }); } return NextResponse.json({ @@ -150,9 +126,6 @@ export async function POST(request: NextRequest) { }); } catch (error) { console.error("Create challenge error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/championship/enroll/route.ts b/app/api/championship/enroll/route.ts index b763d32..94e9277 100644 --- a/app/api/championship/enroll/route.ts +++ b/app/api/championship/enroll/route.ts @@ -36,10 +36,7 @@ export async function POST(request: NextRequest) { const challengeId = body.challenge_id || body.challengeId; if (!challengeId) { - return NextResponse.json( - { error: "challenge_id is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "challenge_id is required" }, { status: 400 }); } // Check the challenge exists and is in enrollment phase @@ -56,10 +53,7 @@ export async function POST(request: NextRequest) { // Check enrollment is still open (before start_time) const startTime = new Date(challenge.start_time); if (new Date() > startTime) { - return NextResponse.json( - { error: "Enrollment period has ended" }, - { status: 400 } - ); + return NextResponse.json({ error: "Enrollment period has ended" }, { status: 400 }); } if (challenge.status !== "enrolling") { @@ -126,10 +120,7 @@ export async function POST(request: NextRequest) { if (agentError || !agent) { console.error("Failed to enroll agent:", agentError); - return NextResponse.json( - { error: "Failed to enroll agent" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to enroll agent" }, { status: 500 }); } // Record entry payment via TreasuryService @@ -149,9 +140,6 @@ export async function POST(request: NextRequest) { }); } catch (error) { console.error("Enroll agent error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/championship/route.ts b/app/api/championship/route.ts index 5e227eb..f77241d 100644 --- a/app/api/championship/route.ts +++ b/app/api/championship/route.ts @@ -13,15 +13,10 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; +import { supabaseAdmin } from "@/app/utils/supabase-admin"; import type { ChallengeStatus } from "@/app/types/frontend_type"; import { decryptChallenge } from "@/app/utils/championship-crypto"; -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); - export async function GET(request: NextRequest) { try { const searchParams = request.nextUrl.searchParams; @@ -29,17 +24,9 @@ export async function GET(request: NextRequest) { const chainId = searchParams.get("chain_id"); const category = searchParams.get("category"); - const validStatuses: ChallengeStatus[] = [ - "enrolling", - "competing", - "judging", - "completed", - ]; + const validStatuses: ChallengeStatus[] = ["enrolling", "competing", "judging", "completed"]; if (status && !validStatuses.includes(status)) { - return NextResponse.json( - { error: "Invalid status parameter" }, - { status: 400 } - ); + return NextResponse.json({ error: "Invalid status parameter" }, { status: 400 }); } let query = supabaseAdmin @@ -55,10 +42,7 @@ export async function GET(request: NextRequest) { if (error) { console.error("Failed to fetch challenges:", error); - return NextResponse.json( - { error: "Failed to fetch challenges" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to fetch challenges" }, { status: 500 }); } const now = new Date(); @@ -124,9 +108,6 @@ export async function GET(request: NextRequest) { return NextResponse.json({ challenges }); } catch (error) { console.error("Championship list error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/championship/vote/route.ts b/app/api/championship/vote/route.ts index 5eba0d0..72f545d 100644 --- a/app/api/championship/vote/route.ts +++ b/app/api/championship/vote/route.ts @@ -54,16 +54,10 @@ export async function POST(request: NextRequest) { const judgeEnd = new Date(challenge.judge_end); if (now < endTime) { - return NextResponse.json( - { error: "Voting opens after end_time" }, - { status: 400 } - ); + return NextResponse.json({ error: "Voting opens after end_time" }, { status: 400 }); } if (now > judgeEnd) { - return NextResponse.json( - { error: "Judging period has ended" }, - { status: 400 } - ); + return NextResponse.json({ error: "Judging period has ended" }, { status: 400 }); } // Verify the agent exists and is not withdrawn @@ -75,17 +69,11 @@ export async function POST(request: NextRequest) { .single(); if (!agent) { - return NextResponse.json( - { error: "Agent not found in this challenge" }, - { status: 404 } - ); + return NextResponse.json({ error: "Agent not found in this challenge" }, { status: 404 }); } if (agent.status === "withdrawn") { - return NextResponse.json( - { error: "Cannot vote for a withdrawn agent" }, - { status: 400 } - ); + return NextResponse.json({ error: "Cannot vote for a withdrawn agent" }, { status: 400 }); } // Anti-sybil: minimum balance check (user must exist) @@ -96,20 +84,15 @@ export async function POST(request: NextRequest) { .single(); if (!user) { - return NextResponse.json( - { error: "User not found — balance check failed" }, - { status: 403 } - ); + return NextResponse.json({ error: "User not found — balance check failed" }, { status: 403 }); } // Cast vote (UNIQUE constraint prevents double voting) - const { error: voteError } = await admin - .from("challenge_votes") - .insert({ - challenge_id: challengeId, - voter_wallet: auth.walletAddress, - agent_id: agentId, - }); + const { error: voteError } = await admin.from("challenge_votes").insert({ + challenge_id: challengeId, + voter_wallet: auth.walletAddress, + agent_id: agentId, + }); if (voteError) { if (voteError.code === "23505") { @@ -119,10 +102,7 @@ export async function POST(request: NextRequest) { ); } console.error("Failed to cast vote:", voteError); - return NextResponse.json( - { error: "Failed to cast vote" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to cast vote" }, { status: 500 }); } // Increment agent vote count @@ -131,9 +111,6 @@ export async function POST(request: NextRequest) { return NextResponse.json({ success: true }); } catch (error) { console.error("Cast vote error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/community/create/route.ts b/app/api/community/create/route.ts index 5d2ba70..5e839c4 100644 --- a/app/api/community/create/route.ts +++ b/app/api/community/create/route.ts @@ -28,15 +28,9 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; +import { supabaseAdmin } from "@/app/utils/supabase-admin"; import { jwtVerify } from "jose"; -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); - const JWT_SECRET = process.env.SUPABASE_JWT_SECRET!; interface CreateCommunityRequest { @@ -91,17 +85,11 @@ export async function POST(request: NextRequest) { // Validate required fields if (!body.name || body.name.trim() === "") { - return NextResponse.json( - { error: "Community name is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "Community name is required" }, { status: 400 }); } if (!body.description || body.description.trim() === "") { - return NextResponse.json( - { error: "Community description is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "Community description is required" }, { status: 400 }); } if (!body.activityTypes || body.activityTypes.length === 0) { @@ -114,16 +102,10 @@ export async function POST(request: NextRequest) { // Validate token fields if token is provided if (body.token) { if (!body.token.name || body.token.name.trim() === "") { - return NextResponse.json( - { error: "Token name is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "Token name is required" }, { status: 400 }); } - if ( - !body.token.symbol || - !/^[A-Z]{3,5}$/.test(body.token.symbol.toUpperCase()) - ) { + if (!body.token.symbol || !/^[A-Z]{3,5}$/.test(body.token.symbol.toUpperCase())) { return NextResponse.json( { error: "Token symbol must be 3-5 uppercase letters" }, { status: 400 } @@ -142,17 +124,11 @@ export async function POST(request: NextRequest) { } if (!body.token.minBalance) { - return NextResponse.json( - { error: "Token minimum balance is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "Token minimum balance is required" }, { status: 400 }); } if (!body.token.initialSupply) { - return NextResponse.json( - { error: "Token initial supply is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "Token initial supply is required" }, { status: 400 }); } } @@ -160,18 +136,16 @@ export async function POST(request: NextRequest) { const communityId = `comm_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`; // Create community in database - const { error: communityError } = await supabaseAdmin - .from("communities") - .insert({ - community_id: communityId, - owner_wallet: walletAddress, - name: body.name.trim(), - avatar: body.avatar || null, - description: body.description.trim(), - rules: body.rules?.trim() || null, - activity_types: body.activityTypes, - allow_investment: body.allowInvestment, - }); + const { error: communityError } = await supabaseAdmin.from("communities").insert({ + community_id: communityId, + owner_wallet: walletAddress, + name: body.name.trim(), + avatar: body.avatar || null, + description: body.description.trim(), + rules: body.rules?.trim() || null, + activity_types: body.activityTypes, + allow_investment: body.allowInvestment, + }); if (communityError) { console.error("Failed to create community:", communityError); @@ -182,12 +156,10 @@ export async function POST(request: NextRequest) { } // Add owner as a member - const { error: memberError } = await supabaseAdmin - .from("community_members") - .insert({ - community_id: communityId, - user_wallet: walletAddress, - }); + const { error: memberError } = await supabaseAdmin.from("community_members").insert({ + community_id: communityId, + user_wallet: walletAddress, + }); if (memberError) { console.error("Failed to add owner as member:", memberError); @@ -199,23 +171,21 @@ export async function POST(request: NextRequest) { // Generate a unique asset ID (in production, this should be coordinated with the chain) const assetId = Math.floor(Date.now() % 1000000000); - const { error: tokenError } = await supabaseAdmin - .from("community_tokens") - .insert({ - community_id: communityId, - asset_id: assetId, - admin_wallet: walletAddress, - min_balance: body.token.minBalance, - name: body.token.name.trim(), - symbol: body.token.symbol.toUpperCase(), - decimals: body.token.decimals, - initial_supply: body.token.initialSupply, - issuer_wallet: body.token.issuer?.trim() || null, - freezer_wallet: body.token.freezer?.trim() || null, - is_frozen: false, - total_supply: body.token.initialSupply, - icon: body.token.icon?.trim() || null, - }); + const { error: tokenError } = await supabaseAdmin.from("community_tokens").insert({ + community_id: communityId, + asset_id: assetId, + admin_wallet: walletAddress, + min_balance: body.token.minBalance, + name: body.token.name.trim(), + symbol: body.token.symbol.toUpperCase(), + decimals: body.token.decimals, + initial_supply: body.token.initialSupply, + issuer_wallet: body.token.issuer?.trim() || null, + freezer_wallet: body.token.freezer?.trim() || null, + is_frozen: false, + total_supply: body.token.initialSupply, + icon: body.token.icon?.trim() || null, + }); if (tokenError) { console.error("Failed to create community token:", tokenError); @@ -234,9 +204,6 @@ export async function POST(request: NextRequest) { }); } catch (error) { console.error("Create community error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/community/join/route.ts b/app/api/community/join/route.ts index 6943f5c..25a3e5b 100644 --- a/app/api/community/join/route.ts +++ b/app/api/community/join/route.ts @@ -9,15 +9,9 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; +import { supabaseAdmin } from "@/app/utils/supabase-admin"; import { jwtVerify } from "jose"; -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); - const JWT_SECRET = process.env.SUPABASE_JWT_SECRET!; async function verifyToken(request: NextRequest): Promise { @@ -52,10 +46,7 @@ export async function POST(request: NextRequest) { const { communityId } = body; if (!communityId) { - return NextResponse.json( - { error: "Community ID is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "Community ID is required" }, { status: 400 }); } // Check if community exists @@ -66,10 +57,7 @@ export async function POST(request: NextRequest) { .single(); if (communityError || !community) { - return NextResponse.json( - { error: "Community not found" }, - { status: 404 } - ); + return NextResponse.json({ error: "Community not found" }, { status: 404 }); } // Check if already a member @@ -81,35 +69,23 @@ export async function POST(request: NextRequest) { .single(); if (existingMember) { - return NextResponse.json( - { error: "Already a member of this community" }, - { status: 400 } - ); + return NextResponse.json({ error: "Already a member of this community" }, { status: 400 }); } // Join the community - const { error: joinError } = await supabaseAdmin - .from("community_members") - .insert({ - community_id: communityId, - user_wallet: walletAddress, - }); + const { error: joinError } = await supabaseAdmin.from("community_members").insert({ + community_id: communityId, + user_wallet: walletAddress, + }); if (joinError) { console.error("Failed to join community:", joinError); - return NextResponse.json( - { error: "Failed to join community" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to join community" }, { status: 500 }); } return NextResponse.json({ success: true }); } catch (error) { console.error("Join community error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } - diff --git a/app/api/community/leave/route.ts b/app/api/community/leave/route.ts index 287f384..16b1fa1 100644 --- a/app/api/community/leave/route.ts +++ b/app/api/community/leave/route.ts @@ -9,15 +9,9 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; +import { supabaseAdmin } from "@/app/utils/supabase-admin"; import { jwtVerify } from "jose"; -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); - const JWT_SECRET = process.env.SUPABASE_JWT_SECRET!; async function verifyToken(request: NextRequest): Promise { @@ -52,10 +46,7 @@ export async function POST(request: NextRequest) { const { communityId } = body; if (!communityId) { - return NextResponse.json( - { error: "Community ID is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "Community ID is required" }, { status: 400 }); } // Check if community exists and get owner @@ -66,16 +57,16 @@ export async function POST(request: NextRequest) { .single(); if (communityError || !community) { - return NextResponse.json( - { error: "Community not found" }, - { status: 404 } - ); + return NextResponse.json({ error: "Community not found" }, { status: 404 }); } // Check if user is the owner - owners cannot leave their own community if (community.owner_wallet.toLowerCase() === walletAddress.toLowerCase()) { return NextResponse.json( - { error: "Community owners cannot leave their own community. You can delete the community instead." }, + { + error: + "Community owners cannot leave their own community. You can delete the community instead.", + }, { status: 400 } ); } @@ -104,19 +95,12 @@ export async function POST(request: NextRequest) { if (leaveError) { console.error("Failed to leave community:", leaveError); - return NextResponse.json( - { error: "Failed to leave community" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to leave community" }, { status: 500 }); } return NextResponse.json({ success: true }); } catch (error) { console.error("Leave community error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } - diff --git a/app/api/community/members/route.ts b/app/api/community/members/route.ts index 42785ee..d8cd70d 100644 --- a/app/api/community/members/route.ts +++ b/app/api/community/members/route.ts @@ -13,13 +13,7 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; - -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); +import { supabaseAdmin } from "@/app/utils/supabase-admin"; export async function GET(request: NextRequest) { try { @@ -31,7 +25,7 @@ export async function GET(request: NextRequest) { // Bulk membership check if (communityIds && wallet) { const ids = communityIds.split(",").filter((id) => id.trim()); - + if (ids.length === 0) { return NextResponse.json({ membership: {} }); } @@ -44,10 +38,7 @@ export async function GET(request: NextRequest) { if (error) { console.error("Failed to check membership:", error); - return NextResponse.json( - { error: "Failed to check membership" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to check membership" }, { status: 500 }); } const membershipMap: Record = {}; @@ -67,10 +58,7 @@ export async function GET(request: NextRequest) { if (error) { console.error("Failed to fetch members:", error); - return NextResponse.json( - { error: "Failed to fetch members" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to fetch members" }, { status: 500 }); } const members = (data || []).map((m) => m.user_wallet); @@ -83,10 +71,6 @@ export async function GET(request: NextRequest) { ); } catch (error) { console.error("Community members error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } - diff --git a/app/api/community/route.ts b/app/api/community/route.ts index 7e4ecb0..a792443 100644 --- a/app/api/community/route.ts +++ b/app/api/community/route.ts @@ -10,13 +10,7 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; - -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); +import { supabaseAdmin } from "@/app/utils/supabase-admin"; interface DbCommunity { id: string; @@ -105,10 +99,7 @@ export async function GET(request: NextRequest) { if (error) { console.error("Failed to fetch communities:", error); - return NextResponse.json( - { error: "Failed to fetch communities" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to fetch communities" }, { status: 500 }); } communities = data || []; } else if (type === "created") { @@ -120,27 +111,23 @@ export async function GET(request: NextRequest) { if (error) { console.error("Failed to fetch created communities:", error); - return NextResponse.json( - { error: "Failed to fetch communities" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to fetch communities" }, { status: 500 }); } communities = data || []; } else if (type === "joined") { const { data, error } = await supabaseAdmin .from("community_members") - .select(` + .select( + ` community_id, communities (*) - `) + ` + ) .eq("user_wallet", wallet); if (error) { console.error("Failed to fetch joined communities:", error); - return NextResponse.json( - { error: "Failed to fetch communities" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to fetch communities" }, { status: 500 }); } // Filter out communities where user is the owner (those go in "created") @@ -157,10 +144,6 @@ export async function GET(request: NextRequest) { return NextResponse.json({ communities: mappedCommunities }); } catch (error) { console.error("Community list error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } - diff --git a/app/api/community/search/route.ts b/app/api/community/search/route.ts index 5676d99..4974339 100644 --- a/app/api/community/search/route.ts +++ b/app/api/community/search/route.ts @@ -9,13 +9,7 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; - -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); +import { supabaseAdmin } from "@/app/utils/supabase-admin"; interface DbCommunity { id: string; @@ -91,10 +85,7 @@ export async function GET(request: NextRequest) { if (error) { console.error("Failed to search communities:", error); - return NextResponse.json( - { error: "Failed to search communities" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to search communities" }, { status: 500 }); } const communities = await Promise.all((data || []).map(mapCommunity)); @@ -102,10 +93,6 @@ export async function GET(request: NextRequest) { return NextResponse.json({ communities }); } catch (error) { console.error("Search communities error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } - diff --git a/app/api/friends/[walletAddress]/route.ts b/app/api/friends/[walletAddress]/route.ts index f0e651e..8a792bc 100644 --- a/app/api/friends/[walletAddress]/route.ts +++ b/app/api/friends/[walletAddress]/route.ts @@ -8,16 +8,10 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; +import { supabaseAdmin } from "@/app/utils/supabase-admin"; import { jwtVerify } from "jose"; import { deleteFriend } from "@/app/db/supabase"; -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); - const JWT_SECRET = process.env.SUPABASE_JWT_SECRET!; async function verifyToken(request: NextRequest): Promise { @@ -62,10 +56,7 @@ export async function PUT( if (remark !== undefined) updates.remark = remark?.trim() || null; if (Object.keys(updates).length === 0) { - return NextResponse.json( - { error: "No fields to update" }, - { status: 400 } - ); + return NextResponse.json({ error: "No fields to update" }, { status: 400 }); } const { error } = await supabaseAdmin @@ -85,10 +76,7 @@ export async function PUT( return NextResponse.json({ success: true }); } catch (error) { console.error("Update friend error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } @@ -110,18 +98,11 @@ export async function DELETE( const friendWalletAddress = decodeURIComponent(friendWalletAddressParam); // Use the deleteFriend function from supabase.ts with admin client - const result = await deleteFriend( - walletAddress, - friendWalletAddress, - supabaseAdmin - ); + const result = await deleteFriend(walletAddress, friendWalletAddress, supabaseAdmin); if (!result.success) { if (result.error === "Friend not found") { - return NextResponse.json( - { error: result.error }, - { status: 404 } - ); + return NextResponse.json({ error: result.error }, { status: 404 }); } return NextResponse.json( { error: result.error || "Failed to delete friend. Please try again." }, @@ -132,9 +113,6 @@ export async function DELETE( return NextResponse.json({ success: true }); } catch (error) { console.error("Delete friend error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/friends/route.ts b/app/api/friends/route.ts index 5c9a07a..13904dd 100644 --- a/app/api/friends/route.ts +++ b/app/api/friends/route.ts @@ -8,15 +8,9 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; +import { supabaseAdmin } from "@/app/utils/supabase-admin"; import { jwtVerify } from "jose"; -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); - const JWT_SECRET = process.env.SUPABASE_JWT_SECRET!; async function verifyToken(request: NextRequest): Promise { @@ -71,10 +65,7 @@ export async function GET(request: NextRequest) { return NextResponse.json({ friends }); } catch (error) { console.error("Get friends error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } @@ -109,10 +100,7 @@ export async function POST(request: NextRequest) { .single(); if (existing) { - return NextResponse.json( - { error: "Friend already exists" }, - { status: 400 } - ); + return NextResponse.json({ error: "Friend already exists" }, { status: 400 }); } // Add friend @@ -135,9 +123,6 @@ export async function POST(request: NextRequest) { return NextResponse.json({ success: true }); } catch (error) { console.error("Add friend error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/api/poker/bet/route.ts b/app/api/poker/bet/route.ts index bc05d24..87b3610 100644 --- a/app/api/poker/bet/route.ts +++ b/app/api/poker/bet/route.ts @@ -9,21 +9,21 @@ import { placeBet } from "@/app/services/poker"; import { parseEther } from "viem"; export async function POST(request: NextRequest) { - try { - const { tableId, agentSeat, amountEth, mnemonic } = await request.json(); + try { + const { tableId, agentSeat, amountEth, mnemonic } = await request.json(); - if (!tableId || agentSeat === undefined || !amountEth || !mnemonic) { - return NextResponse.json({ error: "Missing required fields" }, { status: 400 }); - } + if (!tableId || agentSeat === undefined || !amountEth || !mnemonic) { + return NextResponse.json({ error: "Missing required fields" }, { status: 400 }); + } - const amountWei = parseEther(String(amountEth)); - const result = await placeBet(mnemonic, Number(tableId), Number(agentSeat), amountWei); + const amountWei = parseEther(String(amountEth)); + const result = await placeBet(mnemonic, Number(tableId), Number(agentSeat), amountWei); - return NextResponse.json({ ok: true, txHash: result.hash }); - } catch (err) { - return NextResponse.json( - { error: err instanceof Error ? err.message : "Transaction failed" }, - { status: 500 } - ); - } + return NextResponse.json({ ok: true, txHash: result.hash }); + } catch (err) { + return NextResponse.json( + { error: err instanceof Error ? err.message : "Transaction failed" }, + { status: 500 } + ); + } } diff --git a/app/api/poker/claim/route.ts b/app/api/poker/claim/route.ts index d6b9474..3be18e0 100644 --- a/app/api/poker/claim/route.ts +++ b/app/api/poker/claim/route.ts @@ -6,30 +6,38 @@ import { NextRequest, NextResponse } from "next/server"; import { claimChips, claimPrize, claimBetWinnings, refund } from "@/app/services/poker"; export async function POST(request: NextRequest) { - try { - const { tableId, claimType, mnemonic } = await request.json(); + try { + const { tableId, claimType, mnemonic } = await request.json(); - if (!tableId || !claimType || !mnemonic) { - return NextResponse.json({ error: "Missing required fields" }, { status: 400 }); - } - - const tid = Number(tableId); - let result; + if (!tableId || !claimType || !mnemonic) { + return NextResponse.json({ error: "Missing required fields" }, { status: 400 }); + } - switch (claimType) { - case "chips": result = await claimChips(mnemonic, tid); break; - case "prize": result = await claimPrize(mnemonic, tid); break; - case "bet": result = await claimBetWinnings(mnemonic, tid); break; - case "refund": result = await refund(mnemonic, tid); break; - default: - return NextResponse.json({ error: "Invalid claimType" }, { status: 400 }); - } + const tid = Number(tableId); + let result; - return NextResponse.json({ ok: true, txHash: result.hash }); - } catch (err) { - return NextResponse.json( - { error: err instanceof Error ? err.message : "Transaction failed" }, - { status: 500 } - ); + switch (claimType) { + case "chips": + result = await claimChips(mnemonic, tid); + break; + case "prize": + result = await claimPrize(mnemonic, tid); + break; + case "bet": + result = await claimBetWinnings(mnemonic, tid); + break; + case "refund": + result = await refund(mnemonic, tid); + break; + default: + return NextResponse.json({ error: "Invalid claimType" }, { status: 400 }); } + + return NextResponse.json({ ok: true, txHash: result.hash }); + } catch (err) { + return NextResponse.json( + { error: err instanceof Error ? err.message : "Transaction failed" }, + { status: 500 } + ); + } } diff --git a/app/api/poker/tables/route.ts b/app/api/poker/tables/route.ts index 1af5ace..cac2d79 100644 --- a/app/api/poker/tables/route.ts +++ b/app/api/poker/tables/route.ts @@ -12,37 +12,37 @@ import { getTableInfo, getAgentInfo, getTotalBetPool } from "@/app/services/poke const MAX_SCAN = 200; // scan from table 0 up to MAX_SCAN export async function GET(request: NextRequest) { - const params = request.nextUrl.searchParams; - const stateFilter = params.get("state"); - const singleId = params.get("tableId"); - - // Single table fetch - if (singleId !== null) { - const tid = Number(singleId); - const table = await getTableInfo(tid); - if (!table) return NextResponse.json({ error: "Table not found" }, { status: 404 }); - - // Enrich with agents - const agents = []; - for (let s = 0; s < table.agentCount; s++) { - const ai = await getAgentInfo(tid, s); - if (ai) agents.push(ai); - } - const betPool = await getTotalBetPool(tid); - - return NextResponse.json({ table: { ...table, agents, betPoolEth: String(betPool) } }); + const params = request.nextUrl.searchParams; + const stateFilter = params.get("state"); + const singleId = params.get("tableId"); + + // Single table fetch + if (singleId !== null) { + const tid = Number(singleId); + const table = await getTableInfo(tid); + if (!table) return NextResponse.json({ error: "Table not found" }, { status: 404 }); + + // Enrich with agents + const agents = []; + for (let s = 0; s < table.agentCount; s++) { + const ai = await getAgentInfo(tid, s); + if (ai) agents.push(ai); } - - // Scan recent tables - const tables = []; - for (let tid = 0; tid < MAX_SCAN; tid++) { - const table = await getTableInfo(tid); - if (!table) break; // contract reverts on missing tables → stop - if (stateFilter !== null && table.state !== Number(stateFilter)) continue; - tables.push(table); - } - - // Return newest-first - tables.reverse(); - return NextResponse.json({ tables }); + const betPool = await getTotalBetPool(tid); + + return NextResponse.json({ table: { ...table, agents, betPoolEth: String(betPool) } }); + } + + // Scan recent tables + const tables = []; + for (let tid = 0; tid < MAX_SCAN; tid++) { + const table = await getTableInfo(tid); + if (!table) break; // contract reverts on missing tables → stop + if (stateFilter !== null && table.state !== Number(stateFilter)) continue; + tables.push(table); + } + + // Return newest-first + tables.reverse(); + return NextResponse.json({ tables }); } diff --git a/app/api/user/chain-accounts/route.ts b/app/api/user/chain-accounts/route.ts index eabfd97..6579db4 100644 --- a/app/api/user/chain-accounts/route.ts +++ b/app/api/user/chain-accounts/route.ts @@ -10,14 +10,9 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; +import { supabaseAdmin } from "@/app/utils/supabase-admin"; import { jwtVerify } from "jose"; -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); - const JWT_SECRET = process.env.SUPABASE_JWT_SECRET!; async function verifyToken(request: NextRequest): Promise { @@ -76,10 +71,7 @@ export async function POST(request: NextRequest) { const accounts: ChainAccountInput[] = body.accounts; if (!Array.isArray(accounts) || accounts.length === 0) { - return NextResponse.json( - { error: "accounts array is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "accounts array is required" }, { status: 400 }); } // Validate input diff --git a/app/api/user/nicknames/route.ts b/app/api/user/nicknames/route.ts index 92d6f50..f296f3d 100644 --- a/app/api/user/nicknames/route.ts +++ b/app/api/user/nicknames/route.ts @@ -8,13 +8,7 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; - -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); +import { supabaseAdmin } from "@/app/utils/supabase-admin"; export async function POST(request: NextRequest) { try { @@ -23,10 +17,7 @@ export async function POST(request: NextRequest) { const { walletAddresses } = body; if (!walletAddresses || !Array.isArray(walletAddresses)) { - return NextResponse.json( - { error: "Wallet addresses array is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "Wallet addresses array is required" }, { status: 400 }); } if (walletAddresses.length === 0) { @@ -60,10 +51,6 @@ export async function POST(request: NextRequest) { return NextResponse.json({ nicknames: nicknameMap }); } catch (error) { console.error("Get nicknames error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } - diff --git a/app/api/user/profile/route.ts b/app/api/user/profile/route.ts index 6ca2c85..d98941b 100644 --- a/app/api/user/profile/route.ts +++ b/app/api/user/profile/route.ts @@ -13,15 +13,9 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; +import { supabaseAdmin } from "@/app/utils/supabase-admin"; import { jwtVerify } from "jose"; -// Server-side Supabase client with service role key -const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! -); - const JWT_SECRET = process.env.SUPABASE_JWT_SECRET!; interface UpdateProfileRequest { @@ -95,9 +89,6 @@ export async function POST(request: NextRequest) { }); } catch (error) { console.error("Update profile error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } diff --git a/app/chains/evm/adapter.ts b/app/chains/evm/adapter.ts index 10108a6..f48d77c 100644 --- a/app/chains/evm/adapter.ts +++ b/app/chains/evm/adapter.ts @@ -152,8 +152,7 @@ export class EVMChainAdapter implements ChainAdapter { async estimateFee(params: TransferParams): Promise { const client = this.getPublicClient(); - const isNative = - params.ticker === this.nativeTicker || !params.tokenIdentifier; + const isNative = params.ticker === this.nativeTicker || !params.tokenIdentifier; let gasEstimate: bigint; @@ -167,11 +166,7 @@ export class EVMChainAdapter implements ChainAdapter { gasEstimate = await client.estimateGas({ account: params.senderAddress as `0x${string}`, to: params.tokenIdentifier as `0x${string}`, - data: this.encodeTransferData( - params.recipientAddress, - params.amount, - 18 - ), + data: this.encodeTransferData(params.recipientAddress, params.amount, 18), }); } @@ -186,9 +181,7 @@ export class EVMChainAdapter implements ChainAdapter { }; } - async sendTransfer( - params: SignedTransferParams - ): Promise { + async sendTransfer(params: SignedTransferParams): Promise { try { const account = mnemonicToAccount(params.mnemonic); const client = createWalletClient({ @@ -197,8 +190,7 @@ export class EVMChainAdapter implements ChainAdapter { transport: http(this.config.rpcUrl), }); - const isNative = - params.ticker === this.nativeTicker || !params.tokenIdentifier; + const isNative = params.ticker === this.nativeTicker || !params.tokenIdentifier; let txHash: `0x${string}`; @@ -221,11 +213,7 @@ export class EVMChainAdapter implements ChainAdapter { txHash = await client.sendTransaction({ to: tokenAddress as `0x${string}`, - data: this.encodeTransferData( - params.recipientAddress, - params.amount, - decimals - ), + data: this.encodeTransferData(params.recipientAddress, params.amount, decimals), }); } @@ -250,10 +238,7 @@ export class EVMChainAdapter implements ChainAdapter { // -- Transaction history -------------------------------------------------- - async fetchTransactions( - address: string, - page = 0 - ): Promise { + async fetchTransactions(address: string, page = 0): Promise { // Block explorer API integration varies per chain. // For Base, Basescan provides an Etherscan-compatible API. // For Monad, this may need to be updated once their explorer is live. @@ -293,8 +278,7 @@ export class EVMChainAdapter implements ChainAdapter { }) => { const isSent = tx.from.toLowerCase() === address.toLowerCase(); const amount = Number(formatEther(BigInt(tx.value))); - const fee = - Number(formatEther(BigInt(tx.gasUsed) * BigInt(tx.gasPrice))); + const fee = Number(formatEther(BigInt(tx.gasUsed) * BigInt(tx.gasPrice))); return { id: tx.hash, from: tx.from, @@ -316,11 +300,7 @@ export class EVMChainAdapter implements ChainAdapter { // -- Internal helpers ----------------------------------------------------- - private encodeTransferData( - to: string, - amount: number, - decimals: number - ): `0x${string}` { + private encodeTransferData(to: string, amount: number, decimals: number): `0x${string}` { // ERC-20 transfer(address,uint256) selector = 0xa9059cbb const amountWei = parseUnits(amount.toString(), decimals); const paddedTo = to.slice(2).toLowerCase().padStart(64, "0"); diff --git a/app/chains/evm/chains.ts b/app/chains/evm/chains.ts index a06b3ed..3f8911c 100644 --- a/app/chains/evm/chains.ts +++ b/app/chains/evm/chains.ts @@ -20,8 +20,7 @@ const BASE_CONFIGS: Record = { chainName: "Base", nativeTicker: "ETH", nativeDecimals: 18, - iconUrl: - "https://assets.coingecko.com/coins/images/1/small/bitcoin.png?1547033579", + iconUrl: "https://assets.coingecko.com/coins/images/1/small/bitcoin.png?1547033579", viemChain: base, rpcUrl: "https://mainnet.base.org", blockExplorerApiUrl: "https://api.basescan.org/api", @@ -31,8 +30,7 @@ const BASE_CONFIGS: Record = { chainName: "Base Sepolia", nativeTicker: "ETH", nativeDecimals: 18, - iconUrl: - "https://assets.coingecko.com/coins/images/1/small/bitcoin.png?1547033579", + iconUrl: "https://assets.coingecko.com/coins/images/1/small/bitcoin.png?1547033579", viemChain: baseSepolia, rpcUrl: "https://sepolia.base.org", blockExplorerApiUrl: "https://api-sepolia.basescan.org/api", diff --git a/app/chains/near/adapter.ts b/app/chains/near/adapter.ts index 1924542..ffa90a8 100644 --- a/app/chains/near/adapter.ts +++ b/app/chains/near/adapter.ts @@ -5,12 +5,7 @@ * for BIP-44 key derivation from the shared mnemonic. */ -import { - Account, - keyToImplicitAddress, - formatNearAmount, - parseNearAmount, -} from "near-api-js"; +import { Account, keyToImplicitAddress, formatNearAmount, parseNearAmount } from "near-api-js"; import { parseSeedPhrase } from "near-api-js/seed-phrase"; import type { @@ -125,9 +120,7 @@ export class NearChainAdapter implements ChainAdapter { }; } - async sendTransfer( - params: SignedTransferParams - ): Promise { + async sendTransfer(params: SignedTransferParams): Promise { try { const { keyPair, accountId } = deriveNearAccount(params.mnemonic); @@ -154,10 +147,7 @@ export class NearChainAdapter implements ChainAdapter { // -- Transaction history -------------------------------------------------- - async fetchTransactions( - address: string, - page = 0 - ): Promise { + async fetchTransactions(address: string, page = 0): Promise { try { const response = await fetch( `${this.nearblocksApiUrl}/account/${address}/txns?page=${page + 1}&per_page=25&order=desc` @@ -183,9 +173,7 @@ export class NearChainAdapter implements ChainAdapter { const isSent = tx.signer_account_id === address; let amount = 0; - const transferAction = tx.actions?.find( - (a) => a.action === "TRANSFER" - ); + const transferAction = tx.actions?.find((a) => a.action === "TRANSFER"); if (transferAction?.args) { try { const args = @@ -202,9 +190,7 @@ export class NearChainAdapter implements ChainAdapter { } const timestamp = tx.block_timestamp - ? new Date( - Number(BigInt(tx.block_timestamp) / BigInt(1000000)) - ).toISOString() + ? new Date(Number(BigInt(tx.block_timestamp) / BigInt(1000000))).toISOString() : new Date().toISOString(); return { diff --git a/app/chains/near/config.ts b/app/chains/near/config.ts index 75de340..282b3af 100644 --- a/app/chains/near/config.ts +++ b/app/chains/near/config.ts @@ -17,8 +17,7 @@ export const NATIVE_TICKER = "NEAR"; export const NEAR_DECIMALS = 24; /** Chain icon URL. */ -export const ICON_URL = - "https://assets.coingecko.com/coins/images/10365/small/near.jpg"; +export const ICON_URL = "https://assets.coingecko.com/coins/images/10365/small/near.jpg"; interface NearNetworkConfig { rpcUrl: string; diff --git a/app/chains/polkadot/adapter.ts b/app/chains/polkadot/adapter.ts index 89814c2..9fd1820 100644 --- a/app/chains/polkadot/adapter.ts +++ b/app/chains/polkadot/adapter.ts @@ -7,11 +7,7 @@ */ import { Keyring } from "@polkadot/keyring"; -import { - cryptoWaitReady, - decodeAddress, - encodeAddress, -} from "@polkadot/util-crypto"; +import { cryptoWaitReady, decodeAddress, encodeAddress } from "@polkadot/util-crypto"; import { createClient, type PolkadotClient } from "polkadot-api"; import { getWsProvider } from "polkadot-api/ws-provider"; import { pah } from "@polkadot-api/descriptors"; @@ -28,14 +24,7 @@ import type { NetworkMode, } from "../types"; -import { - NETWORK_NAME, - CHAIN_ID, - NATIVE_TICKER, - DOT_DECIMALS, - ICON_URL, - getConfig, -} from "./config"; +import { NETWORK_NAME, CHAIN_ID, NATIVE_TICKER, DOT_DECIMALS, ICON_URL, getConfig } from "./config"; // --------------------------------------------------------------------------- // PolkadotChainAdapter @@ -95,8 +84,7 @@ export class PolkadotChainAdapter implements ChainAdapter { // Native DOT balance const accountInfo = await api.query.System.Account.getValue(address); - const dotAmount = - Number(accountInfo.data.free) / Math.pow(10, DOT_DECIMALS); + const dotAmount = Number(accountInfo.data.free) / Math.pow(10, DOT_DECIMALS); if (dotAmount > 0) { coins.push({ @@ -126,9 +114,7 @@ export class PolkadotChainAdapter implements ChainAdapter { : typeof params.tokenIdentifier === "number" ? 6 : DOT_DECIMALS; - const amountSmallest = BigInt( - Math.floor(params.amount * Math.pow(10, decimals)) - ); + const amountSmallest = BigInt(Math.floor(params.amount * Math.pow(10, decimals))); let tx; if (params.ticker === NATIVE_TICKER) { @@ -163,9 +149,7 @@ export class PolkadotChainAdapter implements ChainAdapter { } } - async sendTransfer( - params: SignedTransferParams - ): Promise { + async sendTransfer(params: SignedTransferParams): Promise { await cryptoWaitReady(); const keyring = new Keyring({ type: "sr25519", ss58Format: this.ss58Format }); @@ -180,9 +164,7 @@ export class PolkadotChainAdapter implements ChainAdapter { : typeof params.tokenIdentifier === "number" ? 6 : DOT_DECIMALS; - const amountSmallest = BigInt( - Math.floor(params.amount * Math.pow(10, decimals)) - ); + const amountSmallest = BigInt(Math.floor(params.amount * Math.pow(10, decimals))); let tx; if (params.ticker === NATIVE_TICKER) { @@ -202,10 +184,8 @@ export class PolkadotChainAdapter implements ChainAdapter { }); } - const signer = getPolkadotSigner( - keypair.publicKey, - "Sr25519", - (input) => keypair.sign(input) + const signer = getPolkadotSigner(keypair.publicKey, "Sr25519", (input) => + keypair.sign(input) ); const result = await tx.signAndSubmit(signer); @@ -226,24 +206,18 @@ export class PolkadotChainAdapter implements ChainAdapter { // -- Transaction history -------------------------------------------------- - async fetchTransactions( - address: string, - page = 0 - ): Promise { + async fetchTransactions(address: string, page = 0): Promise { try { - const response = await fetch( - `${this.subscanApiUrl}/api/v2/scan/transfers`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - address, - row: 25, - page, - direction: "all", - }), - } - ); + const response = await fetch(`${this.subscanApiUrl}/api/v2/scan/transfers`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + address, + row: 25, + page, + direction: "all", + }), + }); if (!response.ok) return []; @@ -270,8 +244,7 @@ export class PolkadotChainAdapter implements ChainAdapter { from: t.from, to: t.to, ticker: t.asset_symbol || "DOT", - amount: - parseFloat(t.amount_v2 || t.amount) / Math.pow(10, decimals), + amount: parseFloat(t.amount_v2 || t.amount) / Math.pow(10, decimals), fee: parseFloat(t.fee) / Math.pow(10, 10), timestamp: new Date(t.block_timestamp * 1000).toISOString(), status: t.success ? "completed" : "failed", diff --git a/app/chains/polkadot/config.ts b/app/chains/polkadot/config.ts index 46cad6a..29f5d2b 100644 --- a/app/chains/polkadot/config.ts +++ b/app/chains/polkadot/config.ts @@ -17,8 +17,7 @@ export const NATIVE_TICKER = "DOT"; export const DOT_DECIMALS = 10; /** Chain icon URL. */ -export const ICON_URL = - "https://assets.coingecko.com/coins/images/12171/small/polkadot.png"; +export const ICON_URL = "https://assets.coingecko.com/coins/images/12171/small/polkadot.png"; interface PolkadotNetworkConfig { ss58Format: number; @@ -36,9 +35,7 @@ const CONFIGS: Record = { }, testnet: { ss58Format: 42, - wsEndpoints: [ - "wss://westend-asset-hub-rpc.polkadot.io", - ], + wsEndpoints: ["wss://westend-asset-hub-rpc.polkadot.io"], subscanApiUrl: "https://assethub-westend.api.subscan.io", }, }; diff --git a/app/chains/registry.ts b/app/chains/registry.ts index 5b4f996..54e022b 100644 --- a/app/chains/registry.ts +++ b/app/chains/registry.ts @@ -108,9 +108,7 @@ export function resetRegistry(): void { * Call once at app startup (e.g. in a top-level layout or provider). * If mode is omitted, it reads from localStorage. */ -export async function initChainRegistry( - mode?: NetworkMode -): Promise { +export async function initChainRegistry(mode?: NetworkMode): Promise { const resolvedMode = mode ?? getStoredNetworkMode(); const registry = getChainRegistry(); diff --git a/app/chains/solana/adapter.ts b/app/chains/solana/adapter.ts index 5457d05..f600bd2 100644 --- a/app/chains/solana/adapter.ts +++ b/app/chains/solana/adapter.ts @@ -110,10 +110,9 @@ export class SolanaChainAdapter implements ChainAdapter { // SPL token balances try { - const tokenAccounts = await connection.getParsedTokenAccountsByOwner( - new PublicKey(address), - { programId: new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") } - ); + const tokenAccounts = await connection.getParsedTokenAccountsByOwner(new PublicKey(address), { + programId: new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), + }); for (const { account } of tokenAccounts.value) { const parsed = account.data.parsed?.info; @@ -168,16 +167,13 @@ export class SolanaChainAdapter implements ChainAdapter { }; } - async sendTransfer( - params: SignedTransferParams - ): Promise { + async sendTransfer(params: SignedTransferParams): Promise { try { const connection = this.getConnection(); const kp = keypairFromMnemonic(params.mnemonic); const recipient = new PublicKey(params.recipientAddress); - const isNative = - params.ticker === NATIVE_TICKER || !params.tokenIdentifier; + const isNative = params.ticker === NATIVE_TICKER || !params.tokenIdentifier; if (isNative) { const tx = new Transaction().add( @@ -193,29 +189,15 @@ export class SolanaChainAdapter implements ChainAdapter { } // SPL token transfer - const { getOrCreateAssociatedTokenAccount, transfer } = await import( - "@solana/spl-token" - ); + const { getOrCreateAssociatedTokenAccount, transfer } = await import("@solana/spl-token"); const mint = new PublicKey(params.tokenIdentifier as string); - const senderATA = await getOrCreateAssociatedTokenAccount( - connection, - kp, - mint, - kp.publicKey - ); + const senderATA = await getOrCreateAssociatedTokenAccount(connection, kp, mint, kp.publicKey); - const recipientATA = await getOrCreateAssociatedTokenAccount( - connection, - kp, - mint, - recipient - ); + const recipientATA = await getOrCreateAssociatedTokenAccount(connection, kp, mint, recipient); const decimals = 9; - const amountRaw = BigInt( - Math.floor(params.amount * Math.pow(10, decimals)) - ); + const amountRaw = BigInt(Math.floor(params.amount * Math.pow(10, decimals))); const signature = await transfer( connection, @@ -237,9 +219,7 @@ export class SolanaChainAdapter implements ChainAdapter { // -- Transaction history -------------------------------------------------- - async fetchTransactions( - address: string, - ): Promise { + async fetchTransactions(address: string): Promise { try { const connection = this.getConnection(); const pubkey = new PublicKey(address); diff --git a/app/chains/solana/config.ts b/app/chains/solana/config.ts index ebdb9d5..6edf944 100644 --- a/app/chains/solana/config.ts +++ b/app/chains/solana/config.ts @@ -17,8 +17,7 @@ export const NATIVE_TICKER = "SOL"; export const SOL_DECIMALS = 9; /** Chain icon URL. */ -export const ICON_URL = - "https://assets.coingecko.com/coins/images/4128/small/solana.png"; +export const ICON_URL = "https://assets.coingecko.com/coins/images/4128/small/solana.png"; /** BIP-44 derivation path for Solana. */ export const DERIVATION_PATH = "m/44'/501'/0'/0'"; diff --git a/app/contexts/NetworkModeContext.tsx b/app/contexts/NetworkModeContext.tsx index 6408e44..6b61897 100644 --- a/app/contexts/NetworkModeContext.tsx +++ b/app/contexts/NetworkModeContext.tsx @@ -1,21 +1,13 @@ "use client"; -import { - createContext, - useContext, - useState, - useCallback, - type ReactNode, -} from "react"; +import { createContext, useContext, useState, useCallback, type ReactNode } from "react"; import type { NetworkMode } from "@/app/chains/types"; import { NETWORK_MODE_KEY } from "@/app/types/constants"; import { resetRegistry } from "@/app/chains/registry"; function getInitialNetworkMode(): NetworkMode { if (typeof window === "undefined") return "mainnet"; - return localStorage.getItem(NETWORK_MODE_KEY) === "testnet" - ? "testnet" - : "mainnet"; + return localStorage.getItem(NETWORK_MODE_KEY) === "testnet" ? "testnet" : "mainnet"; } interface NetworkModeContextValue { diff --git a/app/create-password/page.tsx b/app/create-password/page.tsx index 749064c..2c6b555 100644 --- a/app/create-password/page.tsx +++ b/app/create-password/page.tsx @@ -23,7 +23,7 @@ export default function CreatePasswordPage() { const handleConfirm = async () => { if (!canSubmit) return; - + setIsLoading(true); try { // Get the wallet from localStorage @@ -35,7 +35,7 @@ export default function CreatePasswordPage() { // Encrypt the wallet with the password const success = await encryptWallet(wallet, password); - + if (success) { // Navigate to login page on success router.push("/login"); @@ -52,16 +52,13 @@ export default function CreatePasswordPage() {
{/* Header */}
-

Create Password

-

+

Only for this device

@@ -70,10 +67,7 @@ export default function CreatePasswordPage() {
{/* Create Password field */}
-