Skip to content

Comments

updates#227

Open
sirgawain0x wants to merge 289 commits intocreativeplatform:mainfrom
sirgawain0x:main
Open

updates#227
sirgawain0x wants to merge 289 commits intocreativeplatform:mainfrom
sirgawain0x:main

Conversation

@sirgawain0x
Copy link
Member

@sirgawain0x sirgawain0x commented Oct 10, 2025

Summary by CodeRabbit

  • New Features

    • IPFS-backed storage for avatars and AI thumbnails with gateway fallbacks; AI thumbnail generation via Gemini.
    • Video comments (threaded) and Live/Multistream UX improvements including OBS stream-key display.
    • Creator tools: NFT/collection support, wallet-based portfolio, MeToken creation flows, and video-level MeToken access controls.
  • Bug Fixes

    • Memory-leak, hydration, and iframe-duplicate fixes; more robust upload, transcoding, subtitle, and pagination handling.
    • Improved error messages and recovery for video processing and MeToken flows.
  • Performance

    • Edge caching for published videos and market data; production-ready logging, rate limiting, and memory optimizations.
  • Documentation

    • Extensive setup, migration, and troubleshooting guides added.

✏️ Tip: You can customize this high-level summary in your review settings.

@sirgawain0x sirgawain0x self-assigned this Oct 10, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 10, 2025

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (55)
  • app/actions/story-actions.ts
  • app/api/ai/generate-thumbnail/route.ts
  • app/api/ai/upload-to-ipfs/route.ts
  • app/api/coinbase/session-token/route.ts
  • app/api/creator-profiles/route.ts
  • app/api/creator-profiles/upsert/route.ts
  • app/api/ipfs/upload/route.ts
  • app/api/livepeer/attestation/route.ts
  • app/api/livepeer/livepeer-proxy/route.ts
  • app/api/livepeer/route.ts
  • app/api/livepeer/sign-jwt/route.ts
  • app/api/livepeer/token-gate/route.ts
  • app/api/membership/route.ts
  • app/api/metokens-subgraph/route.ts
  • app/api/metokens/[address]/balance/route.ts
  • app/api/metokens/[address]/route.ts
  • app/api/metokens/[address]/transactions/route.ts
  • app/api/metokens/alchemy/route.ts
  • app/api/metokens/route.ts
  • app/api/metokens/sync/route.ts
  • app/api/poap-proxy/route.ts
  • app/api/poap/create-event/route.ts
  • app/api/reality-eth-subgraph/route.ts
  • app/api/story/factory/deploy-collection/route.ts
  • app/api/story/mint/route.ts
  • app/api/story/rpc-proxy/route.ts
  • app/api/story/transfer/route.ts
  • app/api/swap/execute/route.ts
  • app/api/unlock-nft/route.ts
  • app/api/video-assets/[id]/regenerate-thumbnail/route.ts
  • app/api/video-assets/sync-views/[playbackId]/route.ts
  • app/creator/[address]/page.tsx
  • app/discover/[id]/page.tsx
  • app/discover/page.tsx
  • app/live/[address]/page.tsx
  • app/memberships/page.tsx
  • app/predict/[id]/page.tsx
  • app/predict/create/[address]/page.tsx
  • app/predict/page.tsx
  • app/profile/[address]/page.tsx
  • app/providers.tsx
  • app/upload/[address]/page.tsx
  • app/vote/[id]/page.tsx
  • app/vote/create/[address]/page.tsx
  • app/vote/page.tsx
  • app/watch/[playbackId]/WatchClient.tsx
  • app/watch/[playbackId]/page.tsx
  • components/Creator/CreatorProfileHeader.tsx
  • components/Live/LivestreamGrid.tsx
  • components/Live/LivestreamThumbnail.tsx
  • components/Live/StreamThumbnailUploader.tsx
  • components/Market/MeTokenShareButton.tsx
  • components/Market/QuickTradeDialog.tsx
  • components/Navbar.tsx
  • components/NoSSR.tsx

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Large, heterogeneous update adding/altering linting and tooling, many new or removed API routes (MeTokens, video-assets, livepeer, IPFS, swap), extensive documentation, Supabase/RLS and migration changes, player and upload UX updates, IPFS/gateway integrations, production-readiness fixes, and several client/server boundary and transaction-flow improvements. No subjective commentary.

Changes

Cohort / File(s) Summary
Lint & Tooling / Config
/.eslintrc.json, /.npmrc, /.yarnrc, /.gitignore, app/globals.css
ESLint config updated for TS, env flags; npm/yarn network/retry/timeouts added; font-display: swap added; gitignore extended.
Documentation Bulk
*.md (e.g., ALCHEMY_METOKEN_IMPLEMENTATION.md, IPFS_SETUP.md, SECURITY_DEFINER_FIX.md, many others)
Adds/updates dozens of guides, runbooks, migration notes, and architecture docs across MeTokens, IPFS, Goldsky, Livepeer, Supabase/RLS, production readiness, and troubleshooting.
MeTokens API & Sync
app/api/metokens/..., app/api/metokens-subgraph/route.ts, app/api/metokens/alchemy/route.ts, app/api/metokens/sync/route.ts, app/api/metokens/by-id/[id]/route.ts
New Alchemy-backed creation/tracking route, subgraph proxy switched to Goldsky, sync flow pre-checks, by-id endpoint; includes auth, polling, DB writes and enhanced error handling.
Video Assets & Views
app/api/video-assets/..., app/api/video-assets/published/route.ts, app/api/video-assets/sync-views/*, app/api/video-assets/*/contribution/*
New published edge endpoint with caching, multiple fetch-by-id routes, view sync endpoints (POST/GET + cron), contribution endpoints; client-server boundary fixes for view metrics.
Livepeer & AI/Image IPFS
app/api/livepeer/*, app/api/ai/generate-thumbnail/route.ts, app/api/ai/upload-to-ipfs/route.ts, app/api/ipfs/upload/route.ts
Playback-info route, AI thumbnail generation using Gemini with IPFS upload, new server IPFS upload route, removal of audioToText API. Validate API keys, timeouts, and IPFS fallbacks.
Creator Profiles / Upsert
app/api/creator-profiles/route.ts, app/api/creator-profiles/upsert/route.ts
New CRUD and upsert endpoints using service-role client when available; uses onConflict for owner_address and targeted RLS error messaging.
IPFS Gateway & Image Utilities
lib/utils/ai-image-to-ipfs.ts, lib/utils/image-gateway.ts, components/ui/gateway-image.tsx
Utilities to convert URLs/data->File and gateway fallback logic; GatewayImage component added. Review gateway priority and env var usage.
Player / Playback UX
components/Player/* (HeroPlayer, DemoPlayer, TrendingPlayer, PreviewPlayer, OrbisPlayer, Subtitles, ViewsComponent)
New HeroPlayer, safer pause/play utilities, initial muted/volume=0 changes, subtitle import path updates, client→server fetch changes for view metrics.
Broadcast / Multistream / Live UI
components/Live/Broadcast.tsx, app/live/*
Multistream targets scoped to streamId, Settings now exposes streamKey/ingestUrl, multistream optional in payloads, effect dependency tweaks. Review secret handling.
Upload Flow & Video Asset Fields
components/Videos/Upload/*, app/upload/*, services/video-assets.ts
Conditional step rendering, tightened codec/MIME validation, MeToken access fields added/propagated; subtitle processing routes removed in places—check prop signature changes and migrations.
Client/Layout/Providers
app/layout.tsx, app/providers.tsx, components/ErrorBoundary.tsx, components/IframeCleanup.tsx
Adds ErrorBoundary and IframeCleanup; providers wait for iframe cleanup; suppressHydrationWarning and hydration/script adjustments. Validate SSR/CSR boundaries and readiness gating.
Smart Accounts / Payments / Swap
lib/hooks/payments/useX402Payment.ts, app/api/swap/execute/route.ts, SMART_ACCOUNT_AUTH.md
New client-side x402 hook and swap execute route; smart-account auth docs and service-client guidance; ensure env secrets and security practices.
Bundler & Transactioning Helpers
lib/utils/bundler*, lib/utils/transactionPolling.ts, MeToken subscription files
Added bundler error parser/simulation, EIP-5792/value-hex formatting docs and helpers, new MeToken creation polling utilities—review retry/simulation and value formatting.
Logger, Env Validation, Rate Limits
lib/utils/logger.ts, config/index.ts, lib/middleware/rateLimit.ts
Centralized logger, Zod-based env validation, and rate limit middleware added; verify fail-fast behavior in prod and dev differences.
Database / Security Migrations
SECURITY_DEFINER_FIX.md, migrations, many RLS docs
Views/functions migrated to SECURITY INVOKER and RLS policy guidance updated—review migrations and DB verification queries.
Removed / Deleted Routes
app/api/livepeer/audioToText.ts, app/api/wert/*, app/api/metokens-test/route.ts
Audio-to-text and Wert onramp routes removed; ensure callers updated and docs reflect deletions.
Contracts & Examples
NFT_CONTRACT_EXAMPLE.sol
New ERC-721 example contract with mint controls and rate-limits—audit solidity before use.
Misc Hooks & Performance
lib/hooks/*, app/apolloWrapper.tsx
Debounce/useRef fixes, Apollo cache tuning, memory/GC guidance across docs and config changes.

Sequence Diagram(s)

sequenceDiagram
    participant Frontend
    participant API as Supabase Edge / Next API
    participant Alchemy as Alchemy SDK
    participant DB as Supabase Database

    Frontend->>API: POST /api/metokens/alchemy {name,symbol,hubId,assetsDeposited,creatorAddress,...}
    API->>DB: auth check / verify user (service role fallback)
    API->>Alchemy: if txHash -> waitForMeTokenCreation(txHash)
    Alchemy-->>API: tx confirmed -> meTokenAddress
    API->>Alchemy: fetch on-chain MeToken info
    Alchemy-->>API: protocol/token info
    API->>DB: INSERT meTokens & metoken_transactions
    DB-->>API: inserted record
    API-->>Frontend: 201 { success, meToken, address, txHash }
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Possibly related PRs

  • broadcast #115 — overlaps components/Live/Broadcast.tsx edits; related to Broadcast/Settings UI changes.
  • Mobile player updates #136 — overlaps player pause/play and safe-pause changes in DemoPlayer, Player, PreviewPlayer.
  • onramp #212 — modifies Wert onramp routes; directly related to deletions/changes of app/api/wert/*.

Suggested labels

enhancement

Poem

🐰 I hopped through routes and docs all day,

IPFS, MeTokens, players in the fray.
I stitched the gateways, tuned the play,
Cleaned the iframes, chased bugs away.
A carrot toast — deploy and hooray!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'updates' is vague and generic, using a non-descriptive term that fails to convey any meaningful information about the changeset's primary purpose or content. Replace the title with a specific, descriptive summary of the main change. For example: 'Add documentation and utilities for production readiness' or 'Implement memory optimization and error handling improvements'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @sirgawain0x, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a substantial set of updates focused on improving user experience, enhancing core functionalities, and bolstering the application's stability and security. Key areas of improvement include a more robust video upload process, comprehensive error handling, seamless integration of smart accounts and MeTokens, and a new token swap feature. These changes aim to provide a more intuitive, reliable, and feature-rich platform for creators and users.

Highlights

  • Enhanced Error Handling & Logging: Implemented comprehensive error handling and logging across various components, including subgraph queries, video transcoding, and smart account operations. This provides clearer error messages, better debugging information, and graceful degradation for non-critical features.
  • Smart Account & MeToken Integration: Introduced robust integration for Alchemy Smart Accounts and MeTokens. This includes a new MeToken creation flow, subscription management, trading interface, and a dedicated portfolio page. Creator profiles now leverage Supabase for metadata storage, with avatar uploads handled via IPFS.
  • Video Upload Flow Improvements: Refactored the video upload process with conditional rendering for multi-step forms, preventing premature component mounting and console errors. Added video codec validation, enhanced transcoding error feedback, and removed problematic subtitle generation functionality for a smoother user experience.
  • Pagination & Navigation Fixes: Addressed issues with pagination controls disappearing on empty or error pages, ensuring a consistent user experience. Implemented 'smart pagination' logic that only displays controls when necessary, reducing UI clutter.
  • Alchemy Swap Widget: Integrated a new Alchemy Swap Widget for token exchanges (ETH, USDC, DAI). This includes real-time quotes, USD conversion, balance checks, and a robust execution flow for smart accounts, with detailed debugging guides for common issues like AA23 errors.
  • Security & Performance Enhancements: Fixed Supabase SECURITY DEFINER issues by switching to SECURITY INVOKER for views and functions, enhancing RLS. Addressed React memory leaks and infinite re-rendering loops using useRef for stable callbacks, improving app stability and performance.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This is a massive pull request that introduces a significant number of new features and improvements across the application, including a new swap widget with USD support, MeToken creation and trading enhancements, creator profiles, improved error handling, and fixes for memory leaks and UI bugs. The overall quality of the code is high, with robust logic, good error handling, and a focus on user experience. My review focuses on a couple of areas for improvement, but overall, this is an impressive set of updates.

Comment on lines +18 to +20
// The Livepeer SDK's getAll() method retrieves all assets
const response = await fullLivepeer.asset.getAll();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This implementation fetches all assets from Livepeer on every request and then performs pagination on the server. This is highly inefficient and will not scale as the number of assets grows, potentially leading to high memory usage and slow API responses. The Livepeer SDK's getAll method might support server-side pagination parameters (e.g., limit, cursor). It's crucial to use these to only fetch the data needed for the current page. If the SDK does not support pagination, this approach will become a major performance bottleneck.

// CORS headers for security
const corsHeaders = {
'Access-Control-Allow-Origin': process.env.NODE_ENV === 'production'
? 'https://yourdomain.com' // Replace with your actual domain

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Hardcoding the production origin is not ideal. It's better to source this from an environment variable (e.g., process.env.PROD_URL) to make the application more configurable across different deployment environments (staging, production, etc.).

Suggested change
? 'https://yourdomain.com' // Replace with your actual domain
? 'https://your-production-domain.com' // TODO: Replace with process.env.PROD_URL

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 37

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
components/Navbar.tsx (1)

406-440: Fix TokenSelect desync after swapping tokens

Swapping toggles fromToken/toToken, but TokenSelect keeps its own selectedToken state (see components/ui/token-select.tsx) and never reacts to prop changes. After the swap, the UI still shows the old token even though the state flipped, so users see the wrong asset before confirming. Either make TokenSelect controlled (sync internal state when value changes) or force a remount here.

-                <TokenSelect
+                <TokenSelect
+                  key={fromToken}
                   value={fromToken}
                   onChange={setFromToken}
                   className="w-full"
                 />
…
-                <TokenSelect
+                <TokenSelect
+                  key={toToken}
                   value={toToken}
                   onChange={setToToken}
                   className="w-full"
                 />
components/Videos/Upload/FileUpload.tsx (1)

198-207: Restore the uploaded URI update after a successful upload.

uploadedUri never gets set anymore, so the post-upload success block (Lines 358-377) never renders and users lose the copy-to-clipboard IPFS URI. Wire the success path back to pollForMetadataUri (or otherwise set uploadedUri) so we surface the link again:

         onSuccess() {
           console.log("Upload completed");
           setUploadComplete(true);
           setUploadState("complete");
-          if (uploadRequestResult?.asset?.id)
-            onFileUploaded(uploadRequestResult.asset.id);
-          else setError("Upload succeeded but asset ID is missing.");
+          if (uploadRequestResult?.asset?.id) {
+            onFileUploaded(uploadRequestResult.asset.id);
+
+            void (async () => {
+              try {
+                const metadataUri = await pollForMetadataUri(uploadRequestResult.asset.id);
+                setUploadedUri(metadataUri);
+              } catch (pollErr) {
+                console.warn("Failed to resolve metadata URI:", pollErr);
+              }
+            })();
+          } else {
+            setError("Upload succeeded but asset ID is missing.");
+          }
         },
components/UserProfile/UserProfile.tsx (1)

99-180: Restore membership validation on UserProfile page
ProfilePageGuard now only enforces login and omits membership gating—rewrap the page content in MembershipGuard or add equivalent membership checks (components/UserProfile/UserProfile.tsx).

components/account-dropdown/AccountDropdown.tsx (1)

183-351: Hard-coded Base token addresses break send flow off Base.

TOKEN_INFO and the balance fetcher always use the Base deployment (USDC_TOKEN_ADDRESSES.base / DAI_TOKEN_ADDRESSES.base). When the user switches to Optimism (or any other supported chain) we still read balances and build transfers against the Base contracts, so reads revert and any send attempt will fail. Please key both the balance fetch and the ERC-20 transfer target off chain.id, falling back only when we truly don’t support that chain.

components/UserProfile/MeTokensSection.tsx (2)

38-42: Fix stale state: sync nulls too (prevents ghost MeToken UI).

Effect only sets local state when hookMeToken is truthy; it never clears on null, causing stale UI. Assign unconditionally.

Apply this diff:

-  useEffect(() => {
-    if (hookMeToken) {
-      setUserMeToken(hookMeToken);
-    }
-  }, [hookMeToken]);
+  useEffect(() => {
+    setUserMeToken(hookMeToken || null);
+  }, [hookMeToken]);

293-297: Guard division by zero in price display.

totalSupply can be 0; current code may render Infinity/NaN. Add a safe fallback.

Apply this diff:

-                    <p className="text-2xl font-bold">
-                      ${(userMeToken.tvl / (parseFloat(userMeToken.totalSupply.toString()) / 1e18)).toFixed(4)}
-                    </p>
+                    <p className="text-2xl font-bold">
+                      {
+                        (parseFloat(userMeToken.totalSupply.toString()) > 0)
+                          ? `$${(userMeToken.tvl / (parseFloat(userMeToken.totalSupply.toString()) / 1e18)).toFixed(4)}`
+                          : '-'
+                      }
+                    </p>
🧹 Nitpick comments (43)
components/wallet/swap/DIRECT_CLIENT_FIX.md (1)

138-153: Fix markdownlint MD040 violations.

Several fenced code blocks (e.g., Lines 138-153 and later “Traditional EIP-4337 Flow”) omit a language identifier, which triggers markdownlint MD040. Please annotate these blocks with an appropriate language (e.g., text, shell) so the lint step passes.

app/vote/create/[address]/actions.ts (1)

19-19: Consider validating the address parameter against signer address.

The address parameter is checked for presence but never validated against signer.instance.getAddress() or used in the proposal data. This could allow URL address and actual signer mismatch.

Since this appears to be a pre-existing pattern (per AI summary noting no functional changes), you may optionally add validation:

// After line 51, before try block:
const signerAddress = await signer.instance.getAddress();
if (address.toLowerCase() !== signerAddress.toLowerCase()) {
  return {
    success: false,
    error: "Address mismatch: URL address must match connected signer",
  } as ActionResponse;
}

Or remove the parameter if it's not needed and derive it from the signer instead.

Also applies to: 40-44

components/Player/DemoPlayer.tsx (1)

39-41: Restructure component to improve code clarity and enhance error message.

While the guard works correctly (function declarations are hoisted), the current structure where helper functions are declared after the conditional return is confusing and violates typical React component organization. Additionally, the error message lacks styling and accessibility features.

Recommended improvements:

  1. Move function declarations before the guard for better code organization:
  }, [currentPlayingId, playerId]);

+  const resetFadeTimeout = () => {
+    if (fadeTimeoutRef.current) {
+      clearTimeout(fadeTimeoutRef.current);
+    }
+    setControlsVisible(true);
+    fadeTimeoutRef.current = setTimeout(() => {
+      setControlsVisible(false);
+    }, 2000);
+  };
+
+  const handleControlInteraction = () => {
+    setControlsVisible(true);
+    resetFadeTimeout();
+  };
+
+  const handlePlay = () => {
+    setCurrentPlayingId(playerId);
+  };
+
  if (!src || src.length === 0) {
-    return <div>No video source available.</div>;
+    return (
+      <div className="flex h-full w-full items-center justify-center bg-black">
+        <p className="text-lg text-white" role="alert">
+          No video source available.
+        </p>
+      </div>
+    );
  }

-  const resetFadeTimeout = () => {
-    if (fadeTimeoutRef.current) {
-      clearTimeout(fadeTimeoutRef.current);
-    }
-    setControlsVisible(true);
-    fadeTimeoutRef.current = setTimeout(() => {
-      setControlsVisible(false);
-    }, 2000);
-  };
-
-  const handleControlInteraction = () => {
-    setControlsVisible(true);
-    resetFadeTimeout();
-  };
-
-  const handlePlay = () => {
-    setCurrentPlayingId(playerId);
-  };
-
  return (
  1. Enhanced error message includes:
    • Styling to match the player's dark theme and dimensions
    • Semantic role="alert" for screen reader announcement
    • Centered layout consistent with the loading indicator pattern
components/UserProfile/CreatorProfileDisplay.tsx (1)

22-22: Consider adding type definitions for better type safety.

The meToken object returned from the hook is typed as any | null, which prevents compile-time detection of property access errors. Consider defining and using a proper TypeScript interface for the MeToken data shape to catch inconsistencies early.

For example, create a type definition like:

interface MeTokenDisplay {
  name: string;
  symbol: string;
  tvl?: number;
  total_supply?: string | bigint;
  created_at?: string;
}

Then update the hook return type or cast the meToken appropriately. This would have caught the potential property name mismatch flagged in the previous comment.

components/SendTransaction.tsx (5)

182-186: Reuse viem’s erc20Abi; avoid ad-hoc parseAbi

Simpler and less error‑prone.

-        const transferCalldata = encodeFunctionData({
-          abi: parseAbi(["function transfer(address,uint256) returns (bool)"]),
-          functionName: "transfer",
-          args: [recipient as Address, tokenAmount],
-        });
+        const transferCalldata = encodeFunctionData({
+          abi: erc20Abi,
+          functionName: "transfer",
+          args: [recipient as Address, tokenAmount],
+        });

130-131: Prefer form submit or drop preventDefault

onClick gets a MouseEvent; preventDefault isn’t needed without a

. Either wrap in a and set Button type="submit", or simplify handler signature.

-  const handleSend = async (e: React.FormEvent) => {
-    e.preventDefault();
+  const handleSend = async () => {

Also applies to: 357-361


88-89: Gate console logs or use a logger

Avoid noisy logs in production; add env guard or switch to structured logging.

Also applies to: 117-118, 188-195, 211-213


43-45: Avoid duplicating TokenSymbol; reuse shared type

Reuse TokenSymbol from lib/sdk/alchemy/swap-service.ts (or centralize token config) to prevent drift.


231-233: Magic gas buffer (0.001 ETH)

Consider estimating gas or using paymaster info to compute a dynamic buffer; 0.001 may be too high/low across networks.

app/upload/page.tsx (1)

19-26: Remove console logs before production deployment.

The redirect logic correctly prioritizes smart account addresses, but the console logs should be removed or replaced with a proper logging solution for production.

Apply this diff to remove console logs:

   useEffect(() => {
-    console.log('Upload redirect - EOA:', eoaAddress, 'SCA (client):', scaAddress, 'SCA (account):', account?.address);
     if (smartAccountAddress) {
-      console.log('Redirecting to Smart Account upload:', smartAccountAddress);
       router.replace(`/upload/${smartAccountAddress}`);
     } else if (eoaAddress) {
-      console.log('Redirecting to EOA upload:', eoaAddress);
       router.replace(`/upload/${eoaAddress}`);
     }
   }, [eoaAddress, smartAccountAddress, router, account?.address, scaAddress]);
app/profile/page.tsx (1)

19-26: Remove console logs before production deployment.

The redirect logic is correct, but console logs should be removed or replaced with a proper logging solution for production.

Apply this diff to remove console logs:

   useEffect(() => {
-    console.log('Profile redirect - EOA:', eoaAddress, 'SCA (client):', scaAddress, 'SCA (account):', account?.address);
     if (smartAccountAddress) {
-      console.log('Redirecting to Smart Account profile:', smartAccountAddress);
       router.replace(`/profile/${smartAccountAddress}`);
     } else if (eoaAddress) {
-      console.log('Redirecting to EOA profile:', eoaAddress);
       router.replace(`/profile/${eoaAddress}`);
     }
   }, [eoaAddress, smartAccountAddress, router, account?.address, scaAddress]);
components/wallet/swap/DEPLOY_ACCOUNT_GUIDE.md (2)

13-15: Minor: Add language specifier to fenced code block.

The fenced code block at lines 13-15 should specify a language identifier for proper syntax highlighting and markdown linting compliance.

Apply this diff:

-```
+```text
 The error message shows: "Your smart account address: 0x..."

---

`60-62`: **Minor: Add language specifier to fenced code block.**

The fenced code block at lines 60-62 should specify a language identifier for markdown linting compliance.



Apply this diff:

```diff
-```
+```text
 https://basescan.org/address/YOUR_SMART_ACCOUNT_ADDRESS

</blockquote></details>
<details>
<summary>components/IframeCleanup.tsx (1)</summary><blockquote>

`44-65`: **Consider consolidating effects and removing beforeunload cleanup.**

The two `useEffect` hooks could be consolidated, and the `beforeunload` listener may be unnecessary since:
1. Browsers automatically clean up DOM when navigating away
2. The pathname-triggered effect already handles route changes
3. Component unmount cleanup is sufficient for React navigation



Apply this diff to consolidate:

```diff
-  useEffect(() => {
-    // Clean up iframes when route changes
-    cleanupExistingIframes();
-  }, [pathname]);
-
   useEffect(() => {
-    // Clean up on component mount
+    // Clean up on mount and route changes
     cleanupExistingIframes();
-    
-    // Clean up before page unload
-    const handleBeforeUnload = () => {
-      cleanupExistingIframes();
-    };
-
-    window.addEventListener('beforeunload', handleBeforeUnload);
-    
     return () => {
-      window.removeEventListener('beforeunload', handleBeforeUnload);
-      // Clean up on component unmount
       cleanupExistingIframes();
     };
-  }, []);
+  }, [pathname]);
SUBTITLE_STORAGE_IMPLEMENTATION.md (1)

96-107: Add code block languages for lint compliance.

Markdown lint is flagging the fenced blocks without language identifiers (e.g., the flow charts). Append an explicit language after each triple backtick to keep the docs passing mdlint.

components/ErrorBoundary.tsx (1)

29-35: Also clear the stored error when ignoring aborts.

setState({ hasError: false }) leaves the previous error reference hanging around. Reset both hasError and error so abort cases fully clear the boundary state.

-      this.setState({ hasError: false });
+      this.setState({ hasError: false, error: undefined });
VERIFICATION_GUIDE.md (1)

27-36: Specify fenced code block languages to satisfy markdownlint

Markdownlint is flagging MD040 because the fenced blocks (e.g., Line 27) omit a language. Please add an explicit language like text to these status-style snippets throughout the document to unblock the lint step.

VIDEO_ASSET_FETCH_ERROR_FIX.md (1)

6-6: Add language identifier to fenced code block.

The fenced code block should specify a language for proper syntax highlighting.

Apply this diff:

-```
+```text
 Failed to get video asset by playback ID: TypeError: fetch failed

</blockquote></details>
<details>
<summary>ERROR_FIXES_SUMMARY.md (1)</summary><blockquote>

`11-11`: **Add language identifiers to fenced code blocks.**

Multiple fenced code blocks are missing language identifiers for proper syntax highlighting.

Apply these diffs:

```diff
-```
+```text
 GraphQL Error (Code: 500): Subgraph request failed
 Failed to fetch MeTokens from subgraph

```diff
-```
+```text
 CreateThumbnail: No asset ID provided!

```diff
-```
+```text
 ✅ Query key available
 🔗 Forwarding to subgraph endpoint: https://subgraph.satsuma-prod.com/***/...
 ✅ Subgraph query successful


Also applies to: 98-98, 114-114

</blockquote></details>
<details>
<summary>VIDEO_CODEC_VALIDATION.md (1)</summary><blockquote>

`6-6`: **Add language identifier to fenced code block.**

The fenced code block should specify a language for proper syntax highlighting.

Apply this diff:

```diff
-```
+```text
 Video transcoding failed: invalid video file codec or container, check your input file against the input codec and container support matrix

</blockquote></details>
<details>
<summary>components/wallet/swap/ERROR_FIXES.md (1)</summary><blockquote>

`39-90`: **Add languages to fenced code blocks**

markdownlint (MD040) is flagging these fences. Please specify the language (e.g., ` ```typescript `, ` ```bash `, or ` ```text `) for each block so docs keep passing the lint checks.  
 Based on static analysis hints
```diff
-```
+```typescript
 const tokenAmount = await priceService.convertFromUSD(parseFloat(value), token);

…and repeat for the remaining fences with the appropriate language.

SUBTITLE_PROCESSING_FIX_FINAL.md (1)

7-188: Label fenced code blocks with their languages

markdownlint (MD040) reports these fences because they lack language hints. Annotate them (```bash, ```typescript, ```json, etc.) to keep the docs lint-clean.
Based on static analysis hints

-```
+```bash
 curl -X POST "https://livepeer.studio/api/beta/generate/audio-to-text" \

…and update the remaining blocks with the correct language.

components/Videos/Upload/Create-thumbnail.tsx (2)

101-103: Avoid repeated “Video is ready!” toasts on each poll

This fires every poll while phase === "ready". Gate it to fire once on transition.

Example:

const prevPhaseRef = useRef<string | undefined>();
useEffect(() => {
  const phase = livepeerAssetData?.status?.phase;
  if (phase === 'ready' && prevPhaseRef.current !== 'ready') {
    toast.success("Video is ready!", { duration: 2000 });
  }
  prevPhaseRef.current = phase;
}, [livepeerAssetData?.status?.phase]);

46-50: Reduce verbose console logs in production

Console logs with asset internals every poll and error JSON dumps can be noisy. Consider gating behind a debug flag or using a proper logger at debug level.

Also applies to: 62-69, 74-98, 106-114

QUICK_FIX_GUIDE.md (1)

146-146: Update “Last Updated” timestamp

Consider updating the date to reflect this PR’s changes so readers trust the freshness of the guidance.

components/Videos/Upload/CreateThumbnailForm.tsx (6)

166-187: Handle non-2xx HTTP responses and JSON parsing errors

Both fetch helpers assume response.json() yields a success shape. Check response.ok and guard JSON parsing to surface better errors.

Example:

const res = await fetch(url, { ... });
if (!res.ok) {
  const text = await res.text().catch(() => '');
  throw new Error(text || `Request failed (${res.status})`);
}
const result = await res.json().catch(() => ({}));

Also applies to: 189-223


31-39: assetReady prop is unused

Either use it for UI gating/disabled states or remove it to reduce surface area.

Also applies to: 41-46


538-552: Guard number parsing to avoid NaN

Number('') yields NaN. Consider coercing empty to 0 or undefined and validating accordingly.

Example:

onChange={(e) => {
  const v = e.target.value.trim();
  field.onChange(v === '' ? 0 : Number(v));
}}

20-29: Remove unused selectedImage from FormValues

You maintain selectedImage in component state; the form field isn’t used. Drop it to simplify types.

-interface FormValues {
+interface FormValues {
   thumbnailType: "custom" | "ai";
   customImage: File | null;
   aiPrompt: string;
   meTokenConfig: {
     requireMeToken: boolean;
     priceInMeToken: number;
   };
-  selectedImage: string;
 }

68-73: Narrow aiImages type

Use a concrete shape to improve DX and prevent runtime surprises.

Example:

type AiImage = { id?: string; url: string };
const [aiImages, setAiImages] = useState<AiImage[]>([]);

175-176: Hard-coded price (1 USDC) should be configurable

Extract to config or props for easier tuning and testing.

SUBTITLE_PROCESSING_ERROR_FIX.md (1)

7-10: Add a language hint to fenced code blocks.
markdownlint is flagging this block because it lacks a language identifier; please update the opening fence to something like ```text (or the appropriate language) to satisfy MD040 and keep our docs lint‑clean.

app/api/video-assets/[id]/route.ts (1)

4-39: Consider extracting common route handler logic.

This route follows the same pattern as the other video-asset routes (by-asset-id and by-playback-id). The error handling, response formatting, and overall structure are duplicated across all three routes.

Consider extracting a higher-order function or middleware to reduce duplication:

// lib/api/video-assets-handler.ts
type VideoAssetFetcher<T> = (param: T) => Promise<any>;

export function createVideoAssetHandler<T>(
  paramName: string,
  fetcher: VideoAssetFetcher<T>,
  validator?: (param: T) => boolean
) {
  return async (
    request: NextRequest,
    { params }: { params: Promise<{ [key: string]: string }> }
  ) => {
    try {
      const resolvedParams = await params;
      const paramValue = resolvedParams[paramName] as T;

      if (validator && !validator(paramValue)) {
        return NextResponse.json(
          { error: `Invalid ${paramName}` },
          { status: 400 }
        );
      }

      const asset = await fetcher(paramValue);

      if (!asset) {
        return NextResponse.json(
          { error: "Video asset not found" },
          { status: 404 }
        );
      }

      return NextResponse.json(asset, { status: 200 });
    } catch (error) {
      console.error(`[API] Error fetching video asset by ${paramName}:`, error);
      return NextResponse.json(
        {
          error: "Failed to fetch video asset",
          details: error instanceof Error ? error.message : "Unknown error",
        },
        { status: 500 }
      );
    }
  };
}

Then use it in each route:

// app/api/video-assets/[id]/route.ts
export const GET = createVideoAssetHandler(
  'id',
  (id: string) => getVideoAssetById(parseInt(id, 10)),
  (id: string) => !isNaN(parseInt(id, 10))
);
components/ui/pagination.tsx (1)

15-54: Add accessibility labels for screen readers.

The pagination buttons lack aria-label attributes, which would help screen reader users understand the navigation controls better.

Apply this diff to improve accessibility:

       <Button
         onClick={onPrevPage}
         disabled={!hasPrevPage || isLoading}
         variant="outline"
         size="sm"
         className="flex items-center gap-2"
+        aria-label={`Go to previous page ${hasPrevPage ? `(page ${currentPage - 1})` : '(disabled)'}`}
       >
         <ChevronLeft className="h-4 w-4" />
         Previous
       </Button>
       
       <div className="flex items-center gap-2 text-sm text-muted-foreground">
-        <span>Page {currentPage}</span>
+        <span aria-current="page">Page {currentPage}</span>
         {totalDisplayed && <span>({totalDisplayed} videos)</span>}
       </div>
       
       <Button
         onClick={onNextPage}
         disabled={!hasNextPage || isLoading}
         variant="outline"
         size="sm"
         className="flex items-center gap-2"
+        aria-label={`Go to next page ${hasNextPage ? `(page ${currentPage + 1})` : '(disabled)'}`}
       >
         Next
         <ChevronRight className="h-4 w-4" />
       </Button>
app/api/livepeer/playback-info/route.ts (1)

4-34: LGTM! Consider adding response type validation.

The route implementation is clean and follows good error handling practices. The use of query parameters is appropriate for this endpoint.

Consider validating the response structure from Livepeer to catch API changes early:

const result = await fullLivepeer.playback.get(playbackId);

if (!result?.playbackInfo) {
  console.error('Invalid playback info response:', result);
  return NextResponse.json(
    { error: 'Invalid response from Livepeer' },
    { status: 502 }
  );
}

return NextResponse.json(result.playbackInfo);
components/Videos/VideoThumbnail.tsx (2)

71-80: Improve loading state UX.

When isLoading is true, the code immediately shows the Player component instead of displaying a loading skeleton or the thumbnail. This could cause unnecessary player initialization and a jarring UX.

Apply this diff to show a loading state instead of the player:

   // If we should show the player or no thumbnail is available, show the player
-  if (showPlayer || !thumbnailUrl || isLoading) {
+  if (showPlayer || (!thumbnailUrl && !isLoading)) {
     return (
       <Player
         src={src}
         assetId={assetId}
         title={title}
         onPlay={onPlay}
       />
     );
   }
+
+  // Show loading state
+  if (isLoading) {
+    return (
+      <div className="relative aspect-video bg-gray-900 rounded-lg flex items-center justify-center">
+        <div className="w-8 h-8 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
+      </div>
+    );
+  }

44-51: Remove debug console.log statements.

Console.log statements should be removed or replaced with proper logging for production code.

Remove or replace the console.log statements:

         if (dbAsset && 'thumbnail_url' in dbAsset && dbAsset.thumbnail_url) {
-          console.log('Found database thumbnail:', dbAsset.thumbnail_url);
           setThumbnailUrl(dbAsset.thumbnail_url);
           setIsLoading(false);
           return;
         }
         
         // If no database thumbnail, try Livepeer VTT thumbnails
-        console.log('No database thumbnail found, trying Livepeer VTT for:', playbackId);
         const url = await getThumbnailUrl(playbackId);
         setThumbnailUrl(url);
components/UserProfile/MeTokensSection.tsx (2)

116-121: Use robust address validation (viem isAddress).

String checks can pass invalid hex. Use viem’s isAddress for correctness.

Apply this diff:

-    // Validate address format
-    if (!address.startsWith('0x') || address.length !== 42) {
-      alert('Invalid address format. Please enter a valid Ethereum address (0x followed by 40 hex characters).\n\n' +
-        'Note: Do not paste the transaction hash - you need to find the MeToken contract address from the "Internal Txns" tab on Basescan.');
-      return;
-    }
+    // Validate address format
+    if (!isAddress(address as `0x${string}`)) {
+      alert('Invalid address. Please enter a valid Ethereum address.\n\n' +
+        'Note: Do not paste the transaction hash - you need the MeToken contract address from the "Internal Txns" tab on Basescan.');
+      return;
+    }

Also add the import at top:

-import { useState, useEffect } from 'react';
+import { useState, useEffect } from 'react';
+import { isAddress } from 'viem';

103-108: Replace blocking alerts with toasts for better UX.

window.alert blocks UI. Prefer your existing toast/Alert patterns for consistency.

Example:

  • Use useToast() and toast({ variant: 'destructive' | 'default', title, description }) for errors/info.
  • For long guidance, render in-page as already done elsewhere.

Also applies to: 118-121, 142-152

components/wallet/funding/DaiFundingOptions.tsx (2)

35-37: Prefer chain-aware DAI address selection.

Hardcoding Base limits multi-chain; derive from current chain.

Example approach:

  • Import getDaiTokenContract and use the active chain (via useChain() or client.chain).
  • Fallback to base if unknown.
-import { DAI_TOKEN_ADDRESSES } from '@/lib/contracts/DAIToken';
+import { getDaiTokenContract } from '@/lib/contracts/DAIToken';
...
-      const daiContract = {
-        address: DAI_TOKEN_ADDRESSES.base as `0x${string}`,
-        abi: erc20Abi,
-      };
+      const dai = getDaiTokenContract('base'); // TODO: choose from active chain

39-47: Guard missing account before balanceOf call.

Avoid casting possibly undefined address.

Apply this diff:

-      const balance = await client.readContract({
+      const owner = client.account?.address as `0x${string}` | undefined;
+      if (!owner) throw new Error('Wallet not connected');
+      const balance = await client.readContract({
         address: daiContract.address,
         abi: daiContract.abi,
         functionName: 'balanceOf',
-        args: [client.account?.address as `0x${string}`],
+        args: [owner],
       }) as bigint;
components/UserProfile/MeTokenTrading.tsx (2)

115-116: Prefer chain-aware DAI selection.

Hardcoded 'base' reduces portability. Use chain from Account Kit or a config switch.


233-251: Optional: avoid flashing trading UI before subscription status resolves.

If client isn’t ready, isSubscribed can be null and render trading. Consider showing the loader until known.

Comment on lines 7 to 13
'Access-Control-Allow-Origin': process.env.NODE_ENV === 'production'
? 'https://yourdomain.com' // Replace with your actual domain
: 'http://localhost:3000', // Development only
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400', // 24 hours
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix the hard-coded CORS origin.

Access-Control-Allow-Origin is set to https://yourdomain.com, which will break production requests and conflicts with the existing middleware’s allowed-origin list. Wire this header to the real domains (or omit it and let the middleware handle CORS).

🤖 Prompt for AI Agents
In app/api/coinbase/session-token/route.ts around lines 7 to 13 the
Access-Control-Allow-Origin header is hard-coded to "https://yourdomain.com";
replace this by either removing the header here so the existing CORS middleware
handles origins, or read a real value (or list) from configuration (e.g.
process.env.CORS_ORIGIN or a comma-separated ALLOWED_ORIGINS) and set the header
conditionally for production only (or select the origin from the allowed list
based on request origin), ensuring the placeholder is not shipped to production.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 24

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
components/wallet/buy/wert-fund-button.tsx (1)

89-94: Loading state cleared prematurely.

The setLoading(false) in the finally block (line 93) executes immediately after widget.open() returns synchronously, but the widget loads asynchronously. This means:

  1. The button re-enables before the widget actually appears
  2. Users can click the button multiple times, potentially creating duplicate sessions
  3. The loading state doesn't accurately reflect the widget's loading state

Move setLoading(false) into the widget listeners to reflect the actual widget state:

 listeners: {
   loaded: () => {
     // Close the dialog when the Wert widget is loaded
     onClose?.();
+    setLoading(false);
   },
   close: () => {},
   "payment-status": (data: any) => {},
 },

And if you add an error listener (per previous review comment), also call setLoading(false) there. Then remove setLoading(false) from the finally block, or conditionally set it only for errors that occur before widget.open():

 } catch (err) {
   setError("Failed to start Wert session. Please try again.");
+  setLoading(false);
 } finally {
-  setLoading(false);
 }
app/vote/create/[address]/actions.ts (2)

20-20: Unused parameter: chainId is accepted but not used.

The chainId parameter is defined in the schema (line 20) and destructured (line 37), but the chain is hardcoded to base on line 55. Either remove the unused parameter or use it to dynamically select the chain.

Apply this diff to remove the unused parameter:

 const createProposalSchema = z.object({
   title: z.string().min(3, "Title is required"),
   content: z.string().min(3, "Content is required"),
   choices: z.array(z.string().min(1)).min(2, "At least two choices required"),
   start: z.number().int().positive(),
   end: z.number().int().positive(),
   address: z.string().min(1, "Wallet address required"),
-  chainId: z.number().int().positive(),
 });
     const { title, content, choices, start, end, address, chainId } =
-      parsedInput;
+    const { title, content, choices, start, end, address } = parsedInput;

Also applies to: 37-37, 55-55


56-56: Restrict Alchemy API key to server-side only
The variable NEXT_PUBLIC_ALCHEMY_API_KEY is imported in client bundles (e.g. components/Navbar.tsx, TopChart.tsx) and will be exposed to users. If this key must remain private, rename it (e.g. ALCHEMY_API_KEY) and load it only in server-side code.

components/Navbar.tsx (1)

406-425: TokenSelect stays on the old token after swapping

When the swap button flips fromToken/toToken, the TokenSelect UI still shows the previous choice because the component keeps its own state and never syncs when the value prop changes. Users see mismatched tokens. Add a prop-change effect (or make TokenSelect fully controlled) so the displayed token reflects the new value.

 export function TokenSelect({ 
   value = "ETH", 
   onChange,
   className = "" 
 }: TokenSelectProps) {
-  const [isOpen, setIsOpen] = useState(false);
-  const [selectedToken, setSelectedToken] = useState(
-    TOKENS.find(t => t.symbol === value) || TOKENS[0]
-  );
+  const [isOpen, setIsOpen] = useState(false);
+  const [selectedToken, setSelectedToken] = useState(
+    TOKENS.find(t => t.symbol === value) || TOKENS[0]
+  );
+
+  useEffect(() => {
+    const next = TOKENS.find((t) => t.symbol === value);
+    if (next) {
+      setSelectedToken(next);
+    }
+  }, [value]);
♻️ Duplicate comments (36)
components/wallet/swap/AA23_FIX_COMPLETE.md (2)

164-173: Add a language identifier to this code fence.

The fenced block still lacks a language tag; add ```text (or similar) per markdownlint guidance.

-```
+```text
 1. User enters swap amount

176-184: Add a language identifier to this code fence.

Same here—tag the fence with a language for lint compliance.

-```
+```text
 1. User enters swap amount
components/wallet/balance/TokenBalance.tsx (1)

100-118: Token read failures still masked as "0" balances

The individual token catch blocks (lines 113-118 for USDC, lines 134-139 for DAI) still only log errors and set balances to null without calling setError(). This causes failed reads to render as "0" in the UI (lines 235-237, 252-254), which is misleading to users who cannot distinguish between an actual zero balance and a read failure.

The previous review comment on this issue has not been addressed.

Apply this diff to surface token read failures to users:

         } catch (error) {
           if (isMounted && !signal.aborted) {
             console.error("Error fetching USDC balance:", error);
-            setUsdcBalance(null);
+            setError((prev) => prev ?? "Unable to load token balances");
           }
         }
…
         } catch (error) {
           if (isMounted && !signal.aborted) {
             console.error("Error fetching DAI balance:", error);
-            setDaiBalance(null);
+            setError((prev) => prev ?? "Unable to load token balances");
           }
         }

Also applies to: 120-139

components/wallet/buy/wert-fund-button.tsx (1)

78-82: Error handling and close listener remain unimplemented.

The issues raised in the previous review are still present:

  • No error listener to handle widget load failures. Without this, if the Wert widget fails to load, the parent dialog remains open with no user feedback or recovery path.
  • The close listener is empty. Consider calling onClose?.() here to provide a consistent navigation experience when the user closes the Wert widget.

Refer to the previous review comment for the recommended implementation pattern.

components/Player/HeroPlayer.tsx (1)

39-61: Move the empty-src guard below helper declarations

Returning before resetFadeTimeout is initialized resurrects the same ReferenceError noted earlier: the effect above still runs and calls resetFadeTimeout(), so when src is empty we throw “Cannot access 'resetFadeTimeout' before initialization.” Hoist the helper declarations (or declare them as functions) ahead of the guard, then keep the fallback check afterwards.

-  if (!src || src.length === 0) {
-    return <div>No video source available.</div>;
-  }
-
-  const resetFadeTimeout = () => {
+  const resetFadeTimeout = () => {
     if (fadeTimeoutRef.current) {
       clearTimeout(fadeTimeoutRef.current);
     }
     setControlsVisible(true);
     fadeTimeoutRef.current = setTimeout(() => {
       setControlsVisible(false);
     }, 2000);
   };
 
   const handleControlInteraction = () => {
     setControlsVisible(true);
     resetFadeTimeout();
   };
 
   const handlePlay = () => {
     setCurrentPlayingId(playerId);
   };
+
+  if (!src || src.length === 0) {
+    return <div>No video source available.</div>;
+  }
components/Videos/Upload/CreateThumbnailForm.tsx (1)

88-125: Object URL memory leak still present.

The previous review identified this exact issue: URL.revokeObjectURL is called inside promise callbacks and returned from then/catch, so it never runs as effect cleanup, causing blob URL leaks.

The pattern at lines 103-107 and 114-118:

.then(() => {
  const url = URL.createObjectURL(customImage);
  return () => URL.revokeObjectURL(url);  // Never executes
})

Returning a function from inside a promise handler does not make it an effect cleanup. The returned function is simply ignored.

Apply the solution from the previous review or refactor to:

  1. Create the blob URL synchronously at the start of the effect
  2. Set it immediately to state for preview
  3. Return a cleanup function from the effect (not from promise handlers) that revokes the URL
  4. When IPFS upload succeeds, update state with the IPFS URL and explicitly revoke the blob URL
components/wallet/swap/AlchemySwapWidget.tsx (2)

181-193: Still blocking gasless swaps when paymaster is active.

The previous review identified this issue: the component blocks accounts with ethBalance === 0n even when quote.feePayment?.sponsored is true, preventing paymaster-backed swaps from proceeding.

The balance check at lines 181-193 should only enforce the zero-balance error when swaps are NOT paymaster-sponsored:

-        if (ethBalance === 0n) {
+        if (ethBalance === 0n && !swapState.quote?.feePayment?.sponsored) {
           console.warn('Account has zero ETH balance. This may cause swap failures.');
           setSwapState(prev => ({
             ...prev,
             error: 'Account has zero ETH balance. You need ETH for gas fees to perform swaps. Please add some ETH to your account first.'
           }));
-        } else {
+        } else if (ethBalance > 0n || swapState.quote?.feePayment?.sponsored) {
           // Clear any previous balance-related errors
           setSwapState(prev => ({
             ...prev,
             error: prev.error?.includes('zero ETH balance') ? null : prev.error
           }));
         }

Additionally, add swapState.quote?.feePayment?.sponsored to the effect's dependency array so it re-runs when sponsorship status changes.

Also applies to lines 458-464 where similar logic exists.


520-565: isApprovingToken flag never reset on success path.

The previous review identified this issue: setIsApprovingToken(true) at line 525 is never reset to false on the happy path, leaving the button stuck in "Approving..." state even after the swap succeeds.

Apply this diff to reset the flag after approval completes:

             console.log('✅ Approval confirmed! Hash:', approvalTxHash);
             console.log('✅ Proceeding to swap...');
+            setIsApprovingToken(false);
           } else {

Also reset the flag in the final success path around line 615-620:

       setSwapState(prev => ({
         ...prev,
         transactionHash: txHash,
         isSwapping: false,
       }));
+      setIsApprovingToken(false);
       
       onSwapSuccess?.();
UPLOAD_PAGE_ERROR_FIX.md (1)

5-9: Add language identifier to code block.

The code block is missing a language identifier for proper syntax highlighting and markdown compliance.

Apply this diff:

-```
+```console
 ❌ CreateThumbnail: No asset ID provided!
 This usually means the video upload did not complete successfully.
 Please check the FileUpload component logs.
components/Videos/VideoThumbnail.tsx (1)

42-48: Replace unsafe type assertion with proper typing.

The code uses (dbAsset as any).thumbnail_url which bypasses TypeScript's type safety and could lead to runtime errors.

Apply this diff to fix the type assertion:

         // First, try to get thumbnail from database
         const dbAsset = await fetchVideoAssetByPlaybackId(playbackId);
-        if (dbAsset && (dbAsset as any).thumbnail_url) {
-          console.log('Found database thumbnail:', (dbAsset as any).thumbnail_url);
-          setThumbnailUrl((dbAsset as any).thumbnail_url);
+        if (dbAsset && 'thumbnail_url' in dbAsset && dbAsset.thumbnail_url) {
+          console.log('Found database thumbnail:', dbAsset.thumbnail_url);
+          setThumbnailUrl(dbAsset.thumbnail_url);
           setIsLoading(false);
           return;
         }

Better yet, define a proper type for the video asset response and update fetchVideoAssetByPlaybackId to return it.

app/api/livepeer/actions.ts (1)

18-29: Critical: Client-side pagination will not scale.

This implementation fetches ALL assets from Livepeer on every request and then performs pagination in memory. This is highly inefficient and will cause significant performance degradation and memory issues as the asset count grows.

The Livepeer SDK's asset methods likely support server-side pagination parameters (e.g., limit, cursor, or offset). You must use these to fetch only the data needed for the current page.

Run this script to check if the Livepeer SDK supports pagination parameters:

#!/bin/bash
# Check Livepeer SDK pagination support
rg -nP --type=ts -A5 -B5 'asset\.getAll|asset\.list' node_modules/livepeer

If the SDK doesn't support server-side pagination natively, consider:

  1. Using Livepeer's API directly with pagination parameters
  2. Implementing a caching layer to avoid repeated full fetches
  3. Warning users about performance limitations at scale

Based on learnings

app/api/coinbase/session-token/route.ts (2)

7-13: Fix hard-coded production CORS origin.

Access-Control-Allow-Origin is set to the placeholder https://yourdomain.com, so real production requests will fail CORS. Make this dynamic (e.g., derive from env / request origin or drop this header and rely on the global middleware’s CORS handling).


102-192: Stop logging JWTs and API secrets.

console.log lines expose the freshly minted CDP JWT, API key JSON, and session-token payloads. These are sensitive credentials and must never hit logs. Please remove or mask these statements before shipping.

app/api/creator-profiles/fix-address/route.ts (2)

2-2: Instantiate a server-side Supabase client for this route

This still pulls in the browser helper, so the route will crash on the server and you only have anon-key privileges. Create a service-role client here instead.

-import { supabase } from '@/lib/sdk/supabase/client';
+import { createClient } from '@supabase/supabase-js';
+
+const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
+const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
+
+const supabase = createClient(supabaseUrl, supabaseServiceRoleKey, {
+  auth: { persistSession: false },
+});

109-114: Stop returning stack traces and raw error objects to clients

The response still includes details/errorType/fullError, leaking internals. Log server-side, but send a generic message.

-    return NextResponse.json(
-      { 
-        error: 'Failed to fix profile address',
-        details: error instanceof Error ? error.message : 'Unknown error',
-        errorType: error instanceof Error ? error.constructor.name : typeof error,
-        fullError: JSON.stringify(error, Object.getOwnPropertyNames(error))
-      },
-      { status: 500 }
-    );
+    return NextResponse.json(
+      { error: 'Failed to fix profile address' },
+      { status: 500 }
+    );
components/wallet/balance/DaiBalanceChecker.tsx (1)

13-63: Expose this logic as a hook and guard for missing account address

Rendering <DaiBalanceChecker /> still throws because the function returns a plain object, and we also cast an undefined account address into the contract call. Convert this to a hook-style export and bail early when the smart account hasn’t been resolved.

-interface DaiBalanceCheckerProps {
-  onBalanceUpdate?: (balance: bigint) => void;
-  className?: string;
-}
-
-export function DaiBalanceChecker({ onBalanceUpdate, className }: DaiBalanceCheckerProps) {
+interface UseDaiBalanceCheckerOptions {
+  onBalanceUpdate?: (balance: bigint) => void;
+}
+
+export function useDaiBalanceChecker({ onBalanceUpdate }: UseDaiBalanceCheckerOptions = {}) {
@@
-    try {
+    try {
+      const accountAddress = client.account?.address;
+
+      if (!accountAddress) {
+        setError('Smart account not connected');
+        setIsLoading(false);
+        return;
+      }
+
       const daiContract = {
         address: DAI_TOKEN_ADDRESSES.base as `0x${string}`,
         abi: erc20Abi,
       };
       
       const balance = await client.readContract({
         address: daiContract.address,
         abi: daiContract.abi,
         functionName: 'balanceOf',
-        args: [client.account?.address as `0x${string}`],
+        args: [accountAddress],
       }) as bigint;
@@
-  return {
+  return {
@@
-}
-
-export default DaiBalanceChecker;
+}
+
+export default useDaiBalanceChecker;
components/ui/token-select.tsx (1)

3-33: Keep internal selection in sync with the value prop

When the parent updates value, the dropdown keeps showing the old token. Add an effect to realign internal state.

-import { useState } from "react";
+import { useEffect, useState } from "react";
@@
   const [selectedToken, setSelectedToken] = useState(
     TOKENS.find(t => t.symbol === value) || TOKENS[0]
   );
+
+  useEffect(() => {
+    const next = TOKENS.find((t) => t.symbol === value) || TOKENS[0];
+    setSelectedToken(next);
+  }, [value]);
app/api/swap/execute/route.ts (1)

91-96: Do not expose stack traces in the JSON response

details still returns the stack to clients. Keep the logging, but respond with a generic message.

-    return NextResponse.json(
-      { 
-        error: error instanceof Error ? error.message : 'Swap execution failed',
-        success: false,
-        details: error instanceof Error ? error.stack : undefined
-      },
-      { status: 500 }
-    );
+    return NextResponse.json(
+      {
+        error: error instanceof Error ? error.message : 'Swap execution failed',
+        success: false,
+      },
+      { status: 500 }
+    );
app/api/swap/debug/route.ts (1)

3-27: Remove the unauthenticated secret-dump endpoint.

This GET route still leaks sensitive environment data (including private-key prefixes) to anyone who can hit it, with no auth or prod safeguards. That is a critical secret-exposure risk exactly like the already-flagged issue. Please delete the endpoint or, at minimum, gate it to dev-only and stop returning/ logging any part of secrets.

 export async function GET(request: NextRequest) {
-  try {
-    const envCheck = {
-      hasApiKey: !!process.env.NEXT_PUBLIC_ALCHEMY_API_KEY,
-      hasPolicyId: !!process.env.NEXT_PUBLIC_ALCHEMY_PAYMASTER_POLICY_ID,
-      hasPrivateKey: !!process.env.ALCHEMY_SWAP_PRIVATE_KEY,
-      apiKeyPrefix: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY 
-        ? process.env.NEXT_PUBLIC_ALCHEMY_API_KEY.slice(0, 10) + '...' 
-        : 'MISSING',
-      policyIdPrefix: process.env.NEXT_PUBLIC_ALCHEMY_PAYMASTER_POLICY_ID 
-        ? process.env.NEXT_PUBLIC_ALCHEMY_PAYMASTER_POLICY_ID.slice(0, 10) + '...' 
-        : 'MISSING',
-      privateKeyPrefix: process.env.ALCHEMY_SWAP_PRIVATE_KEY 
-        ? process.env.ALCHEMY_SWAP_PRIVATE_KEY.slice(0, 10) + '...' 
-        : 'MISSING',
-    };
-
-    console.log('Environment debug check:', envCheck);
-
-    return NextResponse.json({
-      success: true,
-      environment: envCheck,
-      message: 'Environment variables check completed'
-    });
-  ...
+  if (process.env.NODE_ENV !== 'development') {
+    return NextResponse.json({ error: 'Not available' }, { status: 403 });
+  }
+
+  const envCheck = {
+    hasApiKey: Boolean(process.env.NEXT_PUBLIC_ALCHEMY_API_KEY),
+    hasPolicyId: Boolean(process.env.NEXT_PUBLIC_ALCHEMY_PAYMASTER_POLICY_ID),
+    hasPrivateKey: Boolean(process.env.ALCHEMY_SWAP_PRIVATE_KEY),
+  };
+
+  return NextResponse.json({
+    success: true,
+    environment: envCheck,
+    message: 'Environment variables check completed',
+  });
components/SendTransaction.tsx (2)

39-176: Validate recipient and stop relying on parseFloat balances

We still cast recipient straight to Address and compare balances with parseFloat. Please add isAddress from viem, reject invalid inputs before sending, and convert amounts/balances with parseUnits/formatUnits so we stay BigInt-safe across 18‑decimals. Also derive token contract addresses from the connected chain instead of hardcoding .base.


278-291: Explorer link should respect the active chain

View on BaseScan still assumes Base. Use client.chain?.blockExplorers?.default?.url (with a fallback) so the button opens the right explorer for whatever chain the wallet is on.

app/api/metokens/alchemy/route.ts (1)

144-153: Propagate insert errors when recording the creation transaction

If this insert hits RLS/network issues we still return 201, silently dropping the record. Capture { error } and fail the request when it’s non-null so the client knows to retry or alert.

-      await supabase
-        .from('metoken_transactions')
-        .insert({
+      const { error: txError } = await supabase
+        .from('metoken_transactions')
+        .insert({
           metoken_id: createdMeToken.id,
           user_address: creatorAddress.toLowerCase(),
           transaction_type: 'create',
           amount: parseFloat(assetsDeposited),
           transaction_hash: transactionHash,
           created_at: new Date().toISOString(),
         });
+
+      if (txError) {
+        console.error('Error recording MeToken transaction:', txError);
+        return NextResponse.json(
+          { error: 'Failed to record creation transaction' },
+          { status: 500 }
+        );
+      }
components/wallet/balance/MeTokenBalances.tsx (1)

64-118: Clear the initial loading flag once hooks settle

isLoading stays true forever, so anyone without holdings/userMeToken only ever sees the skeleton. Tie isLoading to meTokenLoading/holdingsLoading (and errors) so we render the empty state once data loads.

 const [isLoading, setIsLoading] = useState(true);
 const [error, setError] = useState<string | null>(null);
 const [showPortfolio, setShowPortfolio] = useState(false);
 
 // Get the user's MeToken data
 const { userMeToken, loading: meTokenLoading, error: meTokenError } = useMeTokensSupabase();
 
 // Get all MeToken holdings
 const { holdings, loading: holdingsLoading } = useMeTokenHoldings();
+
+useEffect(() => {
+  if (!meTokenLoading && !holdingsLoading) {
+    setIsLoading(false);
+  }
+}, [meTokenLoading, holdingsLoading]);
SUBTITLE_PROCESSING_FIX.md (1)

29-57: Align SUBTITLE_PROCESSING_FIX.md with code or implement missing subtitles props/handlers.

The documentation lists new props and callbacks (subtitlesUri, onSubtitlesProcessed, translateSubtitles, getLivepeerAudioToText) in Create-thumbnail.tsx/CreateThumbnailForm.tsx that aren't present in the code. Either update the doc to match the current implementation or add the described props and handlers.

components/UserProfile/AlchemyMeTokenCreator.tsx (2)

338-338: Prevent render-time crashes when the amount field is invalid.

parseEther(assetsDeposited || '0') runs on every render; if the user types an intermediate non-numeric value (e.g., "1." or "e"), parseEther throws and the component crashes. Sanitize the input (or wrap the call in a safe parser) before invoking parseEther so the UI stays stable while the user edits.

Apply this diff to fix the issue:

-  const hasEnoughDai = daiBalance >= parseEther(assetsDeposited || '0');
+  const safeParseAmount = () => {
+    try {
+      return parseEther(assetsDeposited || '0');
+    } catch {
+      return BigInt(0);
+    }
+  };
+  const hasEnoughDai = daiBalance >= safeParseAmount();

214-240: Capture the actual MeToken address before calling callbacks.

extractMeTokenAddress always returns null, so we persist an empty address to Supabase and invoke onMeTokenCreated('' , txHash). That breaks downstream gating because nothing knows which MeToken was just created. Please parse the transaction receipt (e.g., client.waitForUserOperationReceiptdecodeEventLog for the Subscribe/MeTokenCreated event) and feed the real address through before persisting or notifying listeners.

components/Videos/Upload/index.tsx (1)

184-191: Guard against missing database asset ID before publishing.

videoAsset?.id as number still evaluates to undefined when videoAsset hasn't been hydrated (e.g., DB insert failure or a stale render), so we end up calling updateVideoAsset(undefined, …) and Supabase rejects the update. Bail out early if the ID is absent instead of force-casting.

Apply this diff to enforce the guard:

-            await updateVideoAsset(videoAsset?.id as number, {
+            if (!videoAsset?.id) {
+              toast.error("Missing video asset record. Please re-upload the video.");
+              return;
+            }
+
+            await updateVideoAsset(videoAsset.id, {
SUPABASE_RLS_SOLUTION.md (1)

22-40: Option 2 has the same security risks as Option 3.

The RLS policies in Option 2 use WITH CHECK (true) and USING (true), which allow unrestricted public access to insert, update, and delete operations. This effectively provides the same level of access as disabling RLS entirely (Option 3), making it equally unsuitable for production environments.

Apply this diff to add a security warning:

-### Option 2: Update RLS Policies (If you have database access)
+### Option 2: Update RLS Policies (Not Recommended for Production)
 If you can modify the database, update the RLS policies to be more permissive:
+
+**⚠️ Security Warning**: The policies below allow unrestricted public access and should only be used in development environments. This approach bypasses authentication entirely.
 
 ```sql
components/Videos/VideoCardGrid.tsx (1)

22-205: Restore pagination visibility on page 1.

totalPublishedAssets is set to publishedAssets.length, i.e. the current page size, so it never exceeds ITEMS_PER_PAGE. On the first page shouldShowPagination() stays false, hiding the controls even when hasNextPage is true—users can’t reach page 2+. Base the visibility check on hasNextPage (and/or the real total) instead of the per-page length, and drop the redundant state.

-  const [totalPublishedAssets, setTotalPublishedAssets] = useState<number>(0);
...
-      setTotalPublishedAssets(publishedAssets.length);
...
-    return totalPublishedAssets > ITEMS_PER_PAGE || currentPage > 1;
-  }, [totalPublishedAssets, currentPage]);
+  return hasNextPage || currentPage > 1;
+  }, [hasNextPage, currentPage]);
components/UserProfile/UserProfile.tsx (1)

133-133: Preserve profile-specific upload context.

The href="/upload" link now always routes to the authenticated user's upload page, dropping the viewed profile's displayAddress. If users should upload to the profile they're viewing, change it to href={"/upload/${displayAddress}"}. Otherwise, hide or disable the upload button when displayAddress !== walletAddress.

ALCHEMY_METOKEN_IMPLEMENTATION.md (1)

102-104: Correct the documented creation flow.

The doc says "Creates MeToken using Diamond contract's subscribe function," but the actual implementation (see lib/hooks/metokens/useMeTokensSupabase.ts, lines 430-535) invokes METOKEN_FACTORY.create to deploy the MeToken and only later mints via DIAMOND.mint. Update the documentation so the described sequence matches the shipped code path.

app/api/creator-profiles/upsert/route.ts (1)

5-93: Privilege escalation: upsert allows anyone to overwrite profiles.

Because the handler never authenticates the caller, any client can POST arbitrary owner_address (and new profile data) and—when SUPABASE_SERVICE_ROLE_KEY is configured—bypass RLS entirely. That lets an attacker overwrite someone else's profile. Require an authenticated session and enforce that the caller owns the profile (e.g., read the Supabase user from createClient and compare, or verify a signed wallet message) before performing the upsert, instead of unconditionally using the service-role client.

As per coding guidelines

Example fix:

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const { owner_address, username, bio, avatar_url } = body;

    if (!owner_address) {
      return NextResponse.json(
        { success: false, error: 'owner_address is required' },
        { status: 400 }
      );
    }

    // Authenticate the caller
    const supabase = await createClient();
    const { data: { user } } = await supabase.auth.getUser();
    
    if (!user) {
      return NextResponse.json(
        { success: false, error: 'Authentication required' },
        { status: 401 }
      );
    }

    // Verify the caller owns the profile
    // Assuming user.id or user.user_metadata contains the wallet address
    const authenticatedAddress = user.user_metadata?.address?.toLowerCase();
    const requestedAddress = owner_address.toLowerCase();
    
    if (authenticatedAddress !== requestedAddress) {
      return NextResponse.json(
        { success: false, error: 'Cannot modify another user\'s profile' },
        { status: 403 }
      );
    }

    // Now perform the upsert with the authenticated user's context
    const result = await supabase
      .from('creator_profiles')
      .upsert({
        owner_address: requestedAddress,
        username,
        bio,
        avatar_url,
        updated_at: new Date().toISOString(),
      }, {
        onConflict: 'owner_address'
      })
      .select();
    
    // ... rest of error handling
  } catch (error) {
    // ... error handling
  }
}
components/UserProfile/MeTokenTrading.tsx (1)

93-200: Allowance/approval uses Diamond as spender instead of the vault

This repeats the prior issue: checking and approving DAI against the Diamond contract (0xba55…) is the wrong spender. Trading flows require allowance to the hub vault returned by Diamond.getHubInfo(hubId), and buyMeTokens already enforces that. Keeping this code issues redundant approvals that cost the user gas while still leaving the vault allowance untouched. Please either drop the local allowance logic and rely on buyMeTokens’s helper or update the check/approve paths to resolve the vault first and target that address.

components/wallet/funding/DaiFundingOptions.tsx (1)

62-165: Convert requiredAmount from wei before deriving the fiat preset.

requiredAmount is treated as a wei string everywhere else (BigInt(...), formatEther(...)). Parsing that string directly with parseFloat yields values on the order of 1e18, so the on-ramp request asks users to buy absurd sums and effectively breaks funding. Convert to ether first, then round.

-  const hasEnoughDai = requiredAmount ? daiBalance >= BigInt(requiredAmount) : daiBalance > BigInt(0);
-  const hasAnyDai = daiBalance > BigInt(0);
+  const hasAnyDai = daiBalance > BigInt(0);
+  const requiredAmountWei = requiredAmount !== undefined ? BigInt(requiredAmount) : null;
+  const hasEnoughDai = requiredAmountWei !== null ? daiBalance >= requiredAmountWei : hasAnyDai;
+  const presetFiatAmount =
+    requiredAmountWei !== null
+      ? Math.ceil(Number(formatEther(requiredAmountWei)))
+      : 50;
@@
-            <p>Your DAI balance: <span className="font-medium text-foreground">{formatEther(daiBalance)} DAI</span></p>
-            {requiredAmount && (
-              <p>Required: <span className="font-medium text-foreground">{formatEther(BigInt(requiredAmount))} DAI</span></p>
+            <p>Your DAI balance: <span className="font-medium text-foreground">{formatEther(daiBalance)} DAI</span></p>
+            {requiredAmountWei !== null && (
+              <p>Required: <span className="font-medium text-foreground">{formatEther(requiredAmountWei)} DAI</span></p>
@@
-            ? `You have ${formatEther(daiBalance)} DAI, but need ${requiredAmount ? formatEther(BigInt(requiredAmount)) : 'more'} DAI.`
+            ? `You have ${formatEther(daiBalance)} DAI, but need ${requiredAmountWei !== null ? formatEther(requiredAmountWei) : 'more'} DAI.`
@@
-            <DaiFundButton
-              presetAmount={requiredAmount ? Math.ceil(parseFloat(requiredAmount)) : 50}
+            <DaiFundButton
+              presetAmount={presetFiatAmount}
@@
-          {requiredAmount && (
-            <p><strong>Required:</strong> {formatEther(BigInt(requiredAmount))} DAI</p>
+          {requiredAmountWei !== null && (
+            <p><strong>Required:</strong> {formatEther(requiredAmountWei)} DAI</p>
           )}
components/UserProfile/MeTokenCreator.tsx (1)

95-123: Fix the stale MeToken guard after checkUserMeToken().

checkUserMeToken() updates state asynchronously, so immediately reading userMeToken still sees the old value and the guard can miss an existing token—exactly the bug we hit before. Capture and branch on the value returned by checkUserMeToken (or have the hook return it) instead of the stale state snapshot before proceeding to subgraph checks.

components/Videos/Upload/Create-thumbnail.tsx (1)

165-171: Fix stale thumbnailUri passed to onComplete.

setSelectedThumbnail is async, so selectedThumbnail still holds the old value when you invoke onComplete. The previous review already called this out—the handler still forwards the stale state.

   if (livepeerAssetData) {
     setSelectedThumbnail(thumbnailUri);
     onComplete({
-      thumbnailUri: selectedThumbnail as string,
+      thumbnailUri,
       meTokenConfig,
     });
🧹 Nitpick comments (25)
components/Videos/Upload/Stepper-Indicator.tsx (2)

15-23: Consider adding accessibility attributes for screen readers.

The stepper items lack ARIA attributes that would improve accessibility for users relying on assistive technologies.

Consider adding accessibility attributes:

 <div
   className={clsx(
     "m-1 flex h-8 w-8 items-center justify-center rounded-full border-2 text-xs sm:m-[5px] sm:h-[40px] sm:w-[40px] sm:text-base",
     step < activeStep && "bg-secondary text-[#EC407A]",
     step === activeStep && "border-primary text-primary"
   )}
+  role="status"
+  aria-label={`Step ${step} of 3${step < activeStep ? ' (completed)' : step === activeStep ? ' (current)' : ''}`}
+  aria-current={step === activeStep ? 'step' : undefined}
 >
   {step >= activeStep ? step : <Check className="h-3 w-3 sm:h-5 sm:w-5" />}
 </div>

18-18: Consider using a theme color instead of hardcoded hex value.

The hardcoded color text-[#EC407A] is inconsistent with the theme-based colors (bg-primary, text-primary, border-primary) used elsewhere in the component. This could make theme management and color consistency harder to maintain.

Consider defining this pink color in your Tailwind theme configuration and using a semantic utility class like text-accent or text-completed:

-step < activeStep && "bg-secondary text-[#EC407A]",
+step < activeStep && "bg-secondary text-accent",

Then add to your Tailwind config:

// tailwind.config.ts
theme: {
  extend: {
    colors: {
      accent: '#EC407A',
      // or
      completed: '#EC407A',
    }
  }
}
components/wallet/buy/wert-fund-button.tsx (3)

99-99: Consider extracting common button styling.

The className="w-full mb-4" is hardcoded on the button. If this styling pattern is used elsewhere in wallet components, consider defining it as a Button variant or extracting it to a constant for consistency.


53-53: Remove debug logging for production.

The debug console.log should be removed or gated behind a development-only condition to avoid cluttering production logs.

Apply this diff:

-    const partnerId = process.env.NEXT_PUBLIC_WERT_PARTNER_ID;
-    console.log("DEBUG: NEXT_PUBLIC_WERT_PARTNER_ID =", partnerId);
+    const partnerId = process.env.NEXT_PUBLIC_WERT_PARTNER_ID;

20-20: Improve type safety for widget reference.

The wertWidgetRef is typed as any. Check if @wert-io/widget-initializer exports a type for WertWidget instances and use it:

-const wertWidgetRef = useRef<any>(null);
+const wertWidgetRef = useRef<WertWidget | null>(null);

This would provide better type checking and IDE support for widget methods and properties.

components/wallet/swap/ConversionExample.tsx (2)

116-127: Consider validating hex input format.

The hex input field accepts any string without validation. Users could enter invalid hex values (e.g., missing 0x prefix, non-hex characters), which would cause the conversion in the useEffect to fail silently (lines 33-41).

Add basic validation:

         <div className="space-y-2">
           <Label>Hexadecimal (Wei)</Label>
           <Input
             value={hexInput}
-            onChange={(e) => setHexInput(e.target.value)}
+            onChange={(e) => {
+              const value = e.target.value;
+              // Only update if it's a valid hex format or empty (for clearing)
+              if (value === '' || /^0x[0-9a-fA-F]*$/.test(value)) {
+                setHexInput(value);
+              }
+            }}
             placeholder="0x..."
             className="font-mono text-sm"
           />

50-86: Consider debouncing the input handlers.

Both handleTokenAmountChange and handleUsdChange trigger async operations on every keystroke. If a user types quickly, multiple concurrent requests could complete out of order, leading to stale state or UI flicker. Debouncing would improve UX and reduce unnecessary API calls.

Example using a simple debounce hook or utility:

import { useMemo } from 'react';
import { debounce } from 'lodash'; // or implement your own

export function ConversionExample() {
  // ... existing state ...

  const debouncedTokenChange = useMemo(
    () => debounce(async (value: string) => {
      const sanitized = CurrencyConverter.sanitizeAmount(value, 18);
      const numValue = parseFloat(sanitized);
      if (!isNaN(numValue) && numValue > 0) {
        try {
          const hex = AlchemySwapService.formatAmount(sanitized, token);
          setHexInput(hex);
          const usd = await priceService.convertToUSD(numValue, token);
          setUsdValue(usd);
        } catch (error) {
          console.error('Error converting token to hex:', error);
        }
      }
    }, 300),
    [token]
  );

  const handleTokenAmountChange = (value: string) => {
    setTokenAmount(value);
    debouncedTokenChange(value);
  };

  // Similar pattern for handleUsdChange
}
app/api/x402/pay-for-ai-thumbnail/route.ts (1)

7-17: Consider externalizing payment configuration.

The X402_CONFIG is well-structured for USDC/Base integration. However, once you implement the client-side payment flow, consider moving this configuration to a shared config file (e.g., app/config/x402/payment-config.ts) so it can be reused across client components and any server-side validation endpoints.

app/vote/create/[address]/actions.ts (1)

54-60: Consider using a simpler public client for read-only operations.

Creating a full ModularAccountClient just to call getBlockNumber() is inefficient. Since this is a read-only operation and the smart account client isn't used for signing or sending transactions, consider using viem's createPublicClient instead.

Apply this diff to use a lighter-weight public client:

-    try {
-      // 1. Get the modular account client
-      const modularAccountClient = await createModularAccountClient({
-        chain: base, // or your chain object
-        apiKey: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY as string,
-      });
-
-      // 2. Get the current block number (using viem or account kit client)
-      const block = await modularAccountClient.getBlockNumber();
+    try {
+      // 1. Create a public client for read operations
+      const publicClient = createPublicClient({
+        chain: base,
+        transport: http(`https://base-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`),
+      });
+
+      // 2. Get the current block number
+      const block = await publicClient.getBlockNumber();

Don't forget to import createPublicClient and http from viem at the top of the file.

VIDEO_ASSET_FETCH_ERROR_FIX.md (1)

75-78: Add language identifier to fenced code block.

The fenced code block starting at line 75 is missing a language identifier, which reduces readability and prevents syntax highlighting.

Apply this diff to add the language identifier:

-```
+```text
 Client Component → Server Function (with cookies()) → Supabase
      ❌ FAILS: Client can't use server-only APIs

Based on the static analysis hint.

</blockquote></details>
<details>
<summary>app/api/video-assets/by-asset-id/[assetId]/route.ts (1)</summary><blockquote>

`11-16`: **Optional: Defensive check may be unnecessary.**

The `assetId` parameter is extracted from the route path `[assetId]`, so Next.js ensures it's always present. This validation is defensive but redundant since the route pattern guarantees the parameter exists.



Consider removing this check or adding a comment explaining it's for extra safety:

```diff
-    if (!assetId) {
-      return NextResponse.json(
-        { error: "Asset ID is required" },
-        { status: 400 }
-      );
-    }
+    // Note: assetId is guaranteed by route pattern, but we check for safety
+    if (!assetId) {
+      return NextResponse.json(
+        { error: "Asset ID is required" },
+        { status: 400 }
+      );
+    }

Or simply remove it:

     const { assetId } = await params;
-
-    if (!assetId) {
-      return NextResponse.json(
-        { error: "Asset ID is required" },
-        { status: 400 }
-      );
-    }

     const asset = await getVideoAssetByAssetId(assetId);
app/profile/page.tsx (1)

18-27: LGTM! Redirect logic correctly prioritizes smart account.

The redirect flow appropriately prioritizes the smart account address over EOA, aligning with the Account Abstraction approach. The console logging is helpful for debugging the address resolution flow.

Consider removing console.log statements in production or using a proper logger:

-    console.log('Profile redirect - EOA:', eoaAddress, 'SCA (client):', scaAddress, 'SCA (account):', account?.address);
     if (smartAccountAddress) {
-      console.log('Redirecting to Smart Account profile:', smartAccountAddress);
       router.replace(`/profile/${smartAccountAddress}`);
     } else if (eoaAddress) {
-      console.log('Redirecting to EOA profile:', eoaAddress);
       router.replace(`/profile/${eoaAddress}`);
     }
components/IframeCleanup.tsx (2)

14-42: Iframe cleanup is functional but could be more efficient.

The cleanup function uses multiple DOM queries and innerHTML = "" which works but could be optimized.

Consider these improvements:

 const cleanupExistingIframes = () => {
   if (typeof window === "undefined") return;
   
   // Clean up container
   const container = document.getElementById("alchemy-signer-iframe-container");
   if (container) {
-    container.innerHTML = "";
+    // More explicit cleanup of child nodes
+    while (container.firstChild) {
+      container.removeChild(container.firstChild);
+    }
   }
   
-  // Remove all existing Turnkey iframes (there might be multiple)
-  const existingIframes = document.querySelectorAll('[id*="turnkey-iframe"], iframe[id="turnkey-iframe"]');
-  existingIframes.forEach(iframe => {
-    try {
-      iframe.remove();
-    } catch (error) {
-      console.warn("Failed to remove existing iframe:", error);
-    }
-  });
-  
-  // Also check for any iframes with Turnkey-related IDs
-  const turnkeyIframes = document.querySelectorAll('iframe[id*="turnkey"], iframe[src*="turnkey"]');
-  turnkeyIframes.forEach(iframe => {
+  // Remove all Turnkey-related iframes in one query
+  const turnkeyIframes = document.querySelectorAll(
+    'iframe[id*="turnkey"], iframe[src*="turnkey"]'
+  );
+  turnkeyIframes.forEach(iframe => {
     try {
       iframe.remove();
     } catch (error) {
       console.warn("Failed to remove Turnkey iframe:", error);
     }
   });
 };

This consolidates the iframe queries and uses a more explicit cleanup method for the container.


49-65: Consider if beforeunload listener is necessary.

The component cleans up iframes on mount, pathname change, and beforeunload. For SPA navigation in Next.js, the pathname-based cleanup (lines 44-47) should be sufficient. The beforeunload event handler is useful for page reloads/closes but may be redundant for client-side navigation.

If the pathname-based cleanup is sufficient, you could simplify to:

 useEffect(() => {
   // Clean up on component mount
   cleanupExistingIframes();
-  
-  // Clean up before page unload
-  const handleBeforeUnload = () => {
-    cleanupExistingIframes();
-  };
-
-  window.addEventListener('beforeunload', handleBeforeUnload);
   
   return () => {
-    window.removeEventListener('beforeunload', handleBeforeUnload);
     // Clean up on component unmount
     cleanupExistingIframes();
   };
 }, []);

However, keeping the beforeunload handler provides defense-in-depth for browser refresh scenarios.

components/ui/pagination.tsx (1)

26-51: Consider adding aria attributes for better accessibility.

The pagination buttons have good visual affordance with icons and text, and proper disabled states. However, adding ARIA attributes would improve screen reader experience.

 <Button
   onClick={onPrevPage}
   disabled={!hasPrevPage || isLoading}
+  aria-label={isLoading ? "Loading previous page" : "Go to previous page"}
+  aria-disabled={!hasPrevPage || isLoading}
   variant="outline"
   size="sm"
   className="flex items-center gap-2"
 >
   <ChevronLeft className="h-4 w-4" />
   Previous
 </Button>

 {/* ... */}

 <Button
   onClick={onNextPage}
   disabled={!hasNextPage || isLoading}
+  aria-label={isLoading ? "Loading next page" : "Go to next page"}
+  aria-disabled={!hasNextPage || isLoading}
   variant="outline"
   size="sm"
   className="flex items-center gap-2"
 >
   Next
   <ChevronRight className="h-4 w-4" />
 </Button>

This helps screen reader users understand the current state and available actions.

app/api/livepeer/playback-info/route.ts (1)

4-34: Consider adding input validation for playbackId.

The implementation is correct and follows standard API route patterns. However, adding validation for the playbackId format (e.g., checking if it matches expected patterns) could prevent unnecessary calls to Livepeer's API.

Example validation:

     if (!playbackId) {
       return NextResponse.json(
         { error: 'Playback ID is required' },
         { status: 400 }
       );
     }
+
+    // Validate playbackId format if known pattern exists
+    if (!/^[a-zA-Z0-9_-]+$/.test(playbackId)) {
+      return NextResponse.json(
+        { error: 'Invalid playback ID format' },
+        { status: 400 }
+      );
+    }
app/discover/page.tsx (1)

5-5: Remove unused import.

OrbisVideoCardGrid is imported but only used in commented-out code (lines 94-97).

Apply this diff:

 import VideoCardGrid from "@/components/Videos/VideoCardGrid";
 import LivestreamGrid from "@/components/Live/LivestreamGrid";
-import OrbisVideoCardGrid from "@/components/Live/LivestreamGrid";
VERIFICATION_GUIDE.md (1)

147-183: Add fence languages for markdownlint compliance.

The code sections under “Current Configuration” and “Before Fix/After Fix” are missing language identifiers, triggering MD040. Please add something like ```text so the markdown linter passes.

app/api/metokens/route.ts (1)

42-59: Consider more robust error classification beyond string matching.

The current approach checks error.message.includes('Failed to fetch MeToken') to distinguish 404 from 500. This is fragile because it depends on exact error message wording from the service layer. For maintainability, consider having meTokenSupabaseService methods throw typed errors (e.g., NotFoundError, DatabaseError) so you can use instanceof checks instead of string matching.

components/Videos/VideoDetails.tsx (1)

64-67: Avoid type assertion by narrowing the type properly.

The as any assertion at line 66 bypasses TypeScript's type safety. Use a type guard instead to safely narrow the type.

Apply this diff:

       const row = await fetchVideoAssetByPlaybackId(asset.playbackId);
       if (row?.status) {
         const validStatuses = ["draft", "published", "minted", "archived"] as const;
-        if (validStatuses.includes(row.status as any)) {
+        type ValidStatus = (typeof validStatuses)[number];
+        const isValidStatus = (status: string): status is ValidStatus =>
+          (validStatuses as readonly string[]).includes(status);
+        
+        if (isValidStatus(row.status)) {
-          setDbStatus(row.status as "draft" | "published" | "minted" | "archived");
+          setDbStatus(row.status);
         }
       }
components/wallet/buy/dai-fund-button.tsx (2)

86-89: Consider verifying transaction completion instead of using an arbitrary delay.

The 2-second delay at line 87 is arbitrary and may not be sufficient for transaction processing. Consider polling the user's DAI balance or transaction status instead of relying on a fixed timeout.

Example approach:

// Instead of fixed delay
setTimeout(() => {
  onSuccess?.();
}, 2000);

// Poll balance or transaction status
const pollCompletion = async () => {
  for (let i = 0; i < 10; i++) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    // Check balance or transaction status
    const balance = await checkDaiBalance(address);
    if (balance >= expectedAmount) {
      onSuccess?.();
      break;
    }
  }
};
pollCompletion();

45-54: Add timeout and abort controller for the fetch request.

The fetch call at line 45 has no timeout or abort controller, so it could hang indefinitely if the server doesn't respond. This is especially important for user-facing operations.

Apply this diff:

+      const controller = new AbortController();
+      const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout
+      
       const res = await fetch("/api/coinbase/session-token", {
         method: "POST",
         headers: { "Content-Type": "application/json" },
         body: JSON.stringify({ 
           address, 
           assets: ["DAI"],
           signature,
           message
         }),
+        signal: controller.signal,
       });
+      
+      clearTimeout(timeoutId);
TRANSCODING_TROUBLESHOOTING.md (1)

16-20: Add language identifier to the fenced code block.

The static analysis tool flagged that the fenced code block starting at line 16 should have a language specified for syntax highlighting.

Based on learnings

Apply this diff:

 **Check**:
-```
+```javascript
 // Console should show:
 "Start upload - using smart account address: 0x..."
 "Upload completed"
ERROR_FIXES_SUMMARY.md (1)

13-69: Add language identifiers to fenced code blocks.

Several code fences are missing a language hint, which trips markdownlint (MD040) and loses syntax highlighting. Please tag each block with the appropriate language (e.g., bash, env, typescript) for clarity and to satisfy the linter.

Also applies to: 86-110

TRANSCODING_ERROR_FIX.md (1)

7-56: Label fenced code blocks with a language.

Markdownlint (MD040) flags these unlabeled fences, and adding identifiers (e.g., text, typescript) improves readability. Please tag the code blocks throughout this doc.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
components/ui/select.tsx (1)

7-7: Remove unused import.

The optimism import is no longer used since supportedChains now only contains base (line 178).

Apply this diff to remove the unused import:

-import { base, optimism } from "@account-kit/infra";
+import { base } from "@account-kit/infra";

Note: If line 243 in getChainIcon needs to remain for backward compatibility (displaying the icon when users' wallets are still on Optimism), replace optimism.id with the numeric literal 10.

     case base.id:
       return "/images/chains/base.svg";
-    case optimism.id:
+    case 10: // Optimism chain ID
       return "/images/chains/optimism.svg";
app/api/livepeer/token-gate/route.ts (2)

66-78: Tighten timestamp validation to reject future timestamps.

The current implementation uses Math.abs(now - payload.timestamp) which allows timestamps up to 5 minutes in the future. This could be exploited by attackers who pre-generate access keys with future timestamps, potentially bypassing time-based access controls.

Apply this diff to reject future timestamps while allowing a small clock skew:

-    // Validate timestamp age < 5 minutes
-    const MAX_TIMESTAMP_AGE = 5 * 60 * 1000;
+    // Validate timestamp: not too old (<5 min) and not in future (allow 30s clock skew)
+    const MAX_TIMESTAMP_AGE = 5 * 60 * 1000;
+    const MAX_CLOCK_SKEW = 30 * 1000;
     const now = Date.now();
 
-    if (Math.abs(now - payload.timestamp) > MAX_TIMESTAMP_AGE) {
+    const age = now - payload.timestamp;
+    if (age > MAX_TIMESTAMP_AGE || age < -MAX_CLOCK_SKEW) {
       return NextResponse.json(
         {
           allowed: false,
-          message: "Request timestamp too old or from future",
+          message: age > MAX_TIMESTAMP_AGE 
+            ? "Request timestamp too old" 
+            : "Request timestamp is in the future",
         },
         { status: 400 }
       );

268-274: Implement asset accessibility checks before production use.

The checkAssetAccessibility function currently returns true for all assets, effectively bypassing this validation step. This is a security gap that allows access to potentially restricted or unpublished content.

The implementation should verify:

  • Asset publication status
  • Content restrictions or private flags
  • Creator-defined access rules
  • Asset existence in the database

Would you like me to generate an implementation that queries the asset metadata from your database/storage layer? Please provide the data source (e.g., Supabase table structure, API endpoint) and I can create the validation logic.

components/Navbar.tsx (1)

177-198: Stop re-creating the viem client each render.

createPublicClient runs on every render and the effect depends on publicClient, so any re-render (copy toast, menu toggle, theme switch, etc.) triggers a fresh ENS lookup. In practice this hammers Alchemy on every state change and will eventually rate-limit the UI. Hoist the client behind useMemo (or a module-level singleton) and keep the effect keyed only on the selected address.

-import { useState, useEffect, useRef } from "react";
+import { useState, useEffect, useRef, useMemo } from "react";
...
-  const publicClient = createPublicClient({
-    chain: mainnet,
-    transport: alchemy({
-      apiKey: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY as string,
-    }),
-  });
+  const publicClient = useMemo(
+    () =>
+      createPublicClient({
+        chain: mainnet,
+        transport: alchemy({
+          apiKey: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY as string,
+        }),
+      }),
+    []
+  );
♻️ Duplicate comments (4)
components/wallet/balance/TokenBalance.tsx (2)

112-117: Don't mask USDC read failures with "0" balances.

The USDC catch block logs the error and sets balance to null without updating the error state. This causes the UI to display "0" for USDC when the read actually failed, misinforming users. Surface the failure by calling setError so the error card renders instead of a misleading zero balance.

Apply this diff:

       } catch (error) {
         if (isMounted && !signal.aborted) {
           console.error("Error fetching USDC balance:", error);
-          setUsdcBalance(null);
+          setError((prev) => prev ?? "Unable to load USDC balance");
         }
       }

133-137: Don't mask DAI read failures with "0" balances.

The DAI catch block logs the error and sets balance to null without updating the error state. This causes the UI to display "0" for DAI when the read actually failed. Surface the failure by calling setError so the error card renders instead of a misleading zero balance.

Apply this diff:

       } catch (error) {
         if (isMounted && !signal.aborted) {
           console.error("Error fetching DAI balance:", error);
-          setDaiBalance(null);
+          setError((prev) => prev ?? "Unable to load DAI balance");
         }
       }
components/wallet/swap/AlchemySwapWidget.tsx (1)

467-486: Let sponsored swaps bypass the gas buffer check

We already skip the 0.001 ETH guard when feePayment?.sponsored is true, but this block still forces ETH swappers to hold an extra 0.001 ETH even when the paymaster is covering gas. That defeats the gasless flow and will stop sponsored ETH→token swaps for users who only hold the swap amount. Gate this branch behind !swapState.quote?.feePayment?.sponsored (or adjust the total needed calculation so the gas buffer is omitted when sponsored).
Raise priority so paymaster-backed swaps actually work without extra ETH.

-      if (swapState.fromToken === 'ETH') {
+      if (swapState.fromToken === 'ETH' && !swapState.quote?.feePayment?.sponsored) {
         const quoteData = swapState.quote.data;
         const swapAmount = BigInt(quoteData.value || '0x0');
         // Need swap amount + buffer for gas
         const totalNeeded = swapAmount + minGasEth;
components/account-dropdown/AccountDropdown.tsx (1)

175-553: Unblock multi-chain ERC-20 support before shipping.

TOKEN_INFO hardcodes the Base addresses, and both the balance reads (Lines 331‑344) and the send path (Lines 533‑552) reuse those addresses. The moment the user switches to Base Sepolia (or any other supported chain), every readContract/select/send call points at the Base deployment and the user operation reverts. Please resolve the token address from the active chain id, bail out with a toast when we lack coverage, and reuse that derived address everywhere (balances + transfers). The prior review already called this out; it still needs to be fixed.

-const TOKEN_INFO = {
-  USDC: { ... , address: USDC_TOKEN_ADDRESSES.base },
-  DAI:  { ... , address: DAI_TOKEN_ADDRESSES.base },
-} as const;
+const TOKEN_INFO = {
+  USDC: { ..., addresses: USDC_TOKEN_ADDRESSES },
+  DAI:  { ..., addresses: DAI_TOKEN_ADDRESSES },
+} as const;

Then derive:

const chainKey = chain?.id === base.id ? "base" : chain?.id === baseSepolia.id ? "baseSepolia" : undefined;
const tokenAddress = chainKey ? TOKEN_INFO[token].addresses[chainKey] : undefined;
if (!tokenAddress) {
  toast({ variant: "destructive", title: "Unsupported network", description: `No ${token} deployment for ${chain?.name}` });
  return;
}

Use tokenAddress for the balance fetches and the ERC‑20 transfer target so the modal works on every supported network.

🧹 Nitpick comments (9)
components/wallet/balance/TokenBalance.tsx (3)

12-17: Remove unused interface.

The TokenBalanceData interface is defined but never used in the component. The component uses individual state variables (usdcBalance, daiBalance, isLoading, error) instead of this interface.

Apply this diff to remove the unused interface:

-interface TokenBalanceData {
-  symbol: string;
-  balance: string;
-  isLoading: boolean;
-  error: string | null;
-}
-
 // Utility function to format balance with proper precision (without symbol)

99-138: Consider extracting token balance fetching logic.

The USDC and DAI balance fetching logic is nearly identical, differing only in the contract getter, state setter, and error message. This duplication makes the code harder to maintain.

Consider extracting a reusable helper function:

async function fetchTokenBalance(
  tokenType: 'usdc' | 'dai',
  chainKey: keyof typeof import("@/lib/contracts/USDCToken").USDC_TOKEN_ADDRESSES,
  address: `0x${string}`,
  publicClient: any,
  signal: AbortSignal
): Promise<bigint | null> {
  try {
    if (signal.aborted) return null;
    
    const contract = tokenType === 'usdc' 
      ? getUsdcTokenContract(chainKey)
      : getDaiTokenContract(chainKey);
      
    return (await publicClient.readContract({
      address: contract.address,
      abi: contract.abi,
      functionName: "balanceOf",
      args: [address],
    })) as bigint;
  } catch (error) {
    console.error(`Error fetching ${tokenType.toUpperCase()} balance:`, error);
    throw error;
  }
}

Then update the main logic:

try {
  const usdcBalance = await fetchTokenBalance('usdc', chainKey, address as `0x${string}`, publicClient, signal);
  if (isMounted && !signal.aborted) setUsdcBalance(usdcBalance);
} catch (error) {
  if (isMounted && !signal.aborted) {
    setError((prev) => prev ?? "Unable to load USDC balance");
  }
}

try {
  const daiBalance = await fetchTokenBalance('dai', chainKey, address as `0x${string}`, publicClient, signal);
  if (isMounted && !signal.aborted) setDaiBalance(daiBalance);
} catch (error) {
  if (isMounted && !signal.aborted) {
    setError((prev) => prev ?? "Unable to load DAI balance");
  }
}

170-195: Consider extracting token row component.

The token display rows for USDC and DAI are duplicated across loading and success states with nearly identical structure. Extracting a reusable component would reduce duplication and improve maintainability.

Consider creating a TokenRow component:

interface TokenRowProps {
  symbol: string;
  logoPath: string;
  value?: string;
  isLoading?: boolean;
}

function TokenRow({ symbol, logoPath, value, isLoading }: TokenRowProps) {
  return (
    <div className="flex items-center justify-between">
      <div className="flex items-center space-x-2">
        <Image
          src={logoPath}
          alt={symbol}
          width={16}
          height={16}
          className="w-4 h-4"
        />
        <span className="text-sm">{symbol}</span>
      </div>
      {isLoading ? (
        <Skeleton className="h-5 w-16" />
      ) : (
        <span className="text-sm font-medium">{value}</span>
      )}
    </div>
  );
}

Then simplify the component usage:

<CardContent className="space-y-2">
  <TokenRow
    symbol="USDC"
    logoPath="/images/tokens/usdc-logo.svg"
    value={usdcBalance ? formatBalance(formatUnits(usdcBalance, 6)) : "0"}
    isLoading={isLoading}
  />
  <TokenRow
    symbol="DAI"
    logoPath="/images/tokens/dai-logo.svg"
    value={daiBalance ? formatBalance(formatUnits(daiBalance, 18)) : "0"}
    isLoading={isLoading}
  />
</CardContent>

Also applies to: 222-255

components/ui/select.tsx (1)

239-248: Consider removing unreachable Optimism case or adding explanatory comment.

The optimism.id case (lines 243-244) is no longer reachable through the supportedChains dropdown since Optimism was removed. However, this case might still serve a purpose if:

  1. Users' wallets could be on Optimism when they load the page (defensive coding)
  2. The chain state from useChain() could be Optimism before switching

If the Optimism case is intentionally kept for displaying users' current chain state, consider adding a comment explaining this. Otherwise, remove it to keep the code clean.

If the case should be removed:

 function getChainIcon(chainId: number): string {
   switch (chainId) {
     case base.id:
       return "/images/chains/base.svg";
-    case optimism.id:
-      return "/images/chains/optimism.svg";
     default:
       return "/images/chains/default-chain.svg";
   }
 }

If the case should be kept for backward compatibility, add a comment:

 function getChainIcon(chainId: number): string {
   switch (chainId) {
     case base.id:
       return "/images/chains/base.svg";
+    // Keep Optimism case for users whose wallets may still be on Optimism
     case optimism.id:
       return "/images/chains/optimism.svg";
     default:
       return "/images/chains/default-chain.svg";
   }
 }
components/UserProfile/MeTokensSection.tsx (2)

109-153: Improve address validation and use toast for user feedback.

The enhanced manual check has good improvements but could be further refined:

  1. Use toast instead of alert(): Lines 116-118, 140, 145, and 149 use alert() which blocks the UI. Replace with toast notifications for better UX.

  2. Strengthen address validation: Line 115 validates the format but doesn't verify that the characters after 0x are valid hexadecimal. Consider using a regex pattern or viem's isAddress() utility.

  3. Variable consistency: Line 135 calls checkSpecificMeToken(manualMeTokenAddress.trim()) but you've already stored the trimmed value in the address variable (line 112). Use address for consistency.

Example validation using viem:

import { isAddress } from 'viem';

// Replace lines 114-119 with:
if (!isAddress(address)) {
  // Show toast error instead of alert
  return;
}

For variable consistency:

-        const result = await checkSpecificMeToken(manualMeTokenAddress.trim());
+        const result = await checkSpecificMeToken(address);

305-307: Consider adding refresh logic to onProfileUpdated callback.

The CreatorProfileManager component receives an empty onProfileUpdated callback. If profile updates should trigger any UI refresh (e.g., updating displayed profile data elsewhere), consider adding appropriate logic here.

Example:

-<CreatorProfileManager targetAddress={walletAddress} onProfileUpdated={() => {}} />
+<CreatorProfileManager 
+  targetAddress={walletAddress} 
+  onProfileUpdated={handleRefresh} 
+/>
components/Videos/Upload/FileUpload.tsx (3)

79-98: Move constants outside the component.

The SUPPORTED_VIDEO_FORMATS and SUPPORTED_VIDEO_EXTENSIONS arrays are recreated on every render. Move them outside the component as top-level constants for better performance.

Apply this diff:

+// Livepeer supported video formats
+// Containers: MP4, MOV, MKV, WebM, FLV, TS
+// Video codecs: H.264, H.265 (HEVC), VP8, VP9, AV1
+const SUPPORTED_VIDEO_FORMATS = [
+  'video/mp4',
+  'video/quicktime', // .mov
+  'video/x-matroska', // .mkv
+  'video/webm',
+  'video/x-flv',
+  'video/mp2t', // .ts
+  'video/mpeg',
+];
+
+const SUPPORTED_VIDEO_EXTENSIONS = [
+  '.mp4',
+  '.mov',
+  '.mkv',
+  '.webm',
+  '.flv',
+  '.ts',
+  '.mpeg',
+  '.mpg',
+];
+
 const FileUpload: React.FC<FileUploadProps> = ({
   onFileSelect,
   onFileUploaded,
   onPressNext,
   onPressBack,
   metadata,
   newAssetTitle,
 }) => {
   // ... component code
-  // Livepeer supported video formats
-  // Containers: MP4, MOV, MKV, WebM, FLV, TS
-  // Video codecs: H.264, H.265 (HEVC), VP8, VP9, AV1
-  const SUPPORTED_VIDEO_FORMATS = [
-    'video/mp4',
-    'video/quicktime', // .mov
-    'video/x-matroska', // .mkv
-    'video/webm',
-    'video/x-flv',
-    'video/mp2t', // .ts
-    'video/mpeg',
-  ];
-
-  const SUPPORTED_VIDEO_EXTENSIONS = [
-    '.mp4',
-    '.mov',
-    '.mkv',
-    '.webm',
-    '.flv',
-    '.ts',
-    '.mpeg',
-    '.mpg',
-  ];

127-155: Consider removing production console logs.

Line 154 logs the selected file name, address, and account type. While useful for debugging, this could expose sensitive information in production builds.

Consider wrapping with a development check:

   setSelectedFile(file);
   onFileSelect(file);
-  console.log("Selected file:", file?.name, "Address:", address, "Type:", type);
+  if (process.env.NODE_ENV === 'development') {
+    console.log("Selected file:", file?.name, "Address:", address, "Type:", type);
+  }
 };

205-212: Improve metadata polling error handling.

The async IIFE for metadata polling is fire-and-forget (using void). If polling fails, the error is only logged to the console and the user won't see the IPFS URI. Consider showing a toast notification to inform the user that metadata is still processing or if it failed.

           void (async () => {
             try {
               const metadataUri = await pollForMetadataUri(uploadRequestResult.asset.id);
               setUploadedUri(metadataUri);
+              toast.success("IPFS metadata URI generated!");
             } catch (pollErr) {
               console.warn("Failed to resolve metadata URI:", pollErr);
+              toast.warning("Upload succeeded but IPFS metadata is still processing. Check back later.");
             }
           })();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aed79d7 and 69e9077.

📒 Files selected for processing (21)
  • ALCHEMY_METOKEN_IMPLEMENTATION.md (1 hunks)
  • SUPABASE_VIDEO_ARCHITECTURE.md (1 hunks)
  • VIEW_SYNC_SETUP.md (1 hunks)
  • app/api/coinbase/session-token/route.ts (0 hunks)
  • app/api/creator-profiles/fix-address/route.ts (1 hunks)
  • app/api/livepeer/token-gate/route.ts (2 hunks)
  • app/api/video-assets/published/route.ts (1 hunks)
  • app/api/video-assets/sync-views/[playbackId]/route.ts (1 hunks)
  • app/api/video-assets/sync-views/cron/route.ts (1 hunks)
  • app/discover/page.tsx (3 hunks)
  • components/Navbar.tsx (16 hunks)
  • components/UserProfile/MeTokensSection.tsx (7 hunks)
  • components/Videos/Upload/FileUpload.tsx (11 hunks)
  • components/Videos/VideoCard.tsx (7 hunks)
  • components/Videos/VideoCardGrid.tsx (1 hunks)
  • components/Videos/VideoSearch.tsx (1 hunks)
  • components/account-dropdown/AccountDropdown.tsx (14 hunks)
  • components/ui/select.tsx (1 hunks)
  • components/ui/token-select.tsx (1 hunks)
  • components/wallet/balance/TokenBalance.tsx (5 hunks)
  • components/wallet/swap/AlchemySwapWidget.tsx (1 hunks)
💤 Files with no reviewable changes (1)
  • app/api/coinbase/session-token/route.ts
✅ Files skipped from review due to trivial changes (1)
  • ALCHEMY_METOKEN_IMPLEMENTATION.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/api/creator-profiles/fix-address/route.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/blockchain-infra.mdc)

**/*.{ts,tsx,js,jsx}: Use only Viem for blockchain interactions (avoid alternative libraries like ethers.js or web3.js)
Use only Account Kit for account-abstraction-related functionality
Leverage Account Abstraction SDKs: @aa-sdk/core, @account-kit/infra, and @account-kit/react for smart account integration, deployment, and usage
Choose the appropriate smart account type; prefer ModularAccountV2 unless project needs dictate LightAccount, MultiOwnerLightAccount, or MultiOwnerModularAccount; note switching types deploys a different account and may require upgrades
Handle bundler and RPC traffic separately by using split transport when calling createSmartAccountClient
Configure gas and fee estimation when creating SmartAccountClient; provide custom gasEstimator and feeEstimator as needed or use alchemyFeeEstimator where appropriate
Manage paymasters for gas sponsorship via paymasterAndData in SmartAccountClient configuration
Implement gas abstraction via Gas Manager API by creating a gas policy and linking its policyId in client configuration
Integrate third-party paymasters correctly; provide custom gasEstimator and paymasterAndData as required; use erc7677Middleware for ERC-7677 compliant paymasters
Utilize viem primitives: createClient for building clients, getContract for contract interactions, and actions for blockchain operations
Understand and correctly handle UserOperationRequest: send, wait for mining, handle failures, and resubmit using drop-and-replace when necessary

Files:

  • components/ui/select.tsx
  • components/Videos/VideoCard.tsx
  • app/api/livepeer/token-gate/route.ts
  • app/api/video-assets/published/route.ts
  • components/UserProfile/MeTokensSection.tsx
  • components/Videos/Upload/FileUpload.tsx
  • app/api/video-assets/sync-views/[playbackId]/route.ts
  • app/discover/page.tsx
  • components/Videos/VideoCardGrid.tsx
  • components/account-dropdown/AccountDropdown.tsx
  • components/wallet/balance/TokenBalance.tsx
  • components/wallet/swap/AlchemySwapWidget.tsx
  • app/api/video-assets/sync-views/cron/route.ts
  • components/Navbar.tsx
  • components/Videos/VideoSearch.tsx
  • components/ui/token-select.tsx
🧠 Learnings (3)
📚 Learning: 2024-12-04T05:02:55.124Z
Learnt from: liberalterian
PR: creativeplatform/crtv3#80
File: package.json:19-20
Timestamp: 2024-12-04T05:02:55.124Z
Learning: The use of `kreisler/js-google-translate-free` in `FileUpload.tsx` is a temporary measure and will be replaced with the new `/api/livepeer/translation/route.ts` endpoint.

Applied to files:

  • components/Videos/Upload/FileUpload.tsx
📚 Learning: 2025-09-20T04:39:46.689Z
Learnt from: CR
PR: creativeplatform/crtv3#0
File: .cursor/rules/blockchain-infra.mdc:0-0
Timestamp: 2025-09-20T04:39:46.689Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use only Account Kit for account-abstraction-related functionality

Applied to files:

  • components/Navbar.tsx
📚 Learning: 2025-09-20T04:39:46.689Z
Learnt from: CR
PR: creativeplatform/crtv3#0
File: .cursor/rules/blockchain-infra.mdc:0-0
Timestamp: 2025-09-20T04:39:46.689Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Leverage Account Abstraction SDKs: aa-sdk/core, account-kit/infra, and account-kit/react for smart account integration, deployment, and usage

Applied to files:

  • components/Navbar.tsx
🧬 Code graph analysis (14)
components/Videos/VideoCard.tsx (2)
lib/utils/video-assets-client.ts (1)
  • fetchVideoAssetByPlaybackId (17-49)
app/api/livepeer/views.ts (1)
  • fetchAllViews (3-44)
app/api/video-assets/published/route.ts (1)
services/video-assets.ts (1)
  • getPublishedVideoAssets (213-260)
components/UserProfile/MeTokensSection.tsx (3)
lib/hooks/metokens/useMeTokensSupabase.ts (6)
  • useMeTokensSupabase (106-1054)
  • useMeTokensSupabase (342-791)
  • MeTokenInfo (314-324)
  • MeTokenData (326-340)
  • supabaseMeToken (357-398)
  • meTokenAddress (736-753)
lib/sdk/metokens/subgraph.ts (1)
  • meTokensSubgraph (363-363)
components/UserProfile/CreatorProfileManager.tsx (1)
  • CreatorProfileManager (23-265)
components/Videos/Upload/FileUpload.tsx (2)
app/api/livepeer/assetUploadActions.ts (1)
  • getLivepeerUploadUrl (7-65)
components/Videos/Upload/index.tsx (2)
  • data (157-323)
  • livepeerAsset (119-150)
app/api/video-assets/sync-views/[playbackId]/route.ts (2)
app/api/livepeer/views.ts (1)
  • fetchAllViews (3-44)
lib/sdk/supabase/server.ts (1)
  • createClient (4-28)
app/discover/page.tsx (2)
components/Videos/VideoSearch.tsx (1)
  • VideoSearch (50-161)
components/account-dropdown/MembershipSection.tsx (2)
  • MembershipSection (38-141)
  • MembershipDetails (111-111)
components/Videos/VideoCardGrid.tsx (3)
lib/types/video-asset.ts (1)
  • VideoAsset (4-36)
lib/utils/published-videos-client.ts (1)
  • fetchPublishedVideos (25-57)
components/ui/pagination.tsx (1)
  • Pagination (15-54)
components/account-dropdown/AccountDropdown.tsx (5)
lib/sdk/alchemy/swap-service.ts (2)
  • TokenSymbol (10-10)
  • TOKEN_INFO (13-17)
lib/contracts/USDCToken.ts (2)
  • USDC_TOKEN_DECIMALS (9-9)
  • USDC_TOKEN_ADDRESSES (3-6)
lib/contracts/DAIToken.ts (2)
  • DAI_TOKEN_DECIMALS (9-9)
  • DAI_TOKEN_ADDRESSES (3-6)
components/wallet/swap/AlchemySwapWidget.tsx (1)
  • AlchemySwapWidget (34-1007)
components/wallet/balance/MeTokenBalances.tsx (1)
  • MeTokenBalances (60-258)
components/wallet/balance/TokenBalance.tsx (2)
lib/contracts/USDCToken.ts (1)
  • getUsdcTokenContract (11-18)
lib/contracts/DAIToken.ts (1)
  • getDaiTokenContract (11-18)
components/wallet/swap/AlchemySwapWidget.tsx (2)
lib/sdk/alchemy/swap-service.ts (4)
  • TokenSymbol (10-10)
  • BASE_TOKENS (4-8)
  • AlchemySwapService (107-365)
  • alchemySwapService (368-371)
lib/sdk/alchemy/price-service.ts (2)
  • priceService (226-226)
  • PriceService (14-223)
app/api/video-assets/sync-views/cron/route.ts (2)
lib/sdk/supabase/service.ts (1)
  • createServiceClient (14-32)
app/api/livepeer/views.ts (1)
  • fetchAllViews (3-44)
components/Navbar.tsx (1)
components/ui/token-select.tsx (1)
  • TokenSelect (25-120)
components/Videos/VideoSearch.tsx (1)
lib/hooks/useDebounce.ts (1)
  • useDebounce (9-25)
SUPABASE_VIDEO_ARCHITECTURE.md (4)
app/discover/[id]/page.tsx (2)
  • VideoDetailsPage (56-108)
  • id (24-54)
components/Videos/Upload/index.tsx (3)
  • data (157-323)
  • livepeerAsset (119-150)
  • dbAsset (146-149)
components/Videos/VideoDetails.tsx (3)
  • fetchPlaybackSources (55-85)
  • asset (60-66)
  • VideoDetails (47-506)
services/video-assets.ts (1)
  • updateVideoAsset (137-170)
🪛 Gitleaks (8.28.0)
VIEW_SYNC_SETUP.md

[high] 122-123: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.

(curl-auth-header)

🪛 markdownlint-cli2 (0.18.1)
SUPABASE_VIDEO_ARCHITECTURE.md

13-13: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


27-27: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


69-69: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


317-317: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (13)
components/ui/select.tsx (1)

176-222: Confirm Base-only chain selector impact

  • Only occurrences of Optimism are in components/ui/select.tsx: the import and icon mapping in getChainIcon; no other code references Optimism.
  • Selector now restricts to Base; users on Optimism (or other chains) will see their chain’s icon/name but won’t be able to switch.
  • Ensure any user-facing messaging or documentation is updated to reflect Base-only support.
app/api/livepeer/token-gate/route.ts (2)

185-227: LGTM!

The access validation logic is well-structured with proper error handling. The function correctly:

  • Validates the access key
  • Checks token balances
  • Verifies asset accessibility
  • Returns false on any error (fail-closed security model)

5-5: Verify Base-only restriction for token-gate API
No chain ID 10 (Optimism) references found in the token-gate context; confirm no clients invoke this route on Optimism and update docs/UI to reflect Base-only support.

components/UserProfile/MeTokensSection.tsx (4)

55-60: LGTM! Data refresh without page reload.

The new handleRefresh function correctly invokes checkUserMeToken to refresh MeToken data without requiring a full page reload, improving user experience.


216-237: LGTM! Clear explanation of address ownership.

The new alert provides helpful context about the difference between Smart Account and Controller addresses, improving user understanding of MeToken ownership. The conditional rendering ensures it only shows when relevant.


293-297: LGTM! Safe price calculation.

The ternary operator correctly handles the edge case where totalSupply is zero, preventing division by zero and displaying a meaningful fallback (-).


375-458: LGTM! Comprehensive user guidance.

The enhanced UI sections provide excellent user guidance:

  • Clear instructions for finding the MeToken address from Basescan
  • Auto-sync option for convenience
  • Helpful error recovery guidance for common "Already Owns MeToken" issues

These additions significantly improve the user experience and reduce potential confusion.

components/Videos/Upload/FileUpload.tsx (6)

73-74: Good use of Universal Account for smart account address.

The integration correctly uses the smart account address (SCA) for upload operations rather than the controller wallet address. This aligns with Account Abstraction best practices.


163-166: Good wallet connection check before upload.

Properly gates the upload action behind a connected wallet, providing clear user feedback.


248-263: Clear loading and connection states.

The UI properly handles both the loading state while fetching the universal account and the unconnected state, providing clear user feedback in both cases.


292-302: Excellent user guidance for video formats.

The added informative messages clearly explain supported formats, required codecs, and even provide a helpful link to HandBrake for conversion. This will significantly reduce user confusion and support requests.


324-337: Good touch and mobile optimizations.

The buttons properly handle touch interactions with WebkitTapHighlightColor: 'transparent' and include responsive sizing. The disabled states and visual feedback are well implemented.

Also applies to: 400-421


28-35: Confirm removal of onSubtitlesUploaded No code references remain; all consumers are updated accordingly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/Player/Subtitles.tsx (1)

98-137: Add null check before accessing subtitles.

The component calls Object.keys(subtitles as Subtitles) (line 122) without first checking if subtitles is defined. Since the context type allows subtitles to be undefined, this will throw a runtime error when the component renders before subtitles are loaded.

Note: SubtitlesDisplay (line 159) correctly handles this with optional chaining: subtitles?.[language].

Apply this diff to add a null check:

 export function SubtitlesLanguageSelect() {
   const { subtitles, language, setLanguage } = useSubtitles();
 
+  if (!subtitles) {
+    return null;
+  }
+
   return (
     <div className="flex flex-col gap-2">
       <label
         className="text-xs font-medium text-white/90"
         htmlFor="languageSelect"
       >
         Subtitles Language
       </label>
       <Select onValueChange={(value) => setLanguage(value)}>
         <FormControl>
           <SelectTrigger
             className={`inline-flex h-7 items-center justify-between gap-1 rounded-sm 
             bg-gray-400 px-1 text-xs leading-none outline-none outline-1 outline-transparent/50`}
           >
             <SelectValue
               className="bg-gray-400 px-1 text-xs leading-none outline-none hover:bg-gray-300 active:bg-gray-300"
               placeholder="English"
             />
           </SelectTrigger>
         </FormControl>
         <SelectContent className="overflow-hidden rounded-sm bg-gray-400 outline-none">
-          {Object.keys(subtitles as Subtitles).map((language, i) => {
+          {Object.keys(subtitles).map((language, i) => {
             return (
               <SelectItem
                 className="bg-gray-400 px-1 text-xs leading-none outline-none hover:bg-gray-300 active:bg-gray-300"
                 value={language}
                 key={i}
               >
                 {language}
               </SelectItem>
             );
           })}
         </SelectContent>
       </Select>
     </div>
   );
 }
♻️ Duplicate comments (6)
components/home-page/NonLoggedInView.tsx (1)

8-12: Duplicate hook invocation (see parent component).

This hook is also invoked in the parent Home component (line 8 of app/page.tsx). See the review comment on that file for details on the duplicate invocation issue and the recommended fix.

components/Videos/Upload/index.tsx (1)

178-186: Guard against missing videoAsset.id before publishing

videoAsset?.id as number collapses to undefined when the state hasn’t hydrated yet (e.g., DB insert failed), so updateVideoAsset(undefined, …) will throw. Bail out early when the ID is missing instead of force-casting.

-              await updateVideoAsset(videoAsset?.id as number, {
+              if (!videoAsset?.id) {
+                toast.error(
+                  "We couldn’t find the saved video record. Please retry the upload."
+                );
+                return;
+              }
+
+              await updateVideoAsset(videoAsset.id, {
components/Videos/Upload/FileUpload.tsx (2)

198-201: Don’t fall back to video/mp4 metadata

If selectedFile.type is empty, we end up labeling every upload as video/mp4, which misidentifies valid non-MP4 files and defeats the stricter validation above. Rely on the validated MIME type instead of hardcoding a fallback.

-          filetype: selectedFile.type || "video/mp4", // Use actual file MIME type
+          filetype: selectedFile.type,

103-128: Require both MIME type and extension to pass validation

Line 103 validation still only fails when both checks fail, so a spoofed file with a valid extension but wrong/no MIME type slips through. Reject any file missing a MIME type and require both checks to succeed (log the mismatch before returning an error) so renamed/malicious uploads can’t bypass the client gate.

-    const isValidMimeType = SUPPORTED_VIDEO_FORMATS.includes(file.type);
-    
-    if (!isValidExtension && !isValidMimeType) {
+    if (!file.type) {
+      return {
+        valid: false,
+        error:
+          "Unable to determine file type. Please ensure your file is a valid video format.",
+      };
+    }
+
+    const isValidMimeType = SUPPORTED_VIDEO_FORMATS.includes(file.type);
+
+    if (!isValidExtension || !isValidMimeType) {
+      console.warn(
+        "Extension/MIME mismatch",
+        fileExtension,
+        file.type
+      );
       return {
         valid: false,
         error: `Unsupported video format: ${fileExtension}. Please use MP4, MOV, MKV, WebM, FLV, or TS format with H.264/H.265 codec.`,
       };
     }
components/account-dropdown/AccountDropdown.tsx (2)

172-191: Fix ERC-20 addresses to respect the active chain (repeat of prior blocker).

Using Base addresses in TOKEN_INFO breaks balances/transfers on other networks. Store per-chain addresses and resolve by current chain before any readContract/sendUserOperation. Also avoid duplicating TokenSymbol; prefer importing from a single source for consistency.

Apply:

-type TokenSymbol = 'ETH' | 'USDC' | 'DAI';
-
-const TOKEN_INFO = {
-  ETH: { 
-    decimals: 18, 
-    symbol: "ETH",
-    address: null, // Native token
-  },
-  USDC: { 
-    decimals: USDC_TOKEN_DECIMALS, 
-    symbol: "USDC",
-    address: USDC_TOKEN_ADDRESSES.base,
-  },
-  DAI: { 
-    decimals: DAI_TOKEN_DECIMALS, 
-    symbol: "DAI",
-    address: DAI_TOKEN_ADDRESSES.base,
-  },
-} as const;
+type TokenSymbol = 'ETH' | 'USDC' | 'DAI'; // consider importing this from lib/sdk/alchemy/swap-service
+
+const TOKEN_INFO = {
+  ETH: {
+    decimals: 18,
+    symbol: "ETH",
+  },
+  USDC: {
+    decimals: USDC_TOKEN_DECIMALS,
+    symbol: "USDC",
+    addresses: USDC_TOKEN_ADDRESSES,
+  },
+  DAI: {
+    decimals: DAI_TOKEN_DECIMALS,
+    symbol: "DAI",
+    addresses: DAI_TOKEN_ADDRESSES,
+  },
+} as const;

To find other hardcoded usages across the repo:

#!/bin/bash
# Scan for hardcoded Base addresses to fix similar issues.
rg -n -C2 -S "USDC_TOKEN_ADDRESSES\.base|DAI_TOKEN_ADDRESSES\.base|address:\s*USDC_TOKEN_ADDRESSES\.base|address:\s*DAI_TOKEN_ADDRESSES\.base|TOKEN_INFO.*address:"

529-553: Resolve ERC-20 target address by chain before sending.

Sending to Base addresses on other networks will revert. Gate unsupported networks.

-      } else {
-        // Send ERC-20 token (USDC or DAI)
-        const tokenAmount = parseUnits(sendAmount, tokenInfo.decimals);
-        
-        // Encode the transfer calldata
-        const transferCalldata = encodeFunctionData({
-          abi: parseAbi(["function transfer(address,uint256) returns (bool)"]),
-          functionName: "transfer",
-          args: [recipientAddress as Address, tokenAmount],
-        });
-
-        console.log('Sending ERC-20 transfer:', {
-          token: selectedToken,
-          tokenAddress: tokenInfo.address,
-          recipient: recipientAddress,
-          amount: tokenAmount.toString(),
-        });
-
-        operation = await client!.sendUserOperation({
-          uo: {
-            target: tokenInfo.address as Address,
-            data: transferCalldata as Hex,
-            value: BigInt(0), // No native value for ERC-20 transfers
-          },
-        });
-      }
+      } else {
+        // Send ERC-20 token (USDC or DAI)
+        const tokenAmount = parseUnits(sendAmount, tokenInfo.decimals);
+        const chainKey = chain?.id === base.id ? "base" : undefined;
+        const tokenAddress = chainKey
+          ? (TOKEN_INFO[selectedToken] as any).addresses?.[chainKey] as Address | undefined
+          : undefined;
+        if (!tokenAddress) {
+          toast({
+            variant: "destructive",
+            title: "Unsupported network",
+            description: `${selectedToken} is not configured for ${chain?.name}.`,
+          });
+          return;
+        }
+        const transferCalldata = encodeFunctionData({
+          abi: parseAbi(["function transfer(address,uint256) returns (bool)"]),
+          functionName: "transfer",
+          args: [recipientAddress as Address, tokenAmount],
+        });
+        operation = await client!.sendUserOperation({
+          uo: {
+            target: tokenAddress,
+            data: transferCalldata as Hex,
+            value: 0n,
+          },
+        });
+      }
🧹 Nitpick comments (10)
components/auth/LoginButton.tsx (1)

53-61: Remove debug logging before production.

The simplified synchronous approach with error handling via toast is appropriate. However, the console.log on line 54 should be removed or wrapped in a development-only conditional check to avoid cluttering production logs.

Apply this diff to remove the debug statement:

  const handleLogin = () => {
-   console.log('Opening auth modal...', { hasUser: !!user });
    try {
      openAuthModal();
    } catch (error) {

Alternatively, if you want to keep it for debugging, wrap it in a development check:

  const handleLogin = () => {
-   console.log('Opening auth modal...', { hasUser: !!user });
+   if (process.env.NODE_ENV === 'development') {
+     console.log('Opening auth modal...', { hasUser: !!user });
+   }
    try {
      openAuthModal();
components/Player/Subtitles.tsx (1)

95-96: Remove unused type definition.

The LanguageSelectProps type is declared but never used in this file or exported for external use.

Apply this diff to remove the unused type:

-export type LanguageSelectProps = {
-  subtitles: Record<string, Chunk>;
-};
-
 export function SubtitlesLanguageSelect() {
app/discover/page.tsx (2)

82-88: Move hardcoded membership URL to a constant.

The membership URL "https://join.creativeplatform.xyz" is hardcoded. For maintainability and consistency across the codebase, this should be defined in a constants file.

Create a constants file (e.g., lib/constants/urls.ts):

export const MEMBERSHIP_URL = "https://join.creativeplatform.xyz";

Then apply this diff:

+import { MEMBERSHIP_URL } from "@/lib/constants/urls";
+
 // ...
            <Link
-             href="https://join.creativeplatform.xyz"
+             href={MEMBERSHIP_URL}
              target="_blank"

This same constant can be reused in other components like MembershipSection.tsx.


114-119: Remove commented-out code.

The commented-out livestream section is no longer needed since the functionality has been integrated into the membership-gated "Live Now" section above. Keeping dead code in comments clutters the codebase and can cause confusion.

Apply this diff to remove the commented code:

-      {/* Livestream Section - Commented out */}
-      {/* <div>
-        <h2 className="my-4 text-2xl font-bold">All Videos</h2>
-        <LivestreamGrid />
-      </div> */}
     </div>
components/Player/TrendingPlayer.tsx (2)

24-37: Centralize AssetMetadata and wire subtitles into provider

Defining AssetMetadata inside a component risks duplication/divergence. Move it next to Subtitles (e.g., lib/types/video-asset) and import it here. Also, since you already wrap in SubtitlesProvider, set subtitles from assetMetadata to keep behavior consistent with VideoDetails.

Apply locally in this file:

-import { Subtitles } from "@/lib/types/video-asset";
+import { Subtitles, AssetMetadata } from "@/lib/types/video-asset";
@@
-export interface AssetMetadata {
-  assetId: string;
-  playbackId: string;
-  title: string;
-  description?: string;
-  location?: string;
-  category?: string;
-  thumbnailUri?: string;
-  subtitlesUri?: string;
-  subtitles?: Subtitles;
-}

Add this effect to initialize subtitles (outside the selected range):

// inside TrendingPlayer component body
const { setSubtitles } = useSubtitles();
useEffect(() => {
  if (assetMetadata?.subtitles) setSubtitles(assetMetadata.subtitles);
}, [assetMetadata?.subtitles, setSubtitles]);

And define/export AssetMetadata alongside Subtitles in lib/types/video-asset:

export interface AssetMetadata {
  assetId: string;
  playbackId: string;
  title: string;
  description?: string;
  location?: string;
  category?: string;
  thumbnailUri?: string;
  subtitlesUri?: string;
  subtitles?: Subtitles;
}

Based on relevant code snippets.


21-21: Nit: fix timer ref type for browser

Using NodeJS.Timeout in a client component can mis-type setTimeout. Prefer ReturnType.

Example (outside this range):

const fadeTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
components/account-dropdown/AccountDropdown.tsx (4)

99-99: Import viem’s isAddress for safe recipient validation.

Needed for the validation below.

-import { parseEther, type Address, type Hex, encodeFunctionData, parseAbi, parseUnits, formatUnits, erc20Abi } from "viem";
+import { parseEther, type Address, type Hex, encodeFunctionData, parseAbi, parseUnits, formatUnits, erc20Abi, isAddress } from "viem";

493-505: Use bigint-safe amount checks and validate inputs.

Float comparisons can misfire on large/precise values; also validate address and amount.

-    // Check balance
-    const availableBalance = parseFloat(tokenBalances[selectedToken]);
-    const requestedAmount = parseFloat(sendAmount);
-    
-    if (requestedAmount > availableBalance) {
-      toast({
-        variant: "destructive",
-        title: "Insufficient Balance",
-        description: `You have ${availableBalance} ${selectedToken}, but trying to send ${requestedAmount} ${selectedToken}`,
-      });
-      return;
-    }
+    // Validate recipient and amount; bigint‑safe balance check
+    if (!isAddress(recipientAddress)) {
+      toast({ variant: "destructive", title: "Invalid address", description: "Enter a valid EVM address." });
+      return;
+    }
+    try {
+      const decimals = TOKEN_INFO[selectedToken].decimals;
+      const requested = parseUnits(sendAmount, decimals);
+      const available = parseUnits(tokenBalances[selectedToken] || "0", decimals);
+      if (requested === 0n) {
+        toast({ variant: "destructive", title: "Invalid amount", description: "Amount must be greater than 0." });
+        return;
+      }
+      if (requested > available) {
+        toast({
+          variant: "destructive",
+          title: "Insufficient Balance",
+          description: `You have ${tokenBalances[selectedToken]} ${selectedToken}, but tried to send ${sendAmount} ${selectedToken}`,
+        });
+        return;
+      }
+    } catch {
+      toast({ variant: "destructive", title: "Invalid amount", description: "Enter a valid numeric amount." });
+      return;
+    }

556-574: Guard explorer link for chains without block explorer config.

Avoid undefined URL; render action only if available.

-      const explorerUrl = `${chain.blockExplorers?.default.url}/tx/${txHash}`;
+      const explorerBase = chain?.blockExplorers?.default?.url;
+      const explorerUrl = explorerBase ? `${explorerBase}/tx/${txHash}` : undefined;
       console.log("Transaction hash:", txHash);
       toast({
         title: "Transaction Successful!",
         description: "Your transaction has been confirmed.",
-        action: (
-          <ToastAction
-            altText="View on Explorer"
-            onClick={() => window.open(explorerUrl, "_blank")}
-          >
-            View on Explorer
-          </ToastAction>
-        ),
+        action: explorerUrl ? (
+          <ToastAction altText="View on Explorer" onClick={() => window.open(explorerUrl!, "_blank")}>
+            View on Explorer
+          </ToastAction>
+        ) : undefined,
       });

710-719: Optional: ETH “MAX” should reserve gas if no paymaster.

If gas isn’t sponsored, sending full ETH may fail due to fees. Consider subtracting a small buffer (e.g., 0.0002 ETH) from MAX when selectedToken === 'ETH'.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 69e9077 and a550415.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (10)
  • app/discover/page.tsx (3 hunks)
  • app/page.tsx (1 hunks)
  • components/Player/Subtitles.tsx (1 hunks)
  • components/Player/TrendingPlayer.tsx (1 hunks)
  • components/Videos/Upload/FileUpload.tsx (11 hunks)
  • components/Videos/Upload/index.tsx (5 hunks)
  • components/account-dropdown/AccountDropdown.tsx (14 hunks)
  • components/auth/LoginButton.tsx (1 hunks)
  • components/home-page/NonLoggedInView.tsx (1 hunks)
  • components/home-page/TopVideos.tsx (6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/blockchain-infra.mdc)

**/*.{ts,tsx,js,jsx}: Use only Viem for blockchain interactions (avoid alternative libraries like ethers.js or web3.js)
Use only Account Kit for account-abstraction-related functionality
Leverage Account Abstraction SDKs: @aa-sdk/core, @account-kit/infra, and @account-kit/react for smart account integration, deployment, and usage
Choose the appropriate smart account type; prefer ModularAccountV2 unless project needs dictate LightAccount, MultiOwnerLightAccount, or MultiOwnerModularAccount; note switching types deploys a different account and may require upgrades
Handle bundler and RPC traffic separately by using split transport when calling createSmartAccountClient
Configure gas and fee estimation when creating SmartAccountClient; provide custom gasEstimator and feeEstimator as needed or use alchemyFeeEstimator where appropriate
Manage paymasters for gas sponsorship via paymasterAndData in SmartAccountClient configuration
Implement gas abstraction via Gas Manager API by creating a gas policy and linking its policyId in client configuration
Integrate third-party paymasters correctly; provide custom gasEstimator and paymasterAndData as required; use erc7677Middleware for ERC-7677 compliant paymasters
Utilize viem primitives: createClient for building clients, getContract for contract interactions, and actions for blockchain operations
Understand and correctly handle UserOperationRequest: send, wait for mining, handle failures, and resubmit using drop-and-replace when necessary

Files:

  • app/page.tsx
  • components/Player/TrendingPlayer.tsx
  • components/account-dropdown/AccountDropdown.tsx
  • components/auth/LoginButton.tsx
  • app/discover/page.tsx
  • components/home-page/TopVideos.tsx
  • components/Player/Subtitles.tsx
  • components/home-page/NonLoggedInView.tsx
  • components/Videos/Upload/index.tsx
  • components/Videos/Upload/FileUpload.tsx
🧠 Learnings (2)
📚 Learning: 2024-12-04T04:27:45.795Z
Learnt from: liberalterian
PR: creativeplatform/crtv3#80
File: app/components/Videos/Upload/index.tsx:34-36
Timestamp: 2024-12-04T04:27:45.795Z
Learning: When working with Livepeer assets, use the `Asset` type from `'livepeer/models/components'` instead of defining a custom type.

Applied to files:

  • components/Videos/Upload/index.tsx
📚 Learning: 2024-12-04T05:02:55.124Z
Learnt from: liberalterian
PR: creativeplatform/crtv3#80
File: package.json:19-20
Timestamp: 2024-12-04T05:02:55.124Z
Learning: The use of `kreisler/js-google-translate-free` in `FileUpload.tsx` is a temporary measure and will be replaced with the new `/api/livepeer/translation/route.ts` endpoint.

Applied to files:

  • components/Videos/Upload/FileUpload.tsx
🧬 Code graph analysis (10)
app/page.tsx (2)
lib/hooks/accountkit/useAuthStateMonitor.ts (1)
  • useAuthStateMonitor (11-54)
components/ui/use-toast.ts (1)
  • useToast (171-189)
components/Player/TrendingPlayer.tsx (2)
lib/types/video-asset.ts (1)
  • Subtitles (13-13)
components/Videos/VideoDetails.tsx (3)
  • assetMetadata (547-551)
  • SubtitlesInitializer (540-554)
  • asset (60-66)
components/account-dropdown/AccountDropdown.tsx (6)
lib/sdk/alchemy/swap-service.ts (2)
  • TokenSymbol (10-10)
  • TOKEN_INFO (13-17)
lib/contracts/USDCToken.ts (2)
  • USDC_TOKEN_DECIMALS (9-9)
  • USDC_TOKEN_ADDRESSES (3-6)
lib/contracts/DAIToken.ts (2)
  • DAI_TOKEN_DECIMALS (9-9)
  • DAI_TOKEN_ADDRESSES (3-6)
components/wallet/swap/AlchemySwapWidget.tsx (1)
  • AlchemySwapWidget (34-1007)
components/wallet/balance/MeTokenBalances.tsx (1)
  • MeTokenBalances (60-258)
lib/hooks/metokens/useMeTokensSupabase.ts (1)
  • useMeTokensSupabase (342-791)
components/auth/LoginButton.tsx (2)
components/ui/use-toast.ts (1)
  • toast (191-191)
components/ui/button.tsx (1)
  • Button (60-60)
app/discover/page.tsx (2)
components/Videos/VideoSearch.tsx (1)
  • VideoSearch (50-161)
components/account-dropdown/MembershipSection.tsx (2)
  • MembershipSection (38-141)
  • MembershipDetails (111-111)
components/home-page/TopVideos.tsx (2)
lib/utils/abortUtils.ts (1)
  • signal (13-15)
components/Videos/VideoDetails.tsx (3)
  • fetchPlaybackSources (55-85)
  • sources (56-59)
  • asset (60-66)
components/Player/Subtitles.tsx (2)
components/Videos/VideoDetails.tsx (3)
  • SubtitlesInitializer (540-554)
  • assetMetadata (547-551)
  • asset (60-66)
app/api/livepeer/utils.ts (1)
  • SubtitleEntry (1-6)
components/home-page/NonLoggedInView.tsx (3)
lib/hooks/accountkit/useAuthStateMonitor.ts (1)
  • useAuthStateMonitor (11-54)
components/ui/use-toast.ts (1)
  • useToast (171-189)
components/ui/toaster.tsx (1)
  • Toaster (20-48)
components/Videos/Upload/index.tsx (2)
lib/types/video-asset.ts (1)
  • VideoAsset (15-47)
services/video-assets.ts (2)
  • updateVideoAsset (143-197)
  • updateVideoAsset (137-170)
components/Videos/Upload/FileUpload.tsx (1)
app/api/livepeer/assetUploadActions.ts (1)
  • getLivepeerUploadUrl (7-65)
🔇 Additional comments (5)
components/auth/LoginButton.tsx (2)

46-51: LGTM!

Proper cleanup logic that prevents stale deployment state after logout. The effect correctly resets isDeploying when the user is no longer authenticated.


63-70: LGTM!

The fixed loading label "Deploying Account..." is more accurate and consistent than a variable label. The updated loading condition correctly reflects the removal of the isLoading state.

app/discover/page.tsx (2)

1-17: LGTM! Imports are correctly structured.

All necessary dependencies for the membership gating and search/filter features are properly imported. The "use client" directive is correctly placed for a component that uses hooks and state.


93-113: Approve VideoCardGrid pagination and search integration
Pagination is implemented internally using ITEMS_PER_PAGE, offset calculation, hasNextPage state, and the Pagination component; VideoSearch props are correctly wired.

components/home-page/TopVideos.tsx (1)

264-268: Passing assetId through assetMetadata: LGTM

Aligns with the new AssetMetadata shape and enables consumers (e.g., views, detail routes).

sirgawain0x and others added 30 commits February 4, 2026 21:45
This reverts commit e9ada0b, reversing
changes made to deb47da.
Implement immediate redirect to Discover page after video upload success.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant