From 9a666a7b076980a20bbb8e863714452476ec832f Mon Sep 17 00:00:00 2001 From: Prosper <40717516+onahprosper@users.noreply.github.com> Date: Fri, 13 Feb 2026 23:53:29 +0000 Subject: [PATCH] Revert "chore: merge main branch into stable" --- .github/workflows/create-release.yml | 81 ++- .../calculateCorrectedTotalBalance.test.ts | 39 -- app/api/aggregator.ts | 3 +- app/api/v1/wallets/deprecate/route.ts | 183 ------ app/api/v1/wallets/migration-status/route.ts | 111 ---- app/components/AppLayout.tsx | 13 +- app/components/BalanceSkeleton.tsx | 50 -- app/components/MainPageContent.tsx | 18 +- app/components/MaintenanceNoticeModal.tsx | 124 +--- app/components/MigrationZeroBalanceModal.tsx | 195 ------- app/components/MobileDropdown.tsx | 66 +-- app/components/Navbar.tsx | 24 +- app/components/SettingsDropdown.tsx | 28 +- app/components/TransferForm.tsx | 57 +- app/components/WalletDetails.tsx | 218 +++---- app/components/WalletMigrationBanner.tsx | 152 ----- app/components/WalletMigrationModal.tsx | 184 ------ .../WalletMigrationSuccessModal.tsx | 101 ---- .../WalletTransferApprovalModal.tsx | 547 ------------------ app/components/index.ts | 1 - .../wallet-mobile-modal/WalletView.tsx | 111 +--- app/context/BalanceContext.tsx | 275 +-------- app/context/MigrationContext.tsx | 13 - app/context/index.ts | 7 +- app/globals.css | 2 +- app/hooks/useCNGNRate.ts | 215 ++----- app/hooks/useEIP7702Account.ts | 333 ----------- app/hooks/useSmartWalletTransfer.ts | 111 +--- app/hooks/useSortedCrossChainBalances.ts | 17 - app/lib/config.ts | 6 +- app/lib/privy-config.ts | 2 +- app/pages/TransactionForm.tsx | 77 +-- app/pages/TransactionPreview.tsx | 191 +----- app/pages/TransactionStatus.tsx | 2 - app/types.ts | 14 +- app/utils.ts | 32 +- next.config.mjs | 16 +- package.json | 6 +- pnpm-lock.yaml | 94 --- public/images/checkmark-circle.svg | 4 - public/images/desktop-eip-migration.png | Bin 5327 -> 0 bytes public/images/locked.png | Bin 1550 -> 0 bytes public/images/mobile-eip-migration.png | Bin 2843 -> 0 bytes public/images/sent.png | Bin 1325 -> 0 bytes public/images/wallet.png | Bin 1360 -> 0 bytes .../migrations/create_wallet_migrations.sql | 66 --- 46 files changed, 356 insertions(+), 3433 deletions(-) delete mode 100644 __tests__/calculateCorrectedTotalBalance.test.ts delete mode 100644 app/api/v1/wallets/deprecate/route.ts delete mode 100644 app/api/v1/wallets/migration-status/route.ts delete mode 100644 app/components/MigrationZeroBalanceModal.tsx delete mode 100644 app/components/WalletMigrationBanner.tsx delete mode 100644 app/components/WalletMigrationModal.tsx delete mode 100644 app/components/WalletMigrationSuccessModal.tsx delete mode 100644 app/components/WalletTransferApprovalModal.tsx delete mode 100644 app/context/MigrationContext.tsx delete mode 100644 app/hooks/useEIP7702Account.ts delete mode 100644 app/hooks/useSortedCrossChainBalances.ts delete mode 100644 public/images/checkmark-circle.svg delete mode 100644 public/images/desktop-eip-migration.png delete mode 100644 public/images/locked.png delete mode 100644 public/images/mobile-eip-migration.png delete mode 100644 public/images/sent.png delete mode 100644 public/images/wallet.png delete mode 100644 supabase/migrations/create_wallet_migrations.sql diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index bf7cc835..8c676d2e 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -89,51 +89,46 @@ jobs: fi # Format PRs for release notes - { - echo "formatted_prs<> $GITHUB_OUTPUT - - # Format commits by type - use %s (subject) where conventional types live - # Wrap in block to redirect all output to GITHUB_OUTPUT - { - echo "formatted_commits<> $GITHUB_OUTPUT + while IFS='|' read -r number title body labels; do + echo "### PR #$number: $title" + echo "Labels: $labels" echo "" - echo "### Features" - if [ "$latest_tag" = "0.0.0" ]; then - git log --pretty=format:"- %s" | grep -E "^- feat[^!]" || echo "None" - else - git log $latest_tag..HEAD --pretty=format:"- %s" | grep -E "^- feat[^!]" || echo "None" - fi + echo "$body" echo "" - echo "### Fixes" - if [ "$latest_tag" = "0.0.0" ]; then - git log --pretty=format:"- %s" | grep -E "^- fix" || echo "None" - else - git log $latest_tag..HEAD --pretty=format:"- %s" | grep -E "^- fix" || echo "None" - fi - echo "" - echo "### Other Changes" - if [ "$latest_tag" = "0.0.0" ]; then - git log --pretty=format:"- %s" | grep -E "^- (chore|docs|style|refactor|perf|test|ci|build|revert)" || echo "None" - else - git log $latest_tag..HEAD --pretty=format:"- %s" | grep -E "^- (chore|docs|style|refactor|perf|test|ci|build|revert)" || echo "None" - fi - echo "EOF" - } >> $GITHUB_OUTPUT + done < prs.txt + echo "EOF" >> $GITHUB_OUTPUT + + # Format commits by type - use full commit messages for display + echo "formatted_commits<> $GITHUB_OUTPUT + echo "### Breaking Changes" + if [ "$latest_tag" = "0.0.0" ]; then + git log --pretty=format:"%B" | grep -E "^(feat!|BREAKING CHANGE)" || echo "None" + else + git log $latest_tag..HEAD --pretty=format:"%B" | grep -E "^(feat!|BREAKING CHANGE)" || echo "None" + fi + echo "" + echo "### Features" + if [ "$latest_tag" = "0.0.0" ]; then + git log --pretty=format:"%B" | grep -E "^(feat|\[minor\])" || echo "None" + else + git log $latest_tag..HEAD --pretty=format:"%B" | grep -E "^(feat|\[minor\])" || echo "None" + fi + echo "" + echo "### Fixes" + if [ "$latest_tag" = "0.0.0" ]; then + git log --pretty=format:"%B" | grep -E "^(fix)" || echo "None" + else + git log $latest_tag..HEAD --pretty=format:"%B" | grep -E "^(fix)" || echo "None" + fi + echo "" + echo "### Other Changes" + if [ "$latest_tag" = "0.0.0" ]; then + git log --pretty=format:"%B" | grep -E "^(chore|docs|style|refactor|perf|test|ci|build|revert)" || echo "None" + else + git log $latest_tag..HEAD --pretty=format:"%B" | grep -E "^(chore|docs|style|refactor|perf|test|ci|build|revert)" || echo "None" + fi + echo "EOF" >> $GITHUB_OUTPUT - name: Generate new version id: new_version diff --git a/__tests__/calculateCorrectedTotalBalance.test.ts b/__tests__/calculateCorrectedTotalBalance.test.ts deleted file mode 100644 index 005ee30a..00000000 --- a/__tests__/calculateCorrectedTotalBalance.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { calculateCorrectedTotalBalance } from "../app/utils"; - -describe("calculateCorrectedTotalBalance", () => { - it("excludes CNGN face value from total when rate is null", () => { - const rawBalance = { - total: 1600, - balances: { - USDC: 100, - cNGN: 1500, - }, - }; - - expect(calculateCorrectedTotalBalance(rawBalance, null)).toBe(100); - }); - - it("excludes CNGN face value from total when rate is non-positive", () => { - const rawBalance = { - total: 1600, - balances: { - USDC: 100, - CNGN: 1500, - }, - }; - - expect(calculateCorrectedTotalBalance(rawBalance, 0)).toBe(100); - }); - - it("converts CNGN into USD equivalent when rate is available", () => { - const rawBalance = { - total: 1600, - balances: { - USDC: 100, - cNGN: 1500, - }, - }; - - expect(calculateCorrectedTotalBalance(rawBalance, 1500)).toBe(101); - }); -}); diff --git a/app/api/aggregator.ts b/app/api/aggregator.ts index eb213242..1c1b949b 100644 --- a/app/api/aggregator.ts +++ b/app/api/aggregator.ts @@ -45,7 +45,6 @@ export const fetchRate = async ({ currency, providerId, network, - signal, }: RatePayload): Promise => { const startTime = Date.now(); @@ -72,7 +71,7 @@ export const fetchRate = async ({ params.network = network; } - const response = await axios.get(endpoint, { params, signal }); + const response = await axios.get(endpoint, { params }); const { data } = response; // Track successful response diff --git a/app/api/v1/wallets/deprecate/route.ts b/app/api/v1/wallets/deprecate/route.ts deleted file mode 100644 index 58667b4d..00000000 --- a/app/api/v1/wallets/deprecate/route.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { supabaseAdmin } from "@/app/lib/supabase"; -import { withRateLimit } from "@/app/lib/rate-limit"; -import { trackApiRequest, trackApiResponse, trackApiError } from "@/app/lib/server-analytics"; -import { verifyJWT } from "@/app/lib/jwt"; -import { DEFAULT_PRIVY_CONFIG } from "@/app/lib/config"; - -export const POST = withRateLimit(async (request: NextRequest) => { - const startTime = Date.now(); - - try { - // Step 1: Verify authentication token - const authHeader = request.headers.get("Authorization"); - const token = authHeader?.replace("Bearer ", ""); - - if (!token) { - trackApiError(request, "/api/v1/wallets/deprecate", "POST", new Error("Unauthorized"), 401); - return NextResponse.json( - { success: false, error: "Unauthorized" }, - { status: 401 } - ); - } - - let authenticatedUserId: string; - try { - const jwtResult = await verifyJWT(token, DEFAULT_PRIVY_CONFIG); - authenticatedUserId = jwtResult.payload.sub; - - if (!authenticatedUserId) { - trackApiError(request, "/api/v1/wallets/deprecate", "POST", new Error("Invalid token"), 401); - return NextResponse.json( - { success: false, error: "Invalid token" }, - { status: 401 } - ); - } - } catch (jwtError) { - trackApiError(request, "/api/v1/wallets/deprecate", "POST", jwtError as Error, 401); - return NextResponse.json( - { success: false, error: "Invalid or expired token" }, - { status: 401 } - ); - } - - const walletAddress = request.headers.get("x-wallet-address")?.toLowerCase(); - const body = await request.json(); - const { oldAddress, newAddress, txHash, userId } = body; - - if (!walletAddress || !oldAddress || !newAddress || !userId) { - trackApiError(request, "/api/v1/wallets/deprecate", "POST", new Error("Missing required fields"), 400); - return NextResponse.json( - { success: false, error: "Missing required fields" }, - { status: 400 } - ); - } - - // Step 2: Verify userId matches authenticated user (CRITICAL SECURITY FIX) - if (userId !== authenticatedUserId) { - trackApiError(request, "/api/v1/wallets/deprecate", "POST", new Error("Unauthorized: userId mismatch"), 403); - return NextResponse.json( - { success: false, error: "Unauthorized" }, - { status: 403 } - ); - } - - // Step 3: Verify wallet addresses match - if (newAddress.toLowerCase() !== walletAddress) { - trackApiError(request, "/api/v1/wallets/deprecate", "POST", new Error("Wallet address mismatch"), 403); - return NextResponse.json( - { success: false, error: "Wallet address mismatch" }, - { status: 403 } - ); - } - - trackApiRequest(request, "/api/v1/wallets/deprecate", "POST", { - wallet_address: walletAddress, - old_address: oldAddress, - new_address: newAddress, - }); - - // Step 4: Atomic database operations with rollback on failure - // Ensure old (SCW) wallet exists and mark as deprecated (upsert so we insert if never saved to DB) - const now = new Date().toISOString(); - const { error: deprecateError } = await supabaseAdmin - .from("wallets") - .upsert( - { - address: oldAddress.toLowerCase(), - user_id: userId, - wallet_type: "smart_contract", - status: "deprecated", - deprecated_at: now, - migration_completed: true, - migration_tx_hash: txHash, - updated_at: now, - }, - { onConflict: "address,user_id" } - ); - - if (deprecateError) { - trackApiError(request, "/api/v1/wallets/deprecate", "POST", deprecateError, 500); - throw deprecateError; - } - - // Create or update new EOA wallet record - const { error: upsertError } = await supabaseAdmin - .from("wallets") - .upsert({ - address: newAddress.toLowerCase(), - user_id: userId, - wallet_type: "eoa", - status: "active", - created_at: new Date().toISOString(), - updated_at: now, - }, { onConflict: "address,user_id" }); - - if (upsertError) { - // Rollback: Restore old wallet status - const { error: rollbackError } = await supabaseAdmin - .from("wallets") - .update({ - status: "active", - deprecated_at: null, - migration_completed: false, - migration_tx_hash: null, - updated_at: new Date().toISOString(), - }) - .eq("address", oldAddress.toLowerCase()) - .eq("user_id", userId); - - if (rollbackError) { - console.error("Critical: Rollback failed after EOA upsert error:", rollbackError); - trackApiError(request, "/api/v1/wallets/deprecate", "POST", rollbackError, 500); - } - - trackApiError(request, "/api/v1/wallets/deprecate", "POST", upsertError, 500); - throw upsertError; - } - - // Migrate KYC data - const { error: kycError } = await supabaseAdmin - .from("kyc_data") - .update({ wallet_address: newAddress.toLowerCase() }) - .eq("wallet_address", oldAddress.toLowerCase()) - .eq("user_id", userId); - - if (kycError) { - console.error("KYC migration error:", kycError); - // Return partial success - wallet migrated but KYC migration failed - // This is better than rolling back the entire migration - const responseTime = Date.now() - startTime; - trackApiResponse("/api/v1/wallets/deprecate", "POST", 200, responseTime, { - wallet_address: walletAddress, - migration_successful: true, - kyc_migration_failed: true, - }); - - return NextResponse.json({ - success: true, - message: "Wallet migrated but KYC migration failed", - kycMigrationFailed: true, - }); - } - - const responseTime = Date.now() - startTime; - trackApiResponse("/api/v1/wallets/deprecate", "POST", 200, responseTime, { - wallet_address: walletAddress, - migration_successful: true, - }); - - return NextResponse.json({ success: true, message: "Wallet migrated successfully" }); - } catch (error) { - console.error("Error deprecating wallet:", error); - const responseTime = Date.now() - startTime; - trackApiError(request, "/api/v1/wallets/deprecate", "POST", error as Error, 500, { - response_time_ms: responseTime, - }); - - return NextResponse.json( - { success: false, error: "Internal server error" }, - { status: 500 } - ); - } -}); \ No newline at end of file diff --git a/app/api/v1/wallets/migration-status/route.ts b/app/api/v1/wallets/migration-status/route.ts deleted file mode 100644 index c8d66332..00000000 --- a/app/api/v1/wallets/migration-status/route.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { supabaseAdmin } from "@/app/lib/supabase"; -import { getPrivyClient } from "@/app/lib/privy"; - -export async function GET(request: NextRequest) { - try { - const authHeader = request.headers.get("Authorization"); - const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null; - - if (!token) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - let authenticatedUserId: string; - try { - const privy = getPrivyClient(); - const claims = await privy.verifyAuthToken(token); - authenticatedUserId = claims.userId; - } catch { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - const { searchParams } = new URL(request.url); - const userId = searchParams.get("userId"); - - if (!userId) { - return NextResponse.json({ error: "User ID required" }, { status: 400 }); - } - - if (userId !== authenticatedUserId) { - return NextResponse.json({ error: "Forbidden" }, { status: 403 }); - } - - const { data, error } = await supabaseAdmin - .from("wallets") - .select("migration_completed, status, wallet_type") - .eq("user_id", userId) - .eq("wallet_type", "smart_contract") - .single(); - - if (error) { - // PGRST116 = no rows found — user has no smart wallet, not an error - if (error.code === "PGRST116") { - return NextResponse.json({ - migrationCompleted: false, - status: "unknown", - hasSmartWallet: false, - }); - } - - // PGRST205 = table not found in schema cache — migration not applied yet - if (error.code === "PGRST205") { - console.warn( - "⚠️ Wallets table not found in schema cache. Migration may not be applied yet." - ); - - return NextResponse.json({ - migrationCompleted: false, - status: "schema_unavailable", - hasSmartWallet: false, - error: "Database schema not ready", - }); - } - - console.error("Database query error:", error); - return NextResponse.json({ - migrationCompleted: false, - status: "error", - hasSmartWallet: false, - error: error.message, - }); - } - - return NextResponse.json({ - migrationCompleted: data?.migration_completed ?? false, - status: data?.status ?? "unknown", - hasSmartWallet: !!data, - }); - - } catch (error: unknown) { - const errorMessage = - error instanceof Error ? error.message : String(error); - - const isConnectionError = - errorMessage.includes("ENOTFOUND") || - errorMessage.includes("fetch failed") || - errorMessage.includes("ECONNREFUSED") || - errorMessage.includes("ETIMEDOUT"); - - if (isConnectionError) { - console.warn( - "⚠️ Database connection error, returning fallback response:", - errorMessage - ); - return NextResponse.json({ - migrationCompleted: false, - status: "db_unavailable", - hasSmartWallet: false, - error: "Database temporarily unavailable", - }); - } - - console.error("Unexpected error in migration-status route:", error); - return NextResponse.json({ - migrationCompleted: false, - status: "error", - hasSmartWallet: false, - error: errorMessage || "Internal server error", - }, { status: 500 }); - } -} \ No newline at end of file diff --git a/app/components/AppLayout.tsx b/app/components/AppLayout.tsx index a6070df7..14d84856 100644 --- a/app/components/AppLayout.tsx +++ b/app/components/AppLayout.tsx @@ -1,4 +1,3 @@ -"use client"; import React from "react"; import Script from "next/script"; import config from "../lib/config"; @@ -12,15 +11,13 @@ import { PWAInstall, NoticeBanner, } from "./index"; -import { MigrationBannerWrapper } from "../context"; import { MaintenanceNoticeModal, MaintenanceBanner } from "./MaintenanceNoticeModal"; export default function AppLayout({ children }: { children: React.ReactNode }) { - return (
-
+
{config.maintenanceEnabled ? ( @@ -29,7 +26,6 @@ export default function AppLayout({ children }: { children: React.ReactNode }) { ) )} -
}> {children} @@ -45,10 +41,11 @@ export default function AppLayout({ children }: { children: React.ReactNode }) { {`window.BrevoConversationsID=${JSON.stringify(config.brevoConversationsId)}; window.BrevoConversations=window.BrevoConversations||function(){ (window.BrevoConversations.q=window.BrevoConversations.q||[]).push(arguments)}; - window.BrevoConversationsSetup=${config.brevoConversationsGroupId - ? `{groupId:${JSON.stringify(config.brevoConversationsGroupId)}}` + window.BrevoConversationsSetup=${ + config.brevoConversationsGroupId + ? `{groupId:${JSON.stringify(config.brevoConversationsGroupId)}}` : '{}' - }; + }; `}