hi#10
Conversation
WalkthroughAdds a Solana/EOS reward system: new Solana and security utilities, server reward pipeline and API routes, multi-step organization approval UI/pages, Prisma schema migration for rewards, EOS token-balance refactor, theme/hydration tweaks, dependency updates, and supporting docs. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Org as Organization (Wallet)
participant OA as OrganizationApproval (UI)
participant API as /api/organization/approve-reward
participant SA as rewardContributorForIssue (Server Action)
participant SEC as security-utils
participant SOL as solana-utils (treasury)
participant CL as Solana Cluster
participant DB as Prisma/Database
participant UI as Revalidate UI
Org->>OA: Connect wallet, create/fund approval (SOL/SPL/EOS)
OA->>CL: Send on-chain transfer (user → treasury)
CL-->>OA: Signature
OA->>API: POST approval {issue, amount, tokenType, mint, signature, wallets}
API->>DB: Upsert Issue & Approval record
API-->>OA: {success, issueId, signature}
note over SA,SEC: Server-side reward pipeline (alternate flow)
OA->>SA: trigger rewardContributorForIssue(issueId, username)
SA->>SEC: performSecurityChecks(...)
SEC-->>SA: {passed | failures}
SA->>SOL: send*FromTreasury(recipient, amount, mint?)
SOL->>CL: Submit transaction
CL-->>SOL: Signature/confirmation
SA->>DB: Transactional update (Contribution, Issue status -> FUNDED/REWARDED)
SA->>UI: revalidatePath(profile, leaderboard)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
🧹 Nitpick comments (1)
SOLANA_REWARD_FLOW.md (1)
9-44: Add a language hint to the fenced block.The flow diagram fence trips MD040 (“fenced-code-language”). Add a language (e.g.,
text) after the opening backticks to satisfy markdownlint and keep CI green.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (18)
EOS/eostoken.jsx(1 hunks)SOLANA_REWARD_FLOW.md(1 hunks)actions/rewardContributorForIssue.js(3 hunks)actions/userProfile.js(3 hunks)api/organization/approve-reward/route.js(1 hunks)app/api/organization/approve-reward/route.js(1 hunks)app/eos-rewards/page.jsx(1 hunks)app/layout.js(1 hunks)app/solana-demo/page.jsx(1 hunks)app/test-inputs/page.jsx(1 hunks)components/ClientOnly.jsx(1 hunks)components/OrganizationApproval.jsx(1 hunks)components/theme-provider.jsx(1 hunks)lib/security-utils.js(1 hunks)lib/solana-utils.js(1 hunks)package.json(1 hunks)prisma/migrations/20251004214857_update_issue_schema_for_rewards/migration.sql(1 hunks)prisma/schema.prisma(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (11)
app/test-inputs/page.jsx (1)
components/OrganizationApproval.jsx (1)
OrganizationApproval(19-524)
app/layout.js (1)
components/theme-provider.jsx (1)
ThemeProvider(6-18)
lib/security-utils.js (2)
actions/rewardContributorForIssue.js (4)
tokenType(105-105)signature(34-34)tokenMintAddress(106-106)user(71-73)lib/solana-utils.js (10)
signature(217-221)signature(285-289)signature(422-426)verifyTransaction(304-314)getTransactionDetails(335-346)balance(50-50)balance(358-358)balance(454-454)treasuryUtils(437-460)treasuryUtils(437-460)
app/api/organization/approve-reward/route.js (2)
lib/security-utils.js (1)
verifyTransactionIntegrity(105-156)actions/userProfile.js (1)
issue(204-214)
app/solana-demo/page.jsx (1)
components/OrganizationApproval.jsx (1)
OrganizationApproval(19-524)
api/organization/approve-reward/route.js (2)
app/api/organization/approve-reward/route.js (6)
POST(7-105)body(9-9)body(10-20)organizationWallet(111-111)issue(39-69)prisma(5-5)lib/solana-utils.js (1)
verifyTransaction(304-314)
actions/rewardContributorForIssue.js (5)
lib/solana-utils.js (10)
eosUtils(463-475)eosUtils(463-475)isValidWalletAddress(321-328)signature(217-221)signature(285-289)signature(422-426)sendEosFromTreasury(376-434)sendSolFromTreasury(203-229)sendTokenFromTreasury(238-297)verifyTransaction(304-314)actions/userProfile.js (3)
issue(204-214)user(15-28)user(60-60)api/organization/approve-reward/route.js (1)
issue(38-38)app/api/organization/approve-reward/route.js (1)
issue(39-69)lib/security-utils.js (3)
user(321-324)performSecurityChecks(227-291)detectSuspiciousActivity(298-341)
lib/solana-utils.js (3)
EOS/eostoken.jsx (6)
EOS_PRICE_USD(8-8)connection(4-4)connection(12-12)getTokenBalance(10-42)accountInfo(23-23)account(30-30)components/OrganizationApproval.jsx (1)
connection(25-25)actions/rewardContributorForIssue.js (1)
tokenMintAddress(106-106)
app/eos-rewards/page.jsx (4)
EOS/eostoken.jsx (3)
metadata(47-47)fetchTokenMetadata(44-53)fetchTokenMetadata(44-53)components/OrganizationApproval.jsx (2)
loading(21-21)OrganizationApproval(19-524)lib/solana-utils.js (4)
treasuryUtils(437-460)treasuryUtils(437-460)eosUtils(463-475)eosUtils(463-475)components/ClientOnly.jsx (1)
ClientOnly(5-17)
components/OrganizationApproval.jsx (2)
lib/solana-utils.js (26)
connection(30-30)connection(30-30)connection(118-118)connection(186-186)eosUtils(463-475)eosUtils(463-475)treasuryUtils(437-460)treasuryUtils(437-460)publicKey(49-49)publicKey(66-66)getEosBalance(353-368)balance(50-50)balance(358-358)balance(454-454)getSolBalance(47-56)getTokenBalance(64-94)isValidWalletAddress(321-328)transaction(109-115)transaction(158-158)transaction(209-215)transaction(258-258)transaction(306-308)transaction(337-340)transaction(395-395)createTokenTransferTransaction(137-195)createSolTransferTransaction(103-127)components/ClientOnly.jsx (1)
ClientOnly(5-17)
EOS/eostoken.jsx (1)
lib/solana-utils.js (8)
connection(30-30)connection(30-30)connection(118-118)connection(186-186)EOS_PRICE_USD(27-27)getTokenBalance(64-94)accountInfo(75-75)account(81-81)
🪛 Biome (2.1.2)
actions/rewardContributorForIssue.js
[error] 14-14: Shouldn't redeclare 'performSecurityChecks'. Consider to delete it or rename it.
'performSecurityChecks' is defined here:
(lint/suspicious/noRedeclare)
[error] 14-14: Shouldn't redeclare 'detectSuspiciousActivity'. Consider to delete it or rename it.
'detectSuspiciousActivity' is defined here:
(lint/suspicious/noRedeclare)
🪛 Gitleaks (8.28.0)
lib/solana-utils.js
[high] 25-25: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🪛 markdownlint-cli2 (0.18.1)
SOLANA_REWARD_FLOW.md
9-9: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
| import { performSecurityChecks, detectSuspiciousActivity } from "@/lib/security-utils"; | ||
| import { performSecurityChecks, detectSuspiciousActivity } from "@/lib/security-utils"; | ||
|
|
There was a problem hiding this comment.
Remove duplicate import declaration
performSecurityChecks and detectSuspiciousActivity are imported twice, which redeclares the bindings and causes a build-time noRedeclare/syntax error. Drop the duplicate import.
-import { performSecurityChecks, detectSuspiciousActivity } from "@/lib/security-utils";📝 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.
| import { performSecurityChecks, detectSuspiciousActivity } from "@/lib/security-utils"; | |
| import { performSecurityChecks, detectSuspiciousActivity } from "@/lib/security-utils"; | |
| // actions/rewardContributorForIssue.js | |
| import { performSecurityChecks, detectSuspiciousActivity } from "@/lib/security-utils"; |
🧰 Tools
🪛 Biome (2.1.2)
[error] 14-14: Shouldn't redeclare 'performSecurityChecks'. Consider to delete it or rename it.
'performSecurityChecks' is defined here:
(lint/suspicious/noRedeclare)
[error] 14-14: Shouldn't redeclare 'detectSuspiciousActivity'. Consider to delete it or rename it.
'detectSuspiciousActivity' is defined here:
(lint/suspicious/noRedeclare)
🤖 Prompt for AI Agents
In actions/rewardContributorForIssue.js around lines 13 to 15 there is a
duplicate import of performSecurityChecks and detectSuspiciousActivity from
"@/lib/security-utils"; remove the redundant import statement so each symbol is
imported only once, leaving a single import line that brings in both functions.
| if (!issue) return { error: `Issue #${githubIssueId} not found in our database.` }; | ||
| if (!user) return { error: `User '${githubUsername}' is not registered in our app.` }; | ||
| if (issue.status !== 'OPEN') return { error: 'Issue is not open for rewards.' }; | ||
| if (!user.walletAddress) return { error: `User ${githubUsername} has no wallet address.` }; | ||
| if (issue.tokenReward <= 0) return { error: 'Issue has no token reward set.' }; | ||
| if (issue.rewardAmount <= 0) return { error: 'Issue has no token reward set.' }; | ||
|
|
||
| // 3. Security checks | ||
| const securityResult = await performSecurityChecks({ | ||
| userId: user.id, |
There was a problem hiding this comment.
Allow funded issues through the status guard
The approval flow marks issues as FUNDED (see app/api/organization/approve-reward/route.js), so this guard rejects every funded issue and the reward action never executes. Accept the funded status (or similar) before issuing the payout.
- if (issue.status !== 'OPEN') return { error: 'Issue is not open for rewards.' };
+ const eligibleStatuses = ['OPEN', 'FUNDED'];
+ if (!eligibleStatuses.includes(issue.status)) {
+ return { error: `Issue status '${issue.status}' is not eligible for rewards.` };
+ }📝 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.
| if (!issue) return { error: `Issue #${githubIssueId} not found in our database.` }; | |
| if (!user) return { error: `User '${githubUsername}' is not registered in our app.` }; | |
| if (issue.status !== 'OPEN') return { error: 'Issue is not open for rewards.' }; | |
| if (!user.walletAddress) return { error: `User ${githubUsername} has no wallet address.` }; | |
| if (issue.tokenReward <= 0) return { error: 'Issue has no token reward set.' }; | |
| if (issue.rewardAmount <= 0) return { error: 'Issue has no token reward set.' }; | |
| // 3. Security checks | |
| const securityResult = await performSecurityChecks({ | |
| userId: user.id, | |
| if (!issue) return { error: `Issue #${githubIssueId} not found in our database.` }; | |
| if (!user) return { error: `User '${githubUsername}' is not registered in our app.` }; | |
| const eligibleStatuses = ['OPEN', 'FUNDED']; | |
| if (!eligibleStatuses.includes(issue.status)) { | |
| return { error: `Issue status '${issue.status}' is not eligible for rewards.` }; | |
| } | |
| if (!user.walletAddress) return { error: `User ${githubUsername} has no wallet address.` }; | |
| if (issue.rewardAmount <= 0) return { error: 'Issue has no token reward set.' }; | |
| // 3. Security checks | |
| const securityResult = await performSecurityChecks({ |
🤖 Prompt for AI Agents
In actions/rewardContributorForIssue.js around lines 76 to 84, the status guard
currently rejects any non-'OPEN' issue which prevents issues marked 'FUNDED' by
the approval flow from proceeding; modify the check to allow 'FUNDED' (and any
equivalent accepted status) by changing the condition to only error when
issue.status is not one of the allowed statuses (e.g., ['OPEN', 'FUNDED']), and
update the error message to reflect that the issue must be in an allowed state
for rewards.
| // Verify the transaction on Solana blockchain | ||
| const isTransactionValid = await verifyTransaction(transactionSignature); | ||
| if (!isTransactionValid) { | ||
| return NextResponse.json( | ||
| { error: "Transaction verification failed" }, | ||
| { status: 400 } | ||
| ); | ||
| } |
There was a problem hiding this comment.
Transaction verification is ineffective.
verifyTransaction (see lib/solana-utils.js) only checks that the signature exists; it never confirms the payer, recipient, or amount. An attacker can therefore submit any old signature and mark an issue as funded without moving funds to the treasury. Use the hardened integrity helper (or fetch the transaction details yourself) and assert the recipient and amount before accepting the approval.
🤖 Prompt for AI Agents
In api/organization/approve-reward/route.js around lines 28-35, the current
verifyTransaction call only checks signature existence and is insufficient;
replace it with a hardened verification that fetches the transaction details (or
use the provided hardened integrity helper) and assert that the transaction
signature matches the supplied signature, the payer and recipient public keys
match the expected payer/treasury addresses, and the transferred lamports/amount
equals the expected reward amount before returning success; if any of these
checks fail, return a 400 with a clear error.
| tokenReward: rewardAmount, | ||
| // Add additional fields to store reward metadata | ||
| }, | ||
| }); | ||
| } else { | ||
| // Create new issue (if coming from direct approval flow) | ||
| issue = await prisma.issue.create({ | ||
| data: { | ||
| title: issueTitle, | ||
| githubIssueId: BigInt(Date.now()), // Temporary - should be actual GitHub issue ID | ||
| number: Math.floor(Math.random() * 10000), // Temporary - should be actual issue number | ||
| tokenReward: rewardAmount, |
There was a problem hiding this comment.
Updating tokenReward will crash with the new schema.
The Prisma model now exposes rewardAmount; tokenReward no longer exists. This upsert will throw Unknown arg 'tokenReward' in data.tokenReward, breaking the approval flow. Update both the update and create branches to write rewardAmount (and coerce to a number/Decimal as needed).
🤖 Prompt for AI Agents
In api/organization/approve-reward/route.js around lines 44 to 55, the upsert
uses the removed field tokenReward which will cause Prisma to throw Unknown arg
'tokenReward'; update both the update and create branches to write the new field
rewardAmount instead and ensure you coerce the incoming value to the model type
(e.g., Number or Prisma Decimal) before passing it into data.rewardAmount so the
upsert succeeds.
| repository: { | ||
| // This would need to be connected to actual repository | ||
| // For now, this is a placeholder | ||
| connect: { id: 1 } // You'll need to adjust this based on your repository logic | ||
| } | ||
| }, |
There was a problem hiding this comment.
Hard-coded repository linkage corrupts data.
Creating an issue with connect: { id: 1 } will either fail (repo 1 missing) or attach every new issue to the same repository, which is incorrect. Pass the actual repository ID from the request (and validate it) instead of using a placeholder.
| if (formData.tokenType === "EOS") { | ||
| // For EOS token, check if token account exists first | ||
| const userBalance = await getEosBalance(publicKey.toString()); | ||
| console.log('User EOS balance:', userBalance); | ||
|
|
||
| if (userBalance < amount) { | ||
| throw new Error(`Insufficient EOS balance. You have ${userBalance} EOS but trying to transfer ${amount} EOS`); | ||
| } | ||
|
|
||
| // Use SPL token transfer for EOS | ||
| transaction = await createTokenTransferTransaction( | ||
| publicKey.toString(), | ||
| treasuryAddress, | ||
| eosUtils.getMintAddress(), | ||
| Math.floor(amount * (10 ** eosUtils.getDecimals())) // Convert to token units | ||
| ); | ||
| } else if (formData.tokenType === "SOL") { | ||
| // Check SOL balance | ||
| const userBalance = await getSolBalance(publicKey.toString()); | ||
| console.log('User SOL balance:', userBalance); | ||
|
|
||
| if (userBalance < amount + 0.01) { // Include fee | ||
| throw new Error(`Insufficient SOL balance. You have ${userBalance} SOL but trying to transfer ${amount} SOL plus fees`); | ||
| } | ||
|
|
||
| transaction = await createSolTransferTransaction( | ||
| publicKey.toString(), | ||
| treasuryAddress, | ||
| amount | ||
| ); | ||
| } else { | ||
| // For other SPL tokens | ||
| const userBalance = await getTokenBalance(publicKey.toString(), formData.tokenMintAddress); | ||
| console.log('User token balance:', userBalance); | ||
|
|
||
| if (userBalance < amount) { | ||
| throw new Error(`Insufficient token balance. You have ${userBalance} tokens but trying to transfer ${amount} tokens`); | ||
| } | ||
|
|
||
| transaction = await createTokenTransferTransaction( | ||
| publicKey.toString(), | ||
| treasuryAddress, | ||
| formData.tokenMintAddress, | ||
| amount | ||
| ); |
There was a problem hiding this comment.
Compare token balances in the correct units before sending
getEosBalance/getTokenBalance return raw token units (lamports). Comparing those lamports directly to the human-entered amount causes the insufficient-balance guard to pass even when the wallet can’t actually cover the transfer (e.g., 1 EOS = 1,000,000 lamports). Parse the on-chain balance (or convert the requested amount to raw units) before the comparison, and do the same for other SPL tokens. Based on lib/solana-utils.js, lines 352-367.
- const userBalance = await getEosBalance(publicKey.toString());
- console.log('User EOS balance:', userBalance);
-
- if (userBalance < amount) {
+ const userBalanceLamports = await getEosBalance(publicKey.toString());
+ const userBalance = eosUtils.parseAmount(userBalanceLamports);
+ console.log('User EOS balance:', userBalance);
+
+ if (userBalance < amount) {
@@
- const userBalance = await getTokenBalance(publicKey.toString(), formData.tokenMintAddress);
- console.log('User token balance:', userBalance);
-
- if (userBalance < amount) {
+ const userBalanceLamports = await getTokenBalance(publicKey.toString(), formData.tokenMintAddress);
+ console.log('User token balance (raw units):', userBalanceLamports);
+ const requiredLamports = amount * Math.pow(10, eosUtils.getDecimals());
+ if (userBalanceLamports < requiredLamports) {(Adjust the SPL conversion to use the correct decimals for the selected mint.)
| export function ThemeProvider({ children, ...props }) { | ||
| const [mounted, setMounted] = React.useState(false); | ||
|
|
||
| React.useEffect(() => { | ||
| setMounted(true); | ||
| }, []); | ||
|
|
||
| if (!mounted) { | ||
| return children; | ||
| } | ||
|
|
||
| return <NextThemesProvider {...props}>{children}</NextThemesProvider>; |
There was a problem hiding this comment.
Keep NextThemesProvider mounted on first render.
By returning children before the component mounts, you remove the NextThemesProvider wrapper during the initial render. Any descendant calling useTheme() will now throw (“useTheme must be used within ”), breaking the app at runtime. Please keep the provider present on the first render (e.g., revert to always rendering NextThemesProviderand handle hydration viasuppressHydrationWarning` or a hidden placeholder) so the context is available immediately.
🤖 Prompt for AI Agents
In components/theme-provider.jsx around lines 6 to 17, the current early return
of children before mounted removes the NextThemesProvider on initial render and
breaks useTheme hooks; remove the conditional return so NextThemesProvider is
always rendered, and handle hydration instead by passing
suppressHydrationWarning to the provider or wrapping children in a placeholder
(e.g., render children with visibility hidden or aria-hidden until mounted) so
the provider/context exists immediately while avoiding flicker.
| }; | ||
|
|
||
| const metadataUri = "https://raw.githubusercontent.com/naveenkumar29052006/eos/main/metadata.json"; | ||
| }const metadataUri = "https://raw.githubusercontent.com/naveenkumar29052006/eos/main/metadata.json"; |
There was a problem hiding this comment.
Fix syntax error after getTokenBalance definition
metadataUri is declared immediately after the closing brace without a separator (}const ...), which is a syntax error and will prevent this module from compiling. Insert a newline or semicolon before defining metadataUri.
-}const metadataUri = "https://raw.githubusercontent.com/naveenkumar29052006/eos/main/metadata.json";
+}
+
+const metadataUri = "https://raw.githubusercontent.com/naveenkumar29052006/eos/main/metadata.json";📝 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 metadataUri = "https://raw.githubusercontent.com/naveenkumar29052006/eos/main/metadata.json"; | |
| } | |
| const metadataUri = "https://raw.githubusercontent.com/naveenkumar29052006/eos/main/metadata.json"; |
🤖 Prompt for AI Agents
In EOS/eostoken.jsx around line 42, there is a syntax error where the closing
brace of the previous function and the declaration of metadataUri are run
together ("}const metadataUri..."); separate them by inserting a newline or a
semicolon after the closing brace so metadataUri is declared on its own
statement (e.g., place a newline before const metadataUri or add a semicolon
after the brace).
| * Get EOS token balance of a wallet | ||
| * @param {string} walletAddress - Wallet address to check balance | ||
| * @returns {Promise<{balance: number, usdValue: number}>} EOS balance and USD value | ||
| */ | ||
| export async function getEosBalance(walletAddress) { | ||
| try { | ||
| if (!walletAddress) { | ||
| return 0; | ||
| } | ||
| const balance = await getTokenBalance(walletAddress, eosUtils.getMintAddress()); | ||
| return balance || 0; | ||
| } catch (error) { | ||
| console.error('Error getting EOS balance:', error); | ||
| // If it's a token account not found error, return 0 | ||
| if (error.name === 'TokenAccountNotFoundError' || error.message.includes('TokenAccountNotFoundError')) { | ||
| return 0; | ||
| } | ||
| return 0; | ||
| } |
There was a problem hiding this comment.
Correct EOS balance scaling and error handling
getTokenBalance returns lamport units, so getEosBalance currently reports balances that are 10⁶ too large and inflates the USD value downstream. The doc comment also promises an object it doesn’t return. Normalize the balance with eosUtils.parseAmount and guard the error handling.
- * @returns {Promise<{balance: number, usdValue: number}>} EOS balance and USD value
+ * @returns {Promise<number>} EOS balance in EOS units
@@
- const balance = await getTokenBalance(walletAddress, eosUtils.getMintAddress());
- return balance || 0;
+ const rawBalance = await getTokenBalance(walletAddress, eosUtils.getMintAddress());
+ return eosUtils.parseAmount(rawBalance || 0);
@@
- if (error.name === 'TokenAccountNotFoundError' || error.message.includes('TokenAccountNotFoundError')) {
+ if (
+ error.name === "TokenAccountNotFoundError" ||
+ error.message?.includes("TokenAccountNotFoundError")
+ ) {📝 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.
| * Get EOS token balance of a wallet | |
| * @param {string} walletAddress - Wallet address to check balance | |
| * @returns {Promise<{balance: number, usdValue: number}>} EOS balance and USD value | |
| */ | |
| export async function getEosBalance(walletAddress) { | |
| try { | |
| if (!walletAddress) { | |
| return 0; | |
| } | |
| const balance = await getTokenBalance(walletAddress, eosUtils.getMintAddress()); | |
| return balance || 0; | |
| } catch (error) { | |
| console.error('Error getting EOS balance:', error); | |
| // If it's a token account not found error, return 0 | |
| if (error.name === 'TokenAccountNotFoundError' || error.message.includes('TokenAccountNotFoundError')) { | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| * Get EOS token balance of a wallet | |
| * @param {string} walletAddress - Wallet address to check balance | |
| * @returns {Promise<number>} EOS balance in EOS units | |
| */ | |
| export async function getEosBalance(walletAddress) { | |
| try { | |
| if (!walletAddress) { | |
| return 0; | |
| } | |
| const rawBalance = await getTokenBalance(walletAddress, eosUtils.getMintAddress()); | |
| return eosUtils.parseAmount(rawBalance || 0); | |
| } catch (error) { | |
| console.error('Error getting EOS balance:', error); | |
| // If it's a token account not found error, return 0 | |
| if ( | |
| error.name === "TokenAccountNotFoundError" || | |
| error.message?.includes("TokenAccountNotFoundError") | |
| ) { | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| } |
🤖 Prompt for AI Agents
In lib/solana-utils.js around lines 349 to 367, the function currently returns
raw lamports and a primitive number (or 0) while the docstring promises an
object and the balance must be scaled; update it to return an object { balance,
usdValue } and normalize the raw token amount using
eosUtils.parseAmount(rawBalance) (so lamports -> tokens), return { balance: 0,
usdValue: 0 } when walletAddress is falsy, and when catching errors guard
accesses (check error exists before reading name/message), keep the
TokenAccountNotFoundError branch to return { balance: 0, usdValue: 0 }, and
ensure the caught error is logged via console.error before returning.
| ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| ADD COLUMN "githubUrl" TEXT, | ||
| ADD COLUMN "organizationWallet" TEXT, | ||
| ADD COLUMN "rewardAmount" DECIMAL(18,9) NOT NULL, | ||
| ADD COLUMN "tokenMintAddress" TEXT, | ||
| ADD COLUMN "tokenType" TEXT, | ||
| ADD COLUMN "transactionSignature" TEXT, | ||
| ADD COLUMN "treasuryWallet" TEXT, | ||
| ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL, | ||
| ALTER COLUMN "id" DROP DEFAULT, |
There was a problem hiding this comment.
Backfill or default new required columns before altering issues.
rewardAmount and updatedAt are added as NOT NULL with no default, so this migration will fail immediately on any existing data in issues. Please add a default/backfill (e.g., populate with UPDATE ... before enforcing NOT NULL, or add a default such as DEFAULT 0/DEFAULT CURRENT_TIMESTAMP and drop it afterwards) so the migration succeeds on production datasets.
🤖 Prompt for AI Agents
In
prisma/migrations/20251004214857_update_issue_schema_for_rewards/migration.sql
around lines 22 to 31, the migration adds rewardAmount and updatedAt as NOT NULL
with no default which will fail on existing rows; fix by adding the columns as
nullable or with a safe temporary default, backfill existing rows, then enforce
NOT NULL: 1) add rewardAmount with DEFAULT 0 (or nullable) and updatedAt with
DEFAULT CURRENT_TIMESTAMP (or nullable); 2) run an UPDATE to set sensible values
for all existing issues; 3) ALTER the columns to SET NOT NULL; 4) if you used
temporary defaults, ALTER to DROP DEFAULT afterward.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
package.json (1)
15-21: Remove duplicate dependency.
@radix-ui/react-slotis declared twice (lines 15 and 21). Remove the duplicate entry to avoid confusion.Apply this diff to remove the duplicate:
"@radix-ui/react-avatar": "^1.1.10", - "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-dialog": "^1.1.15",actions/userProfile.js (2)
88-94: Update field reference from tokenReward to rewardAmount.The code still references
issue.tokenRewardwhich was renamed toissue.rewardAmountin the Prisma schema (schema.prisma line 92). This will cause runtime errors.Apply this diff to fix the field references:
const contributedRepos = contributions.map(contribution => ({ repo: contribution.issue.repository.name, issue: contribution.issue.title, - reward: `${Number(contribution.issue.tokenReward)}x`, + reward: `${Number(contribution.issue.rewardAmount)}x`, date: new Date(contribution.completedAt).toLocaleDateString('en-GB'), // Formats date as DD/MM/YYYY status: 'Success', // Assuming all logged contributions were successful })); // 3. Calculate total earned tokens from the same data - const totalEarned = contributions.reduce((sum, c) => sum + Number(c.issue.tokenReward), 0); + const totalEarned = contributions.reduce((sum, c) => sum + Number(c.issue.rewardAmount), 0);
138-138: Update SQL query to use rewardAmount column.The raw SQL query still references the
tokenRewardcolumn which was renamed torewardAmountin the database schema. This will cause a SQL error at runtime.Apply this diff to fix the column reference:
SELECT u."githubUsername" AS "name", u."githubAvatarUrl" AS "photo", - SUM(i."tokenReward") AS "points", + SUM(i."rewardAmount") AS "points", COUNT(c.id) AS "issuesSolved" FROM "users" u JOIN "contributions" c ON u.id = c."contributorId"
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
actions/userProfile.js(1 hunks)package.json(1 hunks)prisma/schema.prisma(3 hunks)
🔇 Additional comments (4)
package.json (1)
12-12: Inconsistent with AI-generated summary.The summary indicates that
@clerk/nextjswas removed, but it's still present in the dependencies. Verify whether this package should remain or be removed.prisma/schema.prisma (2)
22-22: LGTM!The addition of the
FUNDEDstatus to theIssueStatusenum aligns with the reward workflow changes introduced in this PR.
92-97: LGTM!The new reward-related fields (
rewardAmount,tokenType,tokenMintAddress,organizationWallet,treasuryWallet,transactionSignature) appropriately support the reward workflow. TheDecimal(18, 9)precision forrewardAmountis suitable for token amounts.actions/userProfile.js (1)
232-238: LGTM!The field name change from
tokenRewardtorewardAmountinsetIssueBountycorrectly aligns with the Prisma schema updates.
|
|
||
| repoId Int | ||
| repository Repository @relation(fields: [repoId], references: [id], onDelete: Cascade) | ||
| id String @id @default(cuid()) // Changed to String to support custom IDs |
There was a problem hiding this comment.
🧩 Analysis chain
Verify data migration for id type change.
Changing the Issue.id from Int @id @default(autoincrement()) to String @id @default(cuid()) is a breaking change that affects existing data and foreign key relationships.
Ensure that:
- A migration strategy exists to convert existing integer IDs to string IDs
- All foreign key references (e.g.,
Contribution.issueIdat line 114) are updated consistently - Application code that queries or manipulates
Issue.idis updated to handle string IDs
Run the following script to verify all references to Issue.id in the codebase:
🏁 Script executed:
#!/bin/bash
# Description: Find all references to Issue.id to verify they handle string IDs correctly
# Search for Issue.id references
rg -nP --type=js --type=ts -C3 '\bIssue\.id\b|\bissueId\b'
# Search for Prisma queries that filter by id
rg -nP --type=js --type=ts -C3 'issue.*where.*\bid\b'
# Search for numeric comparisons that might break with string IDs
ast-grep --pattern 'issue.id $_ $_'Length of output: 7754
🏁 Script executed:
#!/bin/bash
if [ -d "prisma/migrations" ]; then
echo "Migration directories:"
find prisma/migrations -maxdepth 2 -type d
else
echo "No prisma/migrations directory found"
fiLength of output: 143
🏁 Script executed:
#!/bin/bash
cat prisma/migrations/20251004214857_update_issue_schema_for_rewards/migration.sqlLength of output: 1745
Fix migration and code references for string IDs and new fields.
- Migration
20251004214857_update_issue_schema_for_rewardsaddsrewardAmountandupdatedAtas NOT NULL without defaults or backfill—addDEFAULTclauses or a data-backfill step to avoid migration failures. - In
api/organization/approve-reward/route.js(line 42), removeparseInt(issueId)—use the string ID directly—and replace the obsoletetokenRewardreference withrewardAmount. - Audit all other Prisma calls/filtering on
Issue.idto ensure they handle string IDs.
🤖 Prompt for AI Agents
In prisma/schema.prisma around line 86 (Issue.id changed to String via
@default(cuid())), update the migration
20251004214857_update_issue_schema_for_rewards to avoid NOT NULL failures by
either adding DEFAULT values for rewardAmount and updatedAt (e.g., DEFAULT 0 for
numeric and DEFAULT now() for timestamp) or include an explicit backfill step in
the migration that sets existing rows to a safe value before making the columns
NOT NULL; in api/organization/approve-reward/route.js at line ~42 remove
parseInt(issueId) and treat issueId as the string ID, replace any usage of
tokenReward with rewardAmount, and ensure the Prisma query uses the string ID;
finally audit all other Prisma queries/filters that reference Issue.id to accept
string IDs (remove parseInt/casts, update typings if necessary) and ensure any
clients or tests that assumed numeric IDs are updated accordingly.
| githubIssueId BigInt? @unique // Made optional | ||
| number Int? // Made optional |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Verify handling of newly optional fields.
Several fields have been made optional (githubIssueId, number, repoId) and the repository relation is now nullable. Ensure that application code properly handles these optional fields.
Run the following script to find code that might assume these fields are non-null:
Also applies to: 101-102
🏁 Script executed:
#!/bin/bash
# Description: Find code that accesses these fields without null checks
# Search for direct property access without null checks
rg -nP --type=js --type=ts -C3 'issue\.githubIssueId(?!\?)'
rg -nP --type=js --type=ts -C3 'issue\.number(?!\?)'
rg -nP --type=js --type=ts -C3 'issue\.repository\.(?!\?)'
# Search for Prisma queries that assume these fields exist
rg -nP --type=js --type=ts -C3 'where.*githubIssueId.*:'Length of output: 643
🏁 Script executed:
#!/bin/bash
# Search for direct access to optional fields number and repository
rg -n --type js --type ts 'issue\.number' -C3
rg -n --type js --type ts 'issue\.repository' -C3
# Search for Prisma queries assuming non-null number or repoId
rg -n --type js --type ts 'where.*number.*:' -C3
rg -n --type js --type ts 'where.*repoId.*:' -C3Length of output: 1012
Guard accesses to newly optional fields
- prisma upsert in actions/userProfile.js (ln.231–234) uses
githubIssueIdandrepoIdwithout null checks - rewardContributorForIssue.js (ln.114) accesses
issue.numberunconditionally - userProfile.js (ln.86) reads
contribution.issue.repository.nameassuming non-nullrepository
Validate or default these fields before use.
🤖 Prompt for AI Agents
In prisma/schema.prisma around lines 88–89 the fields githubIssueId and number
were made optional; update the call sites: in actions/userProfile.js (around
lines 231–234) guard githubIssueId and repoId before including them in the
Prisma upsert—either omit those fields from the upsert data when null/undefined
or provide safe defaults; in rewardContributorForIssue.js (around line 114)
check that issue.number is not null/undefined before using it and handle the
missing-number case (early return or alternative flow); in userProfile.js
(around line 86) validate that contribution.issue.repository exists before
accessing .name (use optional chaining or an explicit null check and provide a
fallback). Ensure all changes avoid runtime null dereferences and only pass
defined values into Prisma operations.
Summary by CodeRabbit
New Features
API
Security
Documentation
Database
Chores