Conversation
cgen-547940b70bbb499793a6d6b939b68077
… RPC from env cgen-4a3699e76f1d44f7bda96039bf5e5b31
cgen-ed50c588e75e47a6af12b5277ff9fba9
…onfig cgen-7434aab389d54ee49bbdc752a1518f99
cgen-76f73dce27474023ad6c1ef0c76fa4a6
cgen-5830c760159840beac87ca90dc367b10
cgen-eae9fcb6237843d2998bbf6a78e30ccd
cgen-0ccec6807dcc452d99c4d7a88adf32eb
cgen-c46ecd2eb6774ec1b57478a633b06a1d
cgen-0ecb608db6bb4e2eae3df7005c6e1d70
cgen-4a3cadc167b44cf99ce35ef7183e4834
cgen-614c1ab4cae44d7d899373b8c5b56fff
cgen-5c79ee083b5346e1936d348af18db176
cgen-0bc8330773f445e29522cb00018f6273
cgen-81a73decf4224bf79a62fdc115040402
cgen-4c9ccba811c047d498523299564c8eb4
cgen-3b1dc6ad33b44a06aabd42af5b19ba71
cgen-cca5671bb647454887b39f4d688900cc
cgen-4a173e1b03554a6fbccff3cf2729bf52
cgen-527d683a04944755a51631a01f7d6af9
cgen-896fd5366ef04df4a47223c3766d4489
cgen-84845ed243854fc5a28b44cade1c2c6b
cgen-503cc782d53d4fec846d72c888783bb1
cgen-8992742fcd904ea5bf63b6841edf1ab9
cgen-644bc1443b2442d9bfac49c30ccc76bc
cgen-0ca2efd369d04ce5addb9e3cd8e76778
cgen-1b487b8ffff444a08632e948257ce901
cgen-864a89a2f4e4422b94c085f0db70048f
cgen-97c127b961e440db8d9f80fa0b380d7d
cgen-7c67a50ecb314d1599d53c5e4fe3c839
cgen-549faa7bfbfb4964a4f0157c58fb5f14
cgen-d50c1f87063b48bc90de3f2e746e26d3
cgen-41dce36669d74a06ad11ec6926b9fbb9
cgen-cc27e3c85010470ab9f8e89cb5023daa
cgen-8acf2da6385b43db8aae9c7a4b9c6613
cgen-d60fbf3fc51841ecb53e5a6f6272df99
cgen-275f686a633843dea0d61de15091917e
…lace) cgen-8602d79c844f41cc91d11b0a474f5454
cgen-6f113e649ba441df92a681dca766d3e3
cgen-f493927c9ebf43398420dfc584814323
cgen-cd4e46f177764fcc91ef767d90d4d81e
cgen-3241011bf94a40cd9f20cdc9e0a8c2f9
cgen-5f1dc6f7e2714ba4a3f205d6859f1b92
cgen-7b19c5ef19b044ee97d68a82e217db37
cgen-ab97154a621e4ef98c9af4048eaf2934
cgen-378e0f6982614a5fae8cdb6614808f6c
cgen-9c0fbae5df134303b3b937468391495f
cgen-2d3fa22aad014da5aaefe1b4d0c0c202
cgen-7982503302c84dfdb5575d9680b89c23
cgen-57291a3ceae74861ab16c4b3ed0a999b
cgen-a7a5c4784b4340dbaa0ea0933a3174ba
cgen-6975825988614f139296a32e41acc444
cgen-fccfa6dab6814a58847443cd38013fb2
cgen-4c850f313b4648bf918224ee7846d5c5
cgen-68ae67fd2c814fad84e5b1b873563ff0
cgen-62c60808c389434da735160126575e11
cgen-825920b6e2504a538ab6160a0f02cb7e
Fix smart contract integration and remove dummy data
WalkthroughIntroduces on-chain integrations and utilities: new hooks for posts, profiles, comments, and follow; updates ConnectWallet to auto-switch to Base and streamline connection; integrates feed with on-chain posts; replaces placeholder contract ABIs/addresses with env-driven JSON ABIs; adds a QA script; adjusts wagmi config, minor React imports, and Tailwind plugin loading. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant App as ConnectWallet
participant Wagmi as wagmi/connect
participant Wallet as EIP-1193 Wallet
participant Chain as Base RPC
User->>App: Click "Connect Wallet"
App->>Wagmi: connect({ preferred connector })
Wagmi->>Wallet: request accounts
Wallet-->>Wagmi: accounts, chainId
Wagmi-->>App: isConnected, connector
App->>Wallet: eth_chainId
Wallet-->>App: current chainId
alt Not Base
App->>Wallet: wallet_addEthereumChain(Base params)
Wallet-->>App: result/err
App->>Wallet: wallet_switchEthereumChain(8453)
Wallet-->>App: switched/err
App-->>User: toast(success/error)
else Already on Base
App-->>User: toast(connected)
end
App->>Chain: subsequent dapp calls (via hooks)
Chain-->>App: responses
sequenceDiagram
autonumber
participant Feed as Feed.tsx
participant Posts as usePosts hook
participant RPC as publicClient (Base)
participant Wallet as writeContract
Note over Feed,Posts: Initial load
Feed->>Posts: usePosts()
Posts->>RPC: readContract(getAllPosts) or fallback reads
RPC-->>Posts: post IDs / tuples
Posts->>Posts: normalize, sort, setState
Posts-->>Feed: posts, isLoading=false
Note over Feed,Posts: Create post
Feed->>Posts: createPost(content)
alt Not connected
Posts->>Wallet: connect (preferred)
end
alt Contract configured
Posts->>Wallet: writeContract(createPost)
Wallet-->>Posts: tx hash / error
Posts->>Posts: set isCreating, toasts
Posts->>RPC: refetch after delay
RPC-->>Posts: updated posts
Posts-->>Feed: updated state
else Mock mode
Posts->>Posts: append local post, toast
Posts-->>Feed: updated state
end
Note over Feed,Posts: Like/Unlike similar flow with writeContract and refetch/optimistic updates
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/PostCard.tsx (1)
16-28: Fix Post type drift and timestamp handling to avoid TS mismatch.Feed passes
timestamp: number | Dateandusername?; PostCard requiresDateandusername(required). This can break type-checking and formatting.Apply:
interface Post { id: string; author: string; - username: string; + username?: string; content: string; - timestamp: Date; + timestamp: number | Date; likes: number; comments: number; reposts: number; - isLiked: boolean; + isLiked?: boolean; avatarUrl?: string; txHash?: string; } -const formatTimestamp = (date: Date) => { - const now = new Date(); - const diffMs = now.getTime() - date.getTime(); +const formatTimestamp = (ts: number | Date) => { + const date = typeof ts === 'number' ? new Date(ts) : ts; + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMins / 60); const diffDays = Math.floor(diffHours / 24); if (diffMins < 1) return "now"; if (diffMins < 60) return `${diffMins}m`; if (diffHours < 24) return `${diffHours}h`; return `${diffDays}d`; }; -<div className="text-sm text-muted-foreground">@{post.username}</div> +<div className="text-sm text-muted-foreground">@{post.username ?? post.author.slice(0, 8)}</div>Also applies to: 47-58, 75-80
🧹 Nitpick comments (24)
src/components/Dashboard.tsx (1)
14-17: Prevent state/DOM drift in dark-mode toggleDrive the DOM class from state via an effect (and avoid manual toggle).
-import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; @@ - const toggleDarkMode = () => { - setDarkMode(!darkMode); - document.documentElement.classList.toggle("dark"); - }; + const toggleDarkMode = () => { + setDarkMode((v) => !v); + }; + useEffect(() => { + document.documentElement.classList.toggle("dark", darkMode); + }, [darkMode]);src/pages/NotFound.tsx (1)
16-18: Use client-side navigationPrefer Link to avoid full page reload.
-import { useLocation } from "react-router-dom"; +import { useLocation, Link } from "react-router-dom"; @@ - <a href="/" className="text-blue-500 underline hover:text-blue-700"> + <Link to="/" className="text-blue-500 underline hover:text-blue-700"> Return to Home - </a> + </Link>tailwind.config.ts (1)
3-3: Duplicate type import triggers noRedeclareRemove the second import of Config.
-import type { Config } from "tailwindcss"; import tailwindcssAnimate from "tailwindcss-animate";src/lib/wagmi.ts (2)
9-28: Avoid double assertion; use a type guard for connectorsReplace as unknown as Connector[] with a proper predicate.
-const connectors = [ - metaMask(), - coinbaseWallet({ - appName: 'BaseLine', - appLogoUrl: '/baseline-logo.png', - }), - ...(walletConnectProjectId - ? [ - walletConnect({ - projectId: walletConnectProjectId, - metadata: { - name: 'BaseLine', - description: 'A Web3 microblogging platform on Base', - url: 'https://baseline.app', - icons: ['/baseline-logo.png'], - }, - }), - ] - : []), -].filter(Boolean) as unknown as Connector[]; +const connectors = [ + metaMask(), + coinbaseWallet({ + appName: 'BaseLine', + appLogoUrl: '/baseline-logo.png', + }), + walletConnectProjectId + ? walletConnect({ + projectId: walletConnectProjectId, + metadata: { + name: 'BaseLine', + description: 'A Web3 microblogging platform on Base', + url: 'https://baseline.app', + icons: ['/baseline-logo.png'], + }, + }) + : undefined, +].filter((c): c is Connector => Boolean(c));
20-24: WalletConnect icon: use absolute HTTPS URLWalletConnect requires absolute HTTPS icon URLs — change icons: ['/baseline-logo.png'] to an absolute URL (e.g. 'https://baseline.app/baseline-logo.png') or another HTTPS-hosted URL. File: src/lib/wagmi.ts (lines 20–24). Asset located at src/assets/baseline-logo.png.
src/components/CreatePost.tsx (2)
16-18: Type onPost to allow sync or asyncReflect actual usage and drop nullable handling.
-interface CreatePostProps { - onPost: (content: string) => void; -} +interface CreatePostProps { + onPost: (content: string) => void | Promise<unknown>; +}
34-53: Simplify handlePost; remove promise duck-typing and fake delayAwait both sync/async uniformly via Promise.resolve; no simulated delay.
const handlePost = async () => { if (!content.trim() || isPosting) return; setIsPosting(true); try { - const res = onPost ? onPost(content) : null; - if (res && typeof (res as { then?: unknown }).then === 'function') { - await res as Promise<unknown>; - } else { - // fallback simulated tx - await new Promise(resolve => setTimeout(resolve, 1000)); - } + await Promise.resolve(onPost(content)); setContent(""); setCharCount(0); } catch (err) { console.error('Post failed', err); } finally { setIsPosting(false); } };scripts/qa-onchain-check.js (1)
12-13: Clarify env error message to include both supported variables.Message references only VITE_BASELINE_POSTS though you also check BASELINE_POSTS.
- console.error('No Posts contract address provided in env VITE_BASELINE_POSTS'); + console.error('No Posts contract address provided (set VITE_BASELINE_POSTS or BASELINE_POSTS)');src/lib/contracts.ts (2)
46-48: Event param name typo: "followingEvent" should be "following".Names don’t affect the event signature but do affect consumer ergonomics and generated typings.
- { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "follower", "type": "address" }, { "indexed": true, "internalType": "address", "name": "followingEvent", "type": "address" } ], "name": "Unfollowed", "type": "event" }, + { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "follower", "type": "address" }, { "indexed": true, "internalType": "address", "name": "following", "type": "address" } ], "name": "Unfollowed", "type": "event" },
1-7: Consider tightening address types to0x${string}and validating env at startup.This improves DX and catches misconfig early.
-export const CONTRACT_ADDRESSES = { - Profiles: import.meta.env.VITE_BASELINE_PROFILES as string | undefined, - Posts: import.meta.env.VITE_BASELINE_POSTS as string | undefined, - Comments: import.meta.env.VITE_BASELINE_COMMENTS as string | undefined, - Follow: import.meta.env.VITE_BASELINE_FOLLOW as string | undefined, -} as const; +export const CONTRACT_ADDRESSES = { + Profiles: import.meta.env.VITE_BASELINE_PROFILES as `0x${string}` | undefined, + Posts: import.meta.env.VITE_BASELINE_POSTS as `0x${string}` | undefined, + Comments: import.meta.env.VITE_BASELINE_COMMENTS as `0x${string}` | undefined, + Follow: import.meta.env.VITE_BASELINE_FOLLOW as `0x${string}` | undefined, +} as const;src/hooks/useComments.ts (3)
44-70: Batch reads with multicall to cut RPC round‑trips.Reading each comment individually is N+1 and slow. Use publicClient.multicall.
- const items = await Promise.all( - (ids as unknown[]).map(async (id: unknown) => { - const raw: unknown = await publicClient.readContract({ - address: commentsAddr as `0x${string}`, - abi: COMMENTS_ABI as unknown as import('abitype').Abi, - functionName: 'comments', - args: [id as unknown], - }); - const arr = raw as unknown as [ - bigint | number | string, - bigint | number | string, - string, - string, - bigint | number - ]; - return { - id: arr[0]?.toString(), - postId: arr[1]?.toString(), - author: arr[2], - content: arr[3], - timestamp: Number(arr[4] ?? Date.now()), - likes: 0, - }; - }) - ); + const calls = (ids as readonly unknown[]).map((id) => ({ + address: commentsAddr as `0x${string}`, + abi: COMMENTS_ABI as unknown as import('abitype').Abi, + functionName: 'comments' as const, + args: [id as unknown], + })); + const results = await publicClient.multicall({ contracts: calls }); + const items = results.map((r) => { + const arr = r.result as unknown as [bigint, bigint, string, string, bigint]; + return { + id: arr[0]?.toString(), + postId: arr[1]?.toString(), + author: arr[2], + content: arr[3], + timestamp: Number(arr[4] ?? 0), // seconds + likes: 0, + }; + });
31-43: Type the read result to avoid unknown and brittle guards.Let viem infer
readonly bigint[]to skip Array.isArray checks.- const ids: unknown = await publicClient.readContract({ + const ids = await publicClient.readContract({ address: commentsAddr as `0x${string}`, abi: COMMENTS_ABI as unknown as import('abitype').Abi, functionName: 'getCommentsForPost', args: [BigInt(postId)], }); - - if (!Array.isArray(ids)) return [];
10-15: Validate numeric postId before BigInt coercion.Prevent runtime throws on malformed ids.
- const addComment = useCallback(async (postId: string, content: string) => { + const addComment = useCallback(async (postId: string, content: string) => { + if (!/^\d+$/.test(postId)) { toast.error('Invalid post id'); return; }src/hooks/useFollow.ts (2)
1-2: Duplicate import of useCallback.Remove the second import to fix the linter error.
-import { useCallback } from 'react'; -import { useCallback } from 'react'; +import { useCallback } from 'react';
12-31: Type cast target addresses to0x${string}to satisfy ABI and catch bad input early.Improves type-safety on writes.
- args: [target], + args: [target as `0x${string}`],Apply similarly in unfollowUser/get* calls.
src/hooks/useProfile.ts (2)
36-45: Avoid precision loss for avatarTokenId; keep bigint in state.On-chain ids can exceed Number.MAX_SAFE_INTEGER.
- avatarTokenId: number; + avatarTokenId: bigint; @@ - avatarTokenId: Number(tup[4] ?? 0), + avatarTokenId: BigInt(tup[4] ?? 0n),And update references accordingly.
86-87: hasProfile should reflect on-chain existence, not just presence of an address.Use the contract’s exists flag.
- hasProfile: Boolean(targetAddress), + hasProfile: Boolean(profile?.exists),src/components/PostCard.tsx (2)
81-90: Make the explorer button actually open the tx.Currently it renders a button with no action/href.
-{post.txHash && ( - <Button - variant="ghost" - size="sm" - className="ml-auto h-6 w-6 p-0 hover:bg-accent" - title="View on Base Scanner" - > - <ExternalLink className="w-3 h-3" /> - </Button> -)} +{post.txHash && ( + <Button + variant="ghost" + size="sm" + className="ml-auto h-6 w-6 p-0 hover:bg-accent" + asChild + > + <a + href={`https://base.blockscout.com/tx/${post.txHash}`} + target="_blank" + rel="noopener noreferrer" + title="View on Base Explorer" + > + <ExternalLink className="w-3 h-3" /> + </a> + </Button> +)}
128-145: Optional: keep the “liking” spinner in sync with actual like/unlike async.
handleLikestops the spinner before the parent async finishes; alsoonLikecan bePromise<void>. Consider awaiting it.-const handleLike = async () => { +const handleLike = async () => { setIsLiking(true); - await new Promise(resolve => setTimeout(resolve, 500)); // Simulate blockchain tx - onLike(post.id); - setIsLiking(false); + try { + await onLike(post.id); + } finally { + setIsLiking(false); + } };Also applies to: 40-45
src/components/Feed.tsx (2)
10-22: Converge on a single Post type to prevent drift.You duplicate
interface Posthere whilesrc/hooks/usePosts.tsexports one. Prefer importing the shared type and only widening the timestamp locally if needed.-import React, { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; +import type { Post as ChainPost } from "@/hooks/usePosts"; -interface Post { - id: string; - author: string; - username?: string; - content: string; - timestamp: number | Date; - likes: number; - comments: number; - reposts: number; - isLiked?: boolean; - avatarUrl?: string; - txHash?: string; -} +type Post = Omit<ChainPost, "timestamp"> & { timestamp: number | Date };Also applies to: 24-37, 43-53
69-70: Nit: avoid deprecatedsubstr.
substris deprecated; useslice.- txHash: `0x${Math.random().toString(16).substr(2, 8)}...` + txHash: `0x${Math.random().toString(16).slice(2, 10)}...`src/components/ConnectWallet.tsx (2)
40-55: Prefer switch-first, add-on-4902 flow for chain handling.Some wallets error if you always call
wallet_addEthereumChain. Try switch; on error 4902, then add and switch.- await ethereum.request({ - method: 'wallet_addEthereumChain', - params: [{ - chainId: '0x' + base.id.toString(16), - chainName: 'Base Mainnet', - rpcUrls: [baseRpc], - nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, - blockExplorerUrls: ['https://base.blockscout.com/'], - }], - }); - - await ethereum.request({ - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x' + base.id.toString(16) }], - }); + try { + await ethereum.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x' + base.id.toString(16) }], + }); + } catch (e: any) { + if (e?.code === 4902) { + await ethereum.request({ + method: 'wallet_addEthereumChain', + params: [{ + chainId: '0x' + base.id.toString(16), + chainName: 'Base Mainnet', + rpcUrls: [baseRpc], + nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, + blockExplorerUrls: ['https://base.blockscout.com/'], + }], + }); + await ethereum.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x' + base.id.toString(16) }], + }); + } else { + throw e; + } + }
66-67: Nit: remove unusedwallets.Dead variable.
- const wallets = []; + // no-opsrc/hooks/usePosts.ts (1)
275-303: Gate like/unlike on connection or provide a connect prompt.Writes will fail when not connected; mirror the connect flow used in
createPostor short-circuit to mock with a toast.- const likePost = useCallback(async (postId: string) => { + const likePost = useCallback(async (postId: string) => { + if (!connected && !useMock) { + toast.error('Connect your wallet to like posts'); + return; + }- const unlikePost = useCallback(async (postId: string) => { + const unlikePost = useCallback(async (postId: string) => { + if (!connected && !useMock) { + toast.error('Connect your wallet to unlike posts'); + return; + }Also applies to: 304-331
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bun.lockbis excluded by!**/bun.lockb
📒 Files selected for processing (17)
scripts/qa-onchain-check.js(1 hunks)src/components/ConnectWallet.tsx(5 hunks)src/components/CreatePost.tsx(3 hunks)src/components/Dashboard.tsx(2 hunks)src/components/Feed.tsx(5 hunks)src/components/PostCard.tsx(2 hunks)src/components/ui/command.tsx(1 hunks)src/components/ui/textarea.tsx(1 hunks)src/hooks/useComments.ts(1 hunks)src/hooks/useFollow.ts(1 hunks)src/hooks/usePosts.ts(1 hunks)src/hooks/useProfile.ts(1 hunks)src/lib/contracts.ts(1 hunks)src/lib/wagmi.ts(1 hunks)src/pages/Index.tsx(1 hunks)src/pages/NotFound.tsx(1 hunks)tailwind.config.ts(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
src/hooks/usePosts.ts (2)
scripts/qa-onchain-check.js (3)
postsAddr(6-6)ids(26-26)post(31-31)src/lib/contracts.ts (1)
CONTRACT_ADDRESSES(2-7)
src/hooks/useComments.ts (1)
src/lib/contracts.ts (2)
CONTRACT_ADDRESSES(2-7)COMMENTS_ABI(37-43)
src/hooks/useFollow.ts (1)
src/lib/contracts.ts (2)
CONTRACT_ADDRESSES(2-7)FOLLOW_ABI(45-54)
src/components/Feed.tsx (1)
src/hooks/usePosts.ts (2)
Post(7-19)usePosts(42-345)
src/hooks/useProfile.ts (1)
src/lib/contracts.ts (2)
CONTRACT_ADDRESSES(2-7)PROFILES_ABI(31-35)
🪛 Biome (2.1.2)
src/hooks/useFollow.ts
[error] 2-2: Shouldn't redeclare 'useCallback'. Consider to delete it or rename it.
'useCallback' is defined here:
(lint/suspicious/noRedeclare)
tailwind.config.ts
[error] 3-3: Shouldn't redeclare 'Config'. Consider to delete it or rename it.
'Config' is defined here:
(lint/suspicious/noRedeclare)
🔇 Additional comments (10)
src/components/Dashboard.tsx (1)
1-1: React default import: OKConsistent with the PR; no issues.
src/pages/NotFound.tsx (1)
2-2: React default import: OKAligns with the project-wide import pattern.
tailwind.config.ts (1)
120-121: ESM plugin import: OKStatic import of tailwindcss-animate is correct.
src/components/ui/textarea.tsx (1)
5-5: Type alias migration: OKNo runtime impact; keeps public typing clear.
src/lib/wagmi.ts (1)
6-8: Env-driven RPC and project ID: OKSane defaults; no issues.
src/components/CreatePost.tsx (1)
1-1: React default import: OKMatches the project pattern; fine.
src/components/ui/command.tsx (1)
24-24: No export required — CommandDialogProps is internalRepository search shows CommandDialogProps is defined and used only in src/components/ui/command.tsx (lines 24, 26). No external usages found; no export needed.
scripts/qa-onchain-check.js (1)
2-2: Ensure this script runs in Node (ESM vs CJS).This file uses ESM import syntax with a shebang. It will fail unless package.json has "type": "module" (or the file is .mjs). Otherwise, switch to require().
Would you like a CJS-compatible variant?
src/hooks/useComments.ts (1)
66-66: Confirm timestamp units.Contracts typically return seconds; if UI expects ms, multiply by 1000.
src/pages/Index.tsx (1)
1-1: LGTM on React import.Consistent with the PR-wide pattern; no issues.
| const POSTS_ABI = [ | ||
| 'function getAllPosts() view returns (uint256[])', | ||
| 'function getPost(uint256) view returns (uint256 id, address author, string content, uint256 timestamp, uint256 likesCount, uint256 commentsCount, bool exists)' | ||
| ]; |
There was a problem hiding this comment.
ABI mismatch with deployed contract schema (getPost shape) and missing getAllPosts definition.
Your local ABI expects getPost to return 7 fields (incl. likes/comments/existence), but src/lib/contracts.ts defines getPost as (Post tuple, uint256) and does not expose commentsCount/exists. Also, POSTS_ABI there does not include getAllPosts, yet the script calls it. This will revert at runtime. Align the ABI and stop calling non-existent functions.
Apply this minimal ABI fix (align to tuple + likeCount) and switch to events-based discovery (PostCreated) rather than getAllPosts:
- const POSTS_ABI = [
- 'function getAllPosts() view returns (uint256[])',
- 'function getPost(uint256) view returns (uint256 id, address author, string content, uint256 timestamp, uint256 likesCount, uint256 commentsCount, bool exists)'
- ];
+ const POSTS_ABI = [
+ 'event PostCreated(uint256 indexed id, address indexed author, string content, uint256 timestamp)',
+ 'function getPost(uint256) view returns ((uint256 id, address author, string content, uint256 timestamp), uint256)',
+ 'function likeCounts(uint256) view returns (uint256)'
+ ];📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const POSTS_ABI = [ | |
| 'function getAllPosts() view returns (uint256[])', | |
| 'function getPost(uint256) view returns (uint256 id, address author, string content, uint256 timestamp, uint256 likesCount, uint256 commentsCount, bool exists)' | |
| ]; | |
| const POSTS_ABI = [ | |
| 'event PostCreated(uint256 indexed id, address indexed author, string content, uint256 timestamp)', | |
| 'function getPost(uint256) view returns ((uint256 id, address author, string content, uint256 timestamp), uint256)', | |
| 'function likeCounts(uint256) view returns (uint256)' | |
| ]; |
🤖 Prompt for AI Agents
In scripts/qa-onchain-check.js around lines 18 to 21, the hardcoded POSTS_ABI is
out of sync with the deployed contract: it declares getPost returning 7 fields
and includes getAllPosts (which the deployed contract/ src/lib/contracts.ts does
not expose). Replace the ABI entries so getPost matches the contract tuple
signature used in src/lib/contracts.ts (Post tuple plus uint256 likeCount) —
remove commentsCount and exists — and remove/stop calling getAllPosts; instead
implement discovery by filtering the PostCreated event from the contract
(queryFilter or provider.getLogs) to enumerate posts and then call getPost for
each returned id. Ensure the ABI only contains the actual functions/events used
(getPost and the PostCreated event) to avoid runtime reverts.
| try { | ||
| const contract = new Contract(postsAddr, POSTS_ABI, provider); | ||
| console.log('Calling getAllPosts...'); | ||
| const ids = await contract.getAllPosts(); | ||
| console.log('Received ids length:', ids.length); | ||
| if (ids.length > 0) { | ||
| const first = ids[0]; | ||
| console.log('Fetching first post id:', first.toString()); | ||
| const post = await contract.getPost(first); | ||
| console.log('Post:', { | ||
| id: post.id.toString(), | ||
| author: post.author, | ||
| content: (post.content || '').slice(0, 120), | ||
| timestamp: post.timestamp.toString(), | ||
| likes: (post.likesCount?.toString?.() || post[4]?.toString?.()) | ||
| }); | ||
| } else { | ||
| console.log('No posts found on-chain (getAllPosts returned empty array).'); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Replace getAllPosts call with event scan to enumerate post ids.
Since the ABI doesn’t include getAllPosts, query PostCreated logs to grab recent post IDs and then read a post via getPost.
- const contract = new Contract(postsAddr, POSTS_ABI, provider);
- console.log('Calling getAllPosts...');
- const ids = await contract.getAllPosts();
- console.log('Received ids length:', ids.length);
- if (ids.length > 0) {
- const first = ids[0];
- console.log('Fetching first post id:', first.toString());
- const post = await contract.getPost(first);
- console.log('Post:', {
- id: post.id.toString(),
- author: post.author,
- content: (post.content || '').slice(0, 120),
- timestamp: post.timestamp.toString(),
- likes: (post.likesCount?.toString?.() || post[4]?.toString?.())
- });
- } else {
- console.log('No posts found on-chain (getAllPosts returned empty array).');
- }
+ const contract = new Contract(postsAddr, POSTS_ABI, provider);
+ console.log('Scanning PostCreated logs...');
+ const topic0 = (await import('ethers')).id('PostCreated(uint256,address,string,uint256)');
+ const logs = await provider.getLogs({ address: postsAddr, topics: [topic0], fromBlock: 0, toBlock: 'latest' });
+ console.log('Found PostCreated logs:', logs.length);
+ if (logs.length > 0) {
+ const last = logs[logs.length - 1];
+ const idHex = last.topics[1]; // indexed id
+ const postId = BigInt(idHex);
+ console.log('Fetching latest post id:', postId.toString());
+ const [post, likeCount] = await contract.getPost(postId);
+ console.log('Post:', {
+ id: post.id.toString(),
+ author: post.author,
+ content: (post.content || '').slice(0, 120),
+ timestamp: post.timestamp.toString(),
+ likes: likeCount?.toString?.()
+ });
+ } else {
+ console.log('No posts found on-chain (no PostCreated logs).');
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In scripts/qa-onchain-check.js around lines 23 to 41, the code calls a
non-existent getAllPosts ABI method; replace that with a log scan for
PostCreated events and decode those logs to enumerate post IDs. Use the provider
to getLogs with a filter for the posts contract address and the PostCreated
event signature (via the POSTS_ABI) for a recent block range, decode each log
with an ethers Interface to extract the post ID list, then if any IDs exist call
contract.getPost(firstId) as before; ensure you handle the case of no logs found
and surface clear console messages and any decode errors.
| const { connectors, connect } = useConnect(); | ||
| const { isConnected } = useAccount(); | ||
| const baseRpc = (import.meta.env.VITE_BASE_RPC as string | undefined) ?? 'https://mainnet.base.org'; |
There was a problem hiding this comment.
Await connection properly; use connectAsync for error handling.
connect isn’t awaited; errors won’t be caught. Use connectAsync and await it.
- const { connectors, connect } = useConnect();
+ const { connectors, connectAsync } = useConnect();- try {
- connect({ connector: target });
- } catch (err) {
+ try {
+ await connectAsync({ connector: target });
+ } catch (err) {
console.error('Connect failed', err);
try { (await import('sonner')).toast.error('Failed to connect wallet'); } catch { console.warn('No sonner available'); }
}Also applies to: 110-132
🤖 Prompt for AI Agents
In src/components/ConnectWallet.tsx around lines 15-17 (and also apply same
change to lines 110-132), the component uses the non-async connect function so
connection attempts aren’t awaited and thrown errors can’t be handled; replace
usages of connect with connectAsync and await the call inside an async handler,
wrap the await in a try/catch to handle and log/display errors, and ensure any
subsequent logic that depends on the connection awaits the connectAsync result
before proceeding.
| txHash: `0x${Math.random().toString(16).substr(2, 8)}...` | ||
| }; | ||
| setPosts([newPost, ...posts]); | ||
| } |
There was a problem hiding this comment.
Use functional state updates to avoid stale closures after awaits.
Multiple setPosts(posts => ...) sites currently reference stale posts, risking lost updates.
- setPosts([newPost, ...posts]);
+ setPosts((prev) => [newPost, ...prev]);- setPosts(posts.map(post =>
+ setPosts((prev) => prev.map(post =>
post.id === postId
? {
...post,
isLiked: !post.isLiked,
likes: post.isLiked ? post.likes - 1 : post.likes + 1
}
: post
));- setPosts(posts.map(post =>
+ setPosts((prev) => prev.map(post =>
post.id === postId
? { ...post, reposts: post.reposts + 1 }
: post
));Also applies to: 86-96, 102-107, 54-58
🤖 Prompt for AI Agents
In src/components/Feed.tsx around lines 69-72 (and similarly at 86-96, 102-107,
and 54-58), the setPosts calls capture the outer posts variable and can lose
updates after awaits; change all setPosts usages to the functional updater
pattern (e.g., setPosts(prev => [newPost, ...prev]) or setPosts(prev =>
prev.map(...)) or setPosts(prev => prev.filter(...))) so updates use the latest
state and avoid stale closures.
| const { connectors, connect } = useConnect(); | ||
|
|
There was a problem hiding this comment.
Don’t return early after connecting; proceed to create the post. Also await connectAsync.
Currently createPost connects then returns, forcing users to retry. Use connectAsync and continue execution.
- const { connectors, connect } = useConnect();
+ const { connectors, connectAsync } = useConnect(); const createPost = useCallback(async (content: string) => {
// If user not connected, trigger wallet popup
if (!connected) {
try {
const preferred = ["MetaMask","Coinbase Wallet"];
const findPreferred = connectors.find((c) => preferred.some((p) => c.name.toLowerCase().includes(p.toLowerCase())));
const fallback = connectors.find((c) => c.ready) || connectors[0];
const target = findPreferred || fallback;
- if (target && connect) {
- await connect({ connector: target });
- try { toast('Please approve the connection in your wallet'); } catch { /* ignore */ }
- return;
- }
+ if (target) {
+ await connectAsync({ connector: target });
+ try { toast.success('Wallet connected'); } catch { /* ignore */ }
+ } else {
+ try { toast.error('No wallet connectors available'); } catch { /* ignore */ }
+ return;
+ }
} catch (err) {
console.error('connect trigger failed', err);
}
try { toast('Please connect your wallet to post'); } catch { /* ignore */ }
- return;
+ // continue; connected state should now be set, or write will fail and be handled below
}Also applies to: 201-220
🤖 Prompt for AI Agents
In src/hooks/usePosts.ts around lines 54-55 (and similarly at 201-220), the
createPost flow currently calls connect and returns immediately which prevents
post creation; replace that with awaiting connectAsync on the connector(s) and
then continue execution to create the post (do not early return). Specifically,
call await connectAsync(...) on the appropriate connector instance(s), handle
any errors, and then proceed to run the post-creation logic so users don't have
to retry; remove the early return and ensure proper async/await flow.
| const { isLoading: isUpdating } = useWaitForTransactionReceipt({ hash: undefined }); | ||
|
|
There was a problem hiding this comment.
useWaitForTransactionReceipt wired with undefined hash.
This never tracks your tx. Either capture the hash from writeContract and wait explicitly, or plumb the hash into the hook.
- const { isLoading: isUpdating } = useWaitForTransactionReceipt({ hash: undefined });
+ // Option A: drop the hook and wait programmatically after writes
+ // const { isLoading: isUpdating } = { isLoading: false as const };And in handleUpdateProfile:
- await writeUpdateProfileWithAvatar({
+ const hash = await writeUpdateProfileWithAvatar({
address: profilesAddr as `0x${string}`,
abi: PROFILES_ABI as unknown as import('abitype').Abi,
functionName: 'updateProfile',
args: [username, bio, avatarContract, BigInt(avatarTokenId ?? 0)],
});
+ await publicClient.waitForTransactionReceipt({ hash });
@@
- await writeUpdateProfile({
+ const hash = await writeUpdateProfile({
address: profilesAddr as `0x${string}`,
abi: PROFILES_ABI as unknown as import('abitype').Abi,
functionName: 'updateProfile',
args: [username, bio, '0x0000000000000000000000000000000000000000', BigInt(0)],
});
+ await publicClient.waitForTransactionReceipt({ hash });
@@
- toast.success('Profile update submitted');
- // refetch
- setTimeout(() => fetchProfile(), 1200);
+ toast.success('Profile updated');
+ await fetchProfile();Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/hooks/useProfile.ts around lines 22-23, useWaitForTransactionReceipt is
being called with a hardcoded undefined hash so it never tracks the transaction;
update the flow to capture the transaction hash returned by writeContract and
pass that hash into useWaitForTransactionReceipt (or alternatively call
waitForTransactionReceipt directly inside handleUpdateProfile with the returned
hash). Concretely: change the hook invocation to accept a reactive/hash variable
that gets set when writeContract resolves, and in handleUpdateProfile assign the
returned tx.hash to that variable (or await the tx receipt there) so the receipt
hook actually monitors the real transaction.
| const fetchProfile = useCallback(async () => { | ||
| const profilesAddr = CONTRACT_ADDRESSES.Profiles; | ||
| if (!profilesAddr || !targetAddress) return; | ||
|
|
||
| const handleCreateProfile = (username: string, bio: string) => { | ||
| if (!targetAddress) return; | ||
|
|
||
| // Mock implementation for now | ||
| toast.success(`Profile created for ${username}`); | ||
|
|
||
| // TODO: Uncomment when contracts are deployed | ||
| /* | ||
| try { | ||
| createProfile({ | ||
| address: CONTRACT_ADDRESSES.Profiles as `0x${string}`, | ||
| abi: PROFILES_ABI, | ||
| functionName: 'createProfile', | ||
| args: [username, bio], | ||
| const res: unknown = await (await import('wagmi')).publicClient.readContract({ | ||
| address: profilesAddr as `0x${string}`, | ||
| abi: PROFILES_ABI as unknown as import('abitype').Abi, | ||
| functionName: 'getProfile', | ||
| args: [targetAddress as `0x${string}`], | ||
| }); | ||
| } catch (error) { | ||
| toast.error('Failed to create profile'); | ||
| console.error(error); | ||
| } | ||
| */ | ||
| }; | ||
|
|
There was a problem hiding this comment.
Broken read path: using (await import('wagmi')).publicClient is invalid.
use wagmi’s usePublicClient() like other hooks; current code will throw at runtime.
-import { useAccount, useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
+import { useAccount, useWriteContract, useWaitForTransactionReceipt, usePublicClient } from 'wagmi';
@@
- const fetchProfile = useCallback(async () => {
+ const publicClient = usePublicClient();
+ const fetchProfile = useCallback(async () => {
@@
- const res: unknown = await (await import('wagmi')).publicClient.readContract({
+ const res = await publicClient.readContract({
address: profilesAddr as `0x${string}`,
abi: PROFILES_ABI as unknown as import('abitype').Abi,
functionName: 'getProfile',
args: [targetAddress as `0x${string}`],
});
@@
- }, [targetAddress]);
+ }, [publicClient, targetAddress]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const fetchProfile = useCallback(async () => { | |
| const profilesAddr = CONTRACT_ADDRESSES.Profiles; | |
| if (!profilesAddr || !targetAddress) return; | |
| const handleCreateProfile = (username: string, bio: string) => { | |
| if (!targetAddress) return; | |
| // Mock implementation for now | |
| toast.success(`Profile created for ${username}`); | |
| // TODO: Uncomment when contracts are deployed | |
| /* | |
| try { | |
| createProfile({ | |
| address: CONTRACT_ADDRESSES.Profiles as `0x${string}`, | |
| abi: PROFILES_ABI, | |
| functionName: 'createProfile', | |
| args: [username, bio], | |
| const res: unknown = await (await import('wagmi')).publicClient.readContract({ | |
| address: profilesAddr as `0x${string}`, | |
| abi: PROFILES_ABI as unknown as import('abitype').Abi, | |
| functionName: 'getProfile', | |
| args: [targetAddress as `0x${string}`], | |
| }); | |
| } catch (error) { | |
| toast.error('Failed to create profile'); | |
| console.error(error); | |
| } | |
| */ | |
| }; | |
| const publicClient = usePublicClient(); | |
| const fetchProfile = useCallback(async () => { | |
| const profilesAddr = CONTRACT_ADDRESSES.Profiles; | |
| if (!profilesAddr || !targetAddress) return; | |
| try { | |
| const res = await publicClient.readContract({ | |
| address: profilesAddr as `0x${string}`, | |
| abi: PROFILES_ABI as unknown as import('abitype').Abi, | |
| functionName: 'getProfile', | |
| args: [targetAddress as `0x${string}`], | |
| }); |
🤖 Prompt for AI Agents
In src/hooks/useProfile.ts around lines 24-35, the code incorrectly attempts to
access (await import('wagmi')).publicClient.readContract which will throw at
runtime; replace this dynamic import usage with wagmi's usePublicClient hook
(call usePublicClient() at the top of the hook/component), then call
publicClient.readContract(...) instead, keeping the same
address/abi/functionName/args; ensure usePublicClient is imported from 'wagmi'
and add publicClient and any other needed values to the useCallback dependency
array.
| const handleUpdateProfile = useCallback(async (username: string, bio: string, avatarContract?: string, avatarTokenId?: number) => { | ||
| const profilesAddr = CONTRACT_ADDRESSES.Profiles; | ||
| if (!profilesAddr || !targetAddress) return; | ||
|
|
||
| const handleSetAvatar = (nftContract: string, tokenId: string) => { | ||
| if (!targetAddress) return; | ||
|
|
||
| // Mock implementation for now | ||
| toast.success('Avatar updated'); | ||
|
|
||
| // TODO: Uncomment when contracts are deployed | ||
| /* | ||
| try { | ||
| setAvatar({ | ||
| address: CONTRACT_ADDRESSES.Profiles as `0x${string}`, | ||
| abi: PROFILES_ABI, | ||
| functionName: 'setAvatar', | ||
| args: [nftContract as `0x${string}`, BigInt(tokenId)], | ||
| }); | ||
| } catch (error) { | ||
| toast.error('Failed to set avatar'); | ||
| console.error(error); | ||
| if (avatarContract) { | ||
| await writeUpdateProfileWithAvatar({ | ||
| address: profilesAddr as `0x${string}`, | ||
| abi: PROFILES_ABI as unknown as import('abitype').Abi, | ||
| functionName: 'updateProfile', | ||
| args: [username, bio, avatarContract, BigInt(avatarTokenId ?? 0)], | ||
| }); | ||
| } else { | ||
| await writeUpdateProfile({ | ||
| address: profilesAddr as `0x${string}`, | ||
| abi: PROFILES_ABI as unknown as import('abitype').Abi, | ||
| functionName: 'updateProfile', | ||
| args: [username, bio, '0x0000000000000000000000000000000000000000', BigInt(0)], | ||
| }); | ||
| } |
There was a problem hiding this comment.
Guard against wiping username/bio when setting avatar.
setAvatar currently passes empty strings if profile isn’t loaded, potentially overwriting on-chain data.
- setAvatar: async (nftContract: string, tokenId: string) => {
- await handleUpdateProfile(profile?.username ?? '', profile?.bio ?? '', nftContract, Number(tokenId));
- },
+ setAvatar: async (nftContract: string, tokenId: string) => {
+ if (!profile) { toast.error('Load profile before setting avatar'); return; }
+ await handleUpdateProfile(profile.username, profile.bio, nftContract, Number(tokenId));
+ },Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/hooks/useProfile.ts around lines 56 to 75, the handler can call
updateProfile with empty username/bio (wiping on-chain data) when the in-memory
profile isn't loaded; change the logic to first ensure the current profile is
present (or return early) and use the existing profile.username and profile.bio
when avatarContract is provided, only falling back to allowed defaults if you
explicitly intend to clear fields; validate avatarTokenId before converting to
BigInt and pass the preserved username/bio into
writeUpdateProfileWithAvatar/writeUpdateProfile so the avatar update doesn't
overwrite other fields.
| { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "uint256", "name": "id", "type": "uint256" }, { "indexed": true, "internalType": "address", "name": "liker", "type": "address" } ], "name": "PostLiked", "type": "event" }, | ||
| { "inputs": [ { "internalType": "string", "name": "content", "type": "string" } ], "name": "createPost", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, | ||
| { "inputs": [ { "internalType": "uint256", "name": "id", "type": "uint256" } ], "name": "getPost", "outputs": [ { "components": [ { "internalType": "uint256", "name": "id", "type": "uint256" }, { "internalType": "address", "name": "author", "type": "address" }, { "internalType": "string", "name": "content", "type": "string" }, { "internalType": "uint256", "name": "timestamp", "type": "uint256" } ], "internalType": "struct Posts.Post", "name": "", "type": "tuple" }, { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, | ||
| { "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "name": "likeCounts", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, | ||
| { "inputs": [ { "internalType": "uint256", "name": "id", "type": "uint256" } ], "name": "likePost", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, | ||
| { "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" }, { "internalType": "address", "name": "", "type": "address" } ], "name": "liked", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "view", "type": "function" }, | ||
| { "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "name": "posts", "outputs": [ { "internalType": "uint256", "name": "id", "type": "uint256" }, { "internalType": "address", "name": "author", "type": "address" }, { "internalType": "string", "name": "content", "type": "string" }, { "internalType": "uint256", "name": "timestamp", "type": "uint256" } ], "stateMutability": "view", "type": "function" } | ||
| ] as const; |
There was a problem hiding this comment.
🧩 Analysis chain
POSTS ABI lacks getAllPosts that the QA script expects.
Either add the view function here if the contract truly supports it, or update consumers (scripts/qa-onchain-check.js) to use events-driven enumeration. Keep a single source of truth for ABI to avoid drift.
Run to check usages expecting getAllPosts:
🏁 Script executed:
#!/bin/bash
rg -nP --type=ts --type=js -C2 '\bgetAllPosts\b'Length of output: 1941
POSTS ABI missing getAllPosts — add it or update consumers
scripts/qa-onchain-check.js and src/hooks/usePosts.ts call getAllPosts, but the POSTS ABI in src/lib/contracts.ts does not include that function. Add the signature "function getAllPosts() view returns (uint256[])" to the ABI (preferred — single source of truth) or update the callers to rely on the existing fallback/event-driven enumeration.
Affected locations:
- src/lib/contracts.ts:22-29 — current POSTS ABI (missing getAllPosts)
- scripts/qa-onchain-check.js:19-26 — defines POSTS_ABI and calls contract.getAllPosts()
- src/hooks/usePosts.ts:75-83 — attempts readContract({ functionName: 'getAllPosts' }); fallback at 129-131
🤖 Prompt for AI Agents
src/lib/contracts.ts lines 22-29: the POSTS ABI defined here is missing the
getAllPosts() view function used by scripts/qa-onchain-check.js and
src/hooks/usePosts.ts; add the ABI entry for getAllPosts — i.e. include the
function signature "function getAllPosts() view returns (uint256[])" in the
exported POSTS ABI array (maintain the as const typing), or alternatively update
the callers to stop invoking getAllPosts and use the existing events/fallback
enumeration.
Summary by CodeRabbit
New Features
Bug Fixes
Style
Chores