Skip to content

Conversation

@sporicle
Copy link
Contributor

@sporicle sporicle commented Dec 1, 2025

Summary by CodeRabbit

  • New Features

    • Added delegated dice rolling functionality with Solana validator integration, allowing users to delegate transactions and roll dice through a feature-rich dashboard with roll history tracking.
    • Enhanced wallet management with improved keypair persistence and centralized configuration.
    • Added custom scrollbar styling for improved UI presentation.
  • Bug Fixes

    • Fixed typo in README documentation.
  • Chores

    • Updated dependencies and added deployment configuration.

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

@vercel
Copy link

vercel bot commented Dec 1, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
er-rolldice Error Error Dec 1, 2025 6:19pm

@coderabbitai
Copy link

coderabbitai bot commented Dec 1, 2025

Caution

Review failed

Failed to post review comments

Walkthrough

This pull request adds comprehensive delegated dice rolling functionality to a Solana program. Changes include a new React page component for delegated rolling, shared Solana utilities and configuration constants, updates to the existing dice roller page, program modifications with new delegate parameters and account structures, expanded test coverage for delegation flows, and dependency version bumps for ephemeral rollups SDK support.

Changes

Cohort / File(s) Summary
Documentation & Configuration
roll-dice/README.md, roll-dice/vercel.json
Fixed typo in README table header; added Vercel deployment configuration with build and output directory settings.
Shared Utilities & Types
roll-dice/app/lib/config.ts, roll-dice/app/lib/solana-utils.ts, roll-dice/app/lib/types.ts, roll-dice/app/lib/delegate-instruction.ts
Introduced configuration constants for Solana program IDs, seeds, endpoints, and timeouts; added utility functions for keypair management, blockhash caching, delegation status checks, and wallet adapter creation; defined RollEntry and CachedBlockhash types; implemented delegate instruction builder with PDA derivation.
UI Components
roll-dice/app/app/page.tsx, roll-dice/app/app/delegated/page.tsx
Refactored existing DiceRoller to use centralized config and shared wallet adapter; added new DiceRollerDelegated component with comprehensive client-side logic for delegation flow, ephemeral connections, blockhash management, and delegated transaction submission.
Styling
roll-dice/app/app/globals.css
Added custom scrollbar styling rules for .custom-scrollbar class with WebKit and standard scrollbar selectors.
Dependencies
roll-dice/app/package.json, roll-dice/package.json, roll-dice/programs/roll-dice-delegated/Cargo.toml
Added @coral-xyz/anchor, @magicblock-labs/ephemeral-rollups-sdk, and @metaplex-foundation/beet to npm dependencies; updated Rust crate versions for ephemeral-rollups-sdk to 0.4.1 and ephemeral-vrf-sdk to 0.1.2.
Solana Program
roll-dice/programs/roll-dice-delegated/src/lib.rs
Updated program ID; modified delegate function to accept DelegateParams; added rollnum field to Player struct; increased account initialization space; added player account to randomness request; updated callback to mutate player account state.
Tests
roll-dice/tests/roll-dice-delegated.ts
Expanded test setup with delegated payer keypair and PDA computation; added test phases for delegation, delegated rolling, and undelegation flows; updated test endpoints and added explicit account/signer specifications.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Delegated page component (roll-dice/app/app/delegated/page.tsx): Dense React component with multiple concurrent operations (blockhash refresh loops, subscription listeners, delegation/undelegation flows); requires careful review of state management, cleanup semantics, and error handling paths.
  • Solana program changes (roll-dice/programs/roll-dice-delegated/src/lib.rs): Structural updates to Player account, delegate function signature change, and mutable state mutations in callback require verification of account validation and security implications.
  • Blockhash caching logic (roll-dice/app/lib/solana-utils.ts): Cache invalidation timing and background refresh patterns need verification to ensure transaction freshness.
  • Delegation instruction builder (roll-dice/app/lib/delegate-instruction.ts): PDA derivation, optional account handling, and instruction serialization require correctness verification.
  • Test expansion (roll-dice/tests/roll-dice-delegated.ts): New delegation and ephemeral provider flows require validation of account state transitions and signer configurations.

Possibly related PRs

Suggested reviewers

  • jonasXchen

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The PR title 'added ephemeral vrf example' is vague and overly broad. The changeset involves substantial new functionality including a delegated dice rolling page, Solana integration utilities, instruction builders, configuration, and test updates—far more than a simple example addition. Consider a more descriptive title that captures the main change, such as 'Add delegated dice rolling with Solana ephemeral provider integration' or similar.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch shuffle

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.

Copy link

@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: 22

Caution

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

⚠️ Outside diff range comments (2)
roll-dice/README.md (1)

9-9: Remove the extraneous trailing slash in the URL.

The link destination has a double trailing slash (https://roll-dice-demo.vercel.app//), which is inconsistent with standard URL formatting. Update to a single trailing slash for consistency.

Apply this diff to fix the URL:

-[https://roll-dice-demo.vercel.app/](https://roll-dice-demo.vercel.app//)
+[https://roll-dice-demo.vercel.app/](https://roll-dice-demo.vercel.app/)
roll-dice/app/package.json (1)

43-43: Avoid using "latest" for @solana/web3.js.

Using "latest" can cause unpredictable build failures when breaking changes are released. The Solana web3.js library recently underwent a major rewrite (v2.x), which has significant API differences from v1.x.

Pin to a specific version to ensure reproducible builds:

-    "@solana/web3.js": "latest",
+    "@solana/web3.js": "^1.98.0",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5fe82fa and 4a4a1f4.

⛔ Files ignored due to path filters (3)
  • roll-dice/Cargo.lock is excluded by !**/*.lock
  • roll-dice/app/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • roll-dice/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (14)
  • roll-dice/README.md (1 hunks)
  • roll-dice/app/app/delegated/page.tsx (1 hunks)
  • roll-dice/app/app/globals.css (1 hunks)
  • roll-dice/app/app/page.tsx (4 hunks)
  • roll-dice/app/lib/config.ts (1 hunks)
  • roll-dice/app/lib/delegate-instruction.ts (1 hunks)
  • roll-dice/app/lib/solana-utils.ts (1 hunks)
  • roll-dice/app/lib/types.ts (1 hunks)
  • roll-dice/app/package.json (1 hunks)
  • roll-dice/package.json (1 hunks)
  • roll-dice/programs/roll-dice-delegated/Cargo.toml (1 hunks)
  • roll-dice/programs/roll-dice-delegated/src/lib.rs (6 hunks)
  • roll-dice/tests/roll-dice-delegated.ts (3 hunks)
  • roll-dice/vercel.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
roll-dice/app/app/delegated/page.tsx (8)
roll-dice/app/lib/config.ts (7)
  • PROGRAM_ID (3-3)
  • ORACLE_QUEUE (6-6)
  • BASE_ENDPOINT (7-7)
  • PLAYER_STORAGE_KEY (8-8)
  • BLOCKHASH_REFRESH_INTERVAL_MS (12-12)
  • ROLL_ANIMATION_INTERVAL_MS (14-14)
  • ROLL_TIMEOUT_MS (13-13)
roll-dice/app/lib/types.ts (2)
  • RollEntry (1-7)
  • CachedBlockhash (9-13)
roll-dice/app/lib/solana-utils.ts (6)
  • getCachedBlockhash (55-70)
  • checkDelegationStatus (72-78)
  • walletAdapterFrom (6-18)
  • fetchAndCacheBlockhash (39-53)
  • loadOrCreateKeypair (20-29)
  • ensureFunds (31-37)
roll-dice/app/components/solana-address.tsx (1)
  • SolanaAddress (8-116)
roll-dice/app/components/ui/card.tsx (3)
  • Card (79-79)
  • CardTitle (79-79)
  • CardContent (79-79)
roll-dice/app/components/ui/accordion.tsx (4)
  • Accordion (58-58)
  • AccordionItem (58-58)
  • AccordionTrigger (58-58)
  • AccordionContent (58-58)
roll-dice/app/components/ui/badge.tsx (1)
  • Badge (36-36)
roll-dice/app/components/ui/table.tsx (6)
  • Table (109-109)
  • TableHeader (110-110)
  • TableRow (114-114)
  • TableHead (113-113)
  • TableBody (111-111)
  • TableCell (115-115)
roll-dice/app/lib/solana-utils.ts (2)
roll-dice/app/lib/config.ts (2)
  • MIN_BALANCE_LAMPORTS (10-10)
  • BLOCKHASH_CACHE_MAX_AGE_MS (11-11)
roll-dice/app/lib/types.ts (1)
  • CachedBlockhash (9-13)
roll-dice/app/app/page.tsx (2)
roll-dice/app/lib/solana-utils.ts (2)
  • loadOrCreateKeypair (20-29)
  • walletAdapterFrom (6-18)
roll-dice/app/lib/config.ts (4)
  • PLAYER_STORAGE_KEY (8-8)
  • BASE_ENDPOINT (7-7)
  • PROGRAM_ID_STANDARD (4-4)
  • PLAYER_SEED (5-5)
roll-dice/tests/roll-dice-delegated.ts (1)
roll-dice/app/lib/delegate-instruction.ts (1)
  • createDelegateInstruction (37-105)
roll-dice/programs/roll-dice-delegated/src/lib.rs (1)
anchor-counter/programs/anchor-counter/src/lib.rs (2)
  • commit (50-58)
  • delegate (36-47)
🪛 Biome (2.1.2)
roll-dice/app/app/delegated/page.tsx

[error] 873-876: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 873-876: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 888-892: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)


[error] 896-900: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)


[error] 903-907: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)


[error] 910-914: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)


[error] 918-922: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)


[error] 954-958: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)


[error] 968-980: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test-examples
🔇 Additional comments (14)
roll-dice/README.md (1)

13-13: Minor typo fix approved.

The correction from "sofware" to "software" improves documentation clarity. Good catch!

roll-dice/vercel.json (1)

1-5: LGTM!

The Vercel configuration correctly sets up the build command and output directory for the Next.js app located in the app subdirectory.

roll-dice/app/lib/types.ts (1)

1-14: LGTM!

The type definitions are clean and appropriate for their intended use cases—tracking roll history entries and caching blockhash data for transaction signing.

roll-dice/programs/roll-dice-delegated/Cargo.toml (1)

25-26: SDK versions are published and compatible.

Both [email protected] and [email protected] are available on crates.io. The dependency updates align with the broader PR changes.

roll-dice/app/lib/delegate-instruction.ts (1)

58-60: No action required — payer account writability is configured correctly.

In Solana, payer accounts do not need to be marked isWritable: true in instruction metadata. Runtime fees and rent deductions are handled automatically, and when a program uses CPI to create accounts (via SystemProgram), the inner instruction handles payer writability. The payer is correctly marked as isSigner: true for authorization purposes.

Likely an incorrect or invalid review comment.

roll-dice/tests/roll-dice-delegated.ts (1)

145-200: Disabled test for undelegating on-curve account.

This test is marked with xit (disabled). If this functionality is required, consider enabling and completing it. If it's intentionally disabled for now, add a comment explaining why.

roll-dice/app/lib/config.ts (1)

1-14: LGTM overall structure.

The centralized configuration module is well-organized with clear constant definitions for program IDs, seeds, endpoints, and timing values. This promotes maintainability and reduces magic values scattered across the codebase.

roll-dice/app/lib/solana-utils.ts (1)

31-37: Airdrop only works on devnet/testnet.

requestAirdrop will fail on mainnet. Consider adding a network check or documenting this limitation clearly. For production use, this function should be disabled or replaced with a different funding mechanism.

 export const ensureFunds = async (connection: Connection, keypair: Keypair): Promise<void> => {
   const balance = await connection.getBalance(keypair.publicKey)
   if (balance < MIN_BALANCE_LAMPORTS * LAMPORTS_PER_SOL) {
+    // Note: Airdrop only works on devnet/testnet, not mainnet
     const signature = await connection.requestAirdrop(keypair.publicKey, LAMPORTS_PER_SOL)
     await connection.confirmTransaction(signature, "confirmed")
   }
 }
roll-dice/programs/roll-dice-delegated/src/lib.rs (6)

1-13: LGTM!

Imports are appropriate for the VRF and ephemeral rollups functionality. The SerializableAccountMeta import supports the new account metadata in randomness requests.


37-41: LGTM!

The SerializableAccountMeta correctly identifies the player account for the callback, marking it as writable (required since the callback modifies player state) and non-signer.


63-74: LGTM!

The explicit DelegateParams struct provides a cleaner API compared to using remaining_accounts for validator configuration. The DelegateConfig is properly constructed from the provided parameters.


92-92: LGTM!

Space calculation is correct: 8 bytes discriminator + 2 bytes for Player data (1 byte last_result + 1 byte rollnum).


137-138: LGTM!

The #[account(mut)] constraint is correctly added since the callback modifies the player's rollnum and last_result fields.


166-170: LGTM!

DelegateParams correctly derives AnchorSerialize and AnchorDeserialize for use as instruction arguments, providing a clean interface for delegation configuration.

Comment on lines +249 to +275
subscriptionIdRef.current = newEphemeralConnection.onAccountChange(
playerPdaRef.current,
(accountInfo) => {
if (!ephemeralProgramRef.current || !accountInfo?.data) return
try {
const player = ephemeralProgramRef.current.coder.accounts.decode("player", accountInfo.data)
const newValue = Number(player.lastResult)
setPlayerAccountData({ lastResult: newValue, rollnum: Number(player.rollnum) })
if (newValue > 0) {
setDiceValue(newValue)
previousDiceValueRef.current = newValue
}
setRollHistory(prev => {
const idx = prev.findIndex(entry => entry.isPending)
if (idx === -1) return prev
const updated = [...prev]
updated[idx] = { ...updated[idx], value: newValue, endTime: Date.now(), isPending: false }
setIsRolling(false)
clearAllIntervals()
return updated
})
} catch (error) {
console.error("[WebSocket] Failed to decode player account:", error)
}
},
{ commitment: "processed" }
)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Duplicated WebSocket subscription handler logic.

The account change handler logic is duplicated between updateEphemeralConnectionToValidator (lines 249-275) and initializeProgram (lines 380-406). Extract this into a reusable helper function to improve maintainability.

const createAccountChangeHandler = () => (accountInfo: AccountInfo<Buffer>) => {
  if (!ephemeralProgramRef.current || !accountInfo?.data) return
  try {
    const player = ephemeralProgramRef.current.coder.accounts.decode("player", accountInfo.data)
    const newValue = Number(player.lastResult)
    setPlayerAccountData({ lastResult: newValue, rollnum: Number(player.rollnum) })
    if (newValue > 0) {
      setDiceValue(newValue)
      previousDiceValueRef.current = newValue
    }
    setRollHistory(prev => {
      const idx = prev.findIndex(entry => entry.isPending)
      if (idx === -1) return prev
      const updated = [...prev]
      updated[idx] = { ...updated[idx], value: newValue, endTime: Date.now(), isPending: false }
      setIsRolling(false)
      clearAllIntervals()
      return updated
    })
  } catch (error) {
    console.error("[WebSocket] Failed to decode player account:", error)
  }
}

Also applies to: 380-406

🤖 Prompt for AI Agents
In roll-dice/app/app/delegated/page.tsx around lines 249-275 (and similarly
380-406), the WebSocket account-change handler logic is duplicated; extract it
to a single reusable function (e.g., createAccountChangeHandler) that returns a
handler of type (accountInfo: AccountInfo<Buffer>) => void and closes over the
existing refs/state (ephemeralProgramRef, playerPdaRef, setPlayerAccountData,
setDiceValue, previousDiceValueRef, setRollHistory, setIsRolling,
clearAllIntervals), replace the duplicated blocks at both locations with calls
to newHandler = createAccountChangeHandler() and pass that handler to
onAccountChange with the same commitment option, and keep the try/catch and
console.error behavior intact so behavior does not change.

}
setIsDelegating(false)
}
}, [isDelegated, refreshDelegationStatus, clearAllIntervals, updateEphemeralConnectionToValidator, sendBackgroundRoll])
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Unused dependency clearAllIntervals in callback.

clearAllIntervals is listed in the dependency array for handleDelegateToValidator but is never called within the function body. Remove it from deps or use it if intended.

-  }, [isDelegated, refreshDelegationStatus, clearAllIntervals, updateEphemeralConnectionToValidator, sendBackgroundRoll])
+  }, [isDelegated, refreshDelegationStatus, updateEphemeralConnectionToValidator, sendBackgroundRoll])
📝 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.

Suggested change
}, [isDelegated, refreshDelegationStatus, clearAllIntervals, updateEphemeralConnectionToValidator, sendBackgroundRoll])
}, [isDelegated, refreshDelegationStatus, updateEphemeralConnectionToValidator, sendBackgroundRoll])
🤖 Prompt for AI Agents
In roll-dice/app/app/delegated/page.tsx around line 579, the dependency array
for the handleDelegateToValidator callback includes clearAllIntervals but that
function is never used inside the callback; remove clearAllIntervals from the
dependency array (or, if you intended to use it, call it inside the callback and
keep it in deps) and run the linter/tests to confirm no stale deps remain.

Comment on lines +639 to +644
// List of all known validator endpoints
const validatorEndpoints = [
"https://devnet-us.magicblock.app",
"https://devnet-as.magicblock.app",
"https://devnet-eu.magicblock.app",
]
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Hardcoded validator endpoints should use configuration.

The validator endpoints are hardcoded. Consider moving these to the config file alongside other endpoint constants for easier maintenance and environment-specific configuration.

+// In config.ts:
+export const VALIDATOR_ENDPOINTS = [
+  "https://devnet-us.magicblock.app",
+  "https://devnet-as.magicblock.app",
+  "https://devnet-eu.magicblock.app",
+]

// In page.tsx:
-const validatorEndpoints = [
-  "https://devnet-us.magicblock.app",
-  "https://devnet-as.magicblock.app",
-  "https://devnet-eu.magicblock.app",
-]
+import { VALIDATOR_ENDPOINTS } from "@/lib/config"

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In roll-dice/app/app/delegated/page.tsx around lines 639–644 the validator
endpoints are hardcoded; move them into configuration by adding a new constant
(e.g., VALIDATOR_ENDPOINTS) in the project config (or environment config) and
replace this inline array with an import from that config; support reading from
process.env (or NEXT_PUBLIC_ env) with a sensible default fallback so dev/prod
can differ, and update any type definitions/imports accordingly.


try {
const randomValue = Math.floor(Math.random() * 6) + 1
const connection = ephemeralConnectionRef.current!
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid non-null assertion; add guard clause.

ephemeralConnectionRef.current! uses a non-null assertion which could cause a runtime error if the ref is unexpectedly null. The guard at line 717 only checks ephemeralProgramRef, not ephemeralConnectionRef.

-    const connection = ephemeralConnectionRef.current!
+    const connection = ephemeralConnectionRef.current
+    if (!connection) {
+      console.error("[RollDice] Ephemeral connection not available")
+      setIsRolling(false)
+      return
+    }
🤖 Prompt for AI Agents
In roll-dice/app/app/delegated/page.tsx around line 764, avoid the non-null
assertion on ephemeralConnectionRef.current; add a guard check for
ephemeralConnectionRef (similar to the existing ephemeralProgramRef guard at
717) before using it. Replace the direct assignment with a guarded flow: if
ephemeralConnectionRef.current is null/undefined, handle it (early return, throw
a descriptive error, or log and exit) and only then assign const connection =
ephemeralConnectionRef.current without the !. Ensure the error message clearly
identifies that ephemeralConnectionRef is missing so callers can diagnose the
issue.

Comment on lines +873 to +879
<div
className="flex items-center gap-2 cursor-pointer hover:bg-gray-50 rounded px-2 py-1 transition-colors"
onClick={() => copyToClipboard(playerPda.toBase58())}
>
<span className="text-xs font-mono">{formatAddress(playerPda.toBase58())}</span>
{copied ? <Check className="h-3 w-3 text-green-500" /> : <Copy className="h-3 w-3 text-gray-400" />}
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add keyboard accessibility to clickable div.

The PDA address copy functionality uses a <div> with onClick but lacks keyboard support. Users navigating with keyboards cannot trigger this action. As flagged by static analysis.

 <div
   className="flex items-center gap-2 cursor-pointer hover:bg-gray-50 rounded px-2 py-1 transition-colors"
   onClick={() => copyToClipboard(playerPda.toBase58())}
+  onKeyDown={(e) => e.key === 'Enter' && copyToClipboard(playerPda.toBase58())}
+  role="button"
+  tabIndex={0}
 >
📝 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.

Suggested change
<div
className="flex items-center gap-2 cursor-pointer hover:bg-gray-50 rounded px-2 py-1 transition-colors"
onClick={() => copyToClipboard(playerPda.toBase58())}
>
<span className="text-xs font-mono">{formatAddress(playerPda.toBase58())}</span>
{copied ? <Check className="h-3 w-3 text-green-500" /> : <Copy className="h-3 w-3 text-gray-400" />}
</div>
<div
className="flex items-center gap-2 cursor-pointer hover:bg-gray-50 rounded px-2 py-1 transition-colors"
onClick={() => copyToClipboard(playerPda.toBase58())}
onKeyDown={(e) => e.key === 'Enter' && copyToClipboard(playerPda.toBase58())}
role="button"
tabIndex={0}
>
<span className="text-xs font-mono">{formatAddress(playerPda.toBase58())}</span>
{copied ? <Check className="h-3 w-3 text-green-500" /> : <Copy className="h-3 w-3 text-gray-400" />}
</div>
🧰 Tools
🪛 Biome (2.1.2)

[error] 873-876: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 873-876: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

🤖 Prompt for AI Agents
In roll-dice/app/app/delegated/page.tsx around lines 873 to 879, the clickable
PDA copy DIV only has an onClick handler and therefore lacks keyboard
accessibility; update the element to behave like a button by adding
role="button", tabIndex={0}, an aria-label (e.g., "Copy PDA address"), and an
onKeyDown handler that triggers the same copyToClipboard(playerPda.toBase58())
action when the Enter or Space keys are pressed; keep the existing onClick and
visual styling intact so both mouse and keyboard users can activate the control.

Comment on lines 7 to 10
"dependencies": {
"@coral-xyz/anchor": "0.32.1"
"@coral-xyz/anchor": "0.32.1",
"@magicblock-labs/ephemeral-rollups-sdk": "^0.4.1"
},
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

LGTM with minor note on version consistency.

The dependencies are appropriate for the ephemeral VRF integration. Consider using consistent version pinning strategy—@coral-xyz/anchor uses an exact version (0.32.1) while @magicblock-labs/ephemeral-rollups-sdk uses a caret range (^0.4.1).

🤖 Prompt for AI Agents
In roll-dice/package.json around lines 7 to 10, the dependency versioning is
inconsistent: "@coral-xyz/anchor" is pinned to "0.32.1" while
"@magicblock-labs/ephemeral-rollups-sdk" uses a caret "^0.4.1"; pick a
consistent versioning strategy and apply it to both dependencies (either pin
both exact versions or use caret ranges for both), updating the package.json
entries accordingly and run npm/yarn install to ensure lockfile consistency.

Comment on lines 49 to 60
pub fn callback_roll_dice_simple(
_ctx: Context<CallbackRollDiceSimpleCtx>,
ctx: Context<CallbackRollDiceSimpleCtx>,
randomness: [u8; 32],
) -> Result<()> {
let rnd_u8 = ephemeral_vrf_sdk::rnd::random_u8_with_range(&randomness, 1, 6);
msg!("Consuming random number: {:?}", rnd_u8);
player.rollnum = player.rollnum.saturating_add(1);
msg!("Roll number: {:?}", player.rollnum);
let player = &mut ctx.accounts.player;
player.last_result = rnd_u8;
Ok(())
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Variable used before declaration.

Lines 55-56 reference player before it is declared on line 57. This will fail to compile due to use of an undefined variable.

Apply this diff to fix the ordering:

 pub fn callback_roll_dice_simple(
     ctx: Context<CallbackRollDiceSimpleCtx>,
     randomness: [u8; 32],
 ) -> Result<()> {
     let rnd_u8 = ephemeral_vrf_sdk::rnd::random_u8_with_range(&randomness, 1, 6);
     msg!("Consuming random number: {:?}", rnd_u8);
+    let player = &mut ctx.accounts.player;
     player.rollnum = player.rollnum.saturating_add(1);
     msg!("Roll number: {:?}", player.rollnum);
-    let player = &mut ctx.accounts.player;
     player.last_result = rnd_u8;
     Ok(())
 }
📝 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.

Suggested change
pub fn callback_roll_dice_simple(
_ctx: Context<CallbackRollDiceSimpleCtx>,
ctx: Context<CallbackRollDiceSimpleCtx>,
randomness: [u8; 32],
) -> Result<()> {
let rnd_u8 = ephemeral_vrf_sdk::rnd::random_u8_with_range(&randomness, 1, 6);
msg!("Consuming random number: {:?}", rnd_u8);
player.rollnum = player.rollnum.saturating_add(1);
msg!("Roll number: {:?}", player.rollnum);
let player = &mut ctx.accounts.player;
player.last_result = rnd_u8;
Ok(())
}
pub fn callback_roll_dice_simple(
ctx: Context<CallbackRollDiceSimpleCtx>,
randomness: [u8; 32],
) -> Result<()> {
let rnd_u8 = ephemeral_vrf_sdk::rnd::random_u8_with_range(&randomness, 1, 6);
msg!("Consuming random number: {:?}", rnd_u8);
let player = &mut ctx.accounts.player;
player.rollnum = player.rollnum.saturating_add(1);
msg!("Roll number: {:?}", player.rollnum);
player.last_result = rnd_u8;
Ok(())
}
🤖 Prompt for AI Agents
In roll-dice/programs/roll-dice-delegated/src/lib.rs around lines 49-60, the
code references player before it is declared which causes a compile error; move
the declaration "let player = &mut ctx.accounts.player;" to before any use of
player (i.e., place it immediately after getting rnd_u8 and before the msg! and
player.rollnum modifications), then update subsequent accesses to use this
declared mutable reference so the borrow semantics remain correct.

import { LAMPORTS_PER_SOL } from "@solana/web3.js";
import { LAMPORTS_PER_SOL, Keypair, SystemProgram, Transaction, PublicKey } from "@solana/web3.js";
import { RandomDiceDelegated } from "../target/types/random_dice_delegated";
import * as crypto from "crypto";
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Unused import when test is disabled.

The crypto import is only used in the disabled xit test at line 145. Consider removing it or enabling the test.

🤖 Prompt for AI Agents
In roll-dice/tests/roll-dice-delegated.ts around line 5, the imported "crypto"
module is unused because the only test that references it is disabled (an xit at
~line 145); remove the unused import line to clean up linter/test warnings, or
alternatively re-enable the disabled test and ensure the crypto usage within
that test is correct — choose one: either delete the import statement at the
top, or change the test from xit to it and verify the test logic uses crypto as
intended.

const delegateTxHash = await provider.sendAndConfirm(new Transaction().add(delegateIx), [provider.wallet.payer,delegatedPayerKeypair]);
console.log("Delegate transaction signature:", delegateTxHash);

await new Promise(resolve => setTimeout(resolve, 5000));
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider extracting magic delay values.

The 5-second delays are hardcoded. Consider extracting these as constants or using a more robust approach like polling for expected state changes.

+const DELEGATION_PROPAGATION_DELAY_MS = 5000;
+
 // In the test:
-    await new Promise(resolve => setTimeout(resolve, 5000));
+    await new Promise(resolve => setTimeout(resolve, DELEGATION_PROPAGATION_DELAY_MS));

Also applies to: 124-124

🤖 Prompt for AI Agents
In roll-dice/tests/roll-dice-delegated.ts around lines 103 and 124, there are
hardcoded 5000ms delays; extract these magic numbers into a named constant
(e.g., const DEFAULT_WAIT_MS = 5000) at the top of the test file or test
utilities, and replace the inline setTimeout usages with that constant, or
better yet implement a small helper that polls for the expected state (e.g.,
waitFor(conditionFn, {timeout: DEFAULT_WAIT_MS, interval: 100})) and use it to
wait for specific conditions instead of fixed sleeps.

Comment on lines +165 to +174
const undelegateAccounts = [
{ pubkey: providerEphemeralRollup.wallet.publicKey, isSigner: true, isWritable: true }, // payer
{ pubkey: delegatedAccount, isSigner: false, isWritable: true }, // delegated_account
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, // owner_program (system for on-curve)
{ pubkey: delegationRecordPda, isSigner: false, isWritable: true }, // delegation_record
{ pubkey: delegationMetadataPda, isSigner: false, isWritable: true }, // delegation_metadata
{ pubkey: DELEGATION_PROGRAM_ID, isSigner: false, isWritable: false }, // delegation_program
{ pubkey: MAGIC_PROGRAM_ID, isSigner: false, isWritable: false }, // magic_program
{ pubkey: MAGIC_CONTEXT_ID, isSigner: false, isWritable: true }, // magic_context
];
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -n "undelegate" --type ts -C 3

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 22695


🏁 Script executed:

# Search for delegation program implementation and instruction definitions
rg -n "delegation" --type ts -i | head -50

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 5402


🏁 Script executed:

# Look for Anchor IDL or instruction definitions that might show signatures
fd -e json -e idl | xargs rg -l "undelegate" 2>/dev/null

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 70


🏁 Script executed:

# Check if there's a delegation program directory or related files
fd -type f -name "*delegat*" | grep -E "\.(ts|rs|json|idl)$"

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 256


🏁 Script executed:

# Look for oncurve-delegation instruction creation to see account signing patterns
rg -n "createCommitAndUndelegateInstruction" --type ts -A 10 | head -60

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 4157


🏁 Script executed:

# Search for delegation program IDL or type definitions
fd -type f \( -name "*.json" -o -name "*.ts" \) | xargs rg -l "undelegate" | grep -E "(idl|types|interface)" | head -10

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 256


🏁 Script executed:

# Look for the delegation program source or accounts interface
fd -type f -name "*delegat*" -path "*/src/*"

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 256


🏁 Script executed:

# Check the specific line in roll-dice-delegated.ts to see the full context
sed -n '130,190p' roll-dice/tests/roll-dice-delegated.ts

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 2780


🏁 Script executed:

# Search for createCommitAndUndelegateInstruction implementation
rg -n "createCommitAndUndelegateInstruction" --type ts -B 2 -A 15 | grep -A 15 "export\|function"

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 68


🏁 Script executed:

# Check if there's an SDK package in node_modules or imported from external package
rg "from.*ephemeral-rollups" roll-dice/tests/roll-dice-delegated.ts

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 134


🏁 Script executed:

# Look for any comments or documentation about undelegate account requirements
rg -n "delegated.*signer\|signer.*delegat" --type ts -i -B 2 -A 2

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 68


🏁 Script executed:

# Check if there's a working undelegate example (non-skipped test) that manually constructs accounts
rg -n "undelegateAccounts\|undelegateIx" --type ts -B 5 -A 2

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 68


🏁 Script executed:

# Look at the working undelegate test that uses Anchor methods
sed -n '120,144p' roll-dice/tests/roll-dice-delegated.ts

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 870


🏁 Script executed:

# Search for any delegation-related documentation or README
fd -type f \( -name "README*" -o -name "*.md" \) | xargs rg -l "undelegate\|delegation" -i 2>/dev/null

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 258


🏁 Script executed:

# Check for any helper functions that might configure accounts
rg -n "delegatedPayerKeypair\|delegatedAccount" roll-dice/tests/roll-dice-delegated.ts | head -20

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 68


🏁 Script executed:

# Look at test setup to see how delegatedPayerKeypair and delegatedAccount are defined
sed -n '1,100p' roll-dice/tests/roll-dice-delegated.ts

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 3860


🏁 Script executed:

# Search for all occurrences of delegatedAccount in the file
grep -n "delegatedAccount\|delegatedPayerKeypair" roll-dice/tests/roll-dice-delegated.ts

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 1761


🏁 Script executed:

# Check if there are any comments or patterns about PDA signing requirements
grep -n "delegation.*PDA\|PDA.*signer" roll-dice/tests/roll-dice-delegated.ts

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 68


🏁 Script executed:

# Look at the complete delegate instruction to see the pattern for comparison
sed -n '95,105p' roll-dice/tests/roll-dice-delegated.ts

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 552


delegatedAccount should be marked as isSigner: true.

The delegated account must sign the undelegation instruction. This follows the pattern established in the createDelegateInstruction (line 100) where delegatedPayerKeypair is included as a signer, and is also consistent with the working Anchor-based undelegate test (line 128) where delegatedWallet automatically includes it as a signer. Change line 167 from isSigner: false to isSigner: true.

🤖 Prompt for AI Agents
In roll-dice/tests/roll-dice-delegated.ts around lines 165 to 174, the
delegatedAccount entry in the undelegateAccounts array is incorrectly set with
isSigner: false; update that entry to isSigner: true and ensure the delegated
account's Keypair (delegatedPayerKeypair / delegatedWallet) is included in the
transaction/signers when sending the undelegate instruction so the delegated
account actually signs the instruction.

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.

2 participants