feat: fix example and add local UI counter UI#39
Conversation
WalkthroughAdds a new React-based “Ephemeral Counter” app with Solana wallet integration, build configs, assets, styles, and entrypoint. Introduces program logic tweaks (counter.exit calls), updates the IDL (removes optional validator from delegate), and aligns tests with new account lists. Includes documentation and licensing files. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant W as Wallet UI
participant CP as ConnectionProvider
participant EP as Ephemeral Provider
participant P as Program (Anchor)
participant RPC as RPC Endpoint(s)
U->>W: Connect wallet
W->>CP: Initialize Solana connection (env/default)
CP->>P: Fetch IDL, init Program(client)
Note over CP,P: Subscribe to counter PDA onAccountChange
W->>EP: Init ephemeral connection (optional)
EP-->>W: Subscribe to ephemeral PDA changes
U->>W: Click Increment / Delegate / Undelegate
W->>P: Build & send tx (wallet or temp keypair)
P->>RPC: Submit and confirm
RPC-->>W: Signature/confirmation
CP-->>W: Account change event(s) → update UI
W-->>U: Show updated counters / alerts
sequenceDiagram
autonumber
participant C as Client
participant Prog as anchor_counter Program
rect rgba(200,230,255,0.3)
Note over C,Prog: Updated on-chain flows
C->>Prog: increment_and_commit
Prog->>Prog: counter.exit(&ID)
Prog-->>C: Success
end
rect rgba(220,255,220,0.3)
C->>Prog: increment_and_undelegate
Prog->>Prog: counter.exit(&ID)
Prog-->>C: Success
end
rect rgba(255,240,200,0.3)
Note over C,Prog: Delegate instruction (IDL)
C->>Prog: delegate (no optional validator account)
Prog-->>C: Success
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
anchor-counter/tests/advanced-magic.ts (1)
61-67: Critical: Remove validator from accounts and use remainingAccounts instead.The
validatorfield was removed from theDelegateInputstruct in the Rust program (line 115-120 in lib.rs). The validator is now passed viactx.remaining_accountsas shown in the program code at line 42. This test still passesvalidatorin the accounts object, which will cause a runtime error since the IDL no longer includes this field.Reference
anchor-counter.tslines 96-113 for the correct pattern usingremainingAccounts.Apply this diff to fix the delegation flow:
it("Delegate counter to ER", async () => { const start = Date.now(); + // Add validator to remaining accounts + const remainingAccounts = [ + { + pubkey: ER_VALIDATOR, + isSigner: false, + isWritable: false, + }, + ]; let tx = await program.methods .delegate() .accounts({ payer: providerMagic.wallet.publicKey, - validator: ER_VALIDATOR, pda: pda, }) + .remainingAccounts(remainingAccounts) .transaction();
🧹 Nitpick comments (4)
anchor-counter/app/app/tsconfig.json (1)
4-13: Keep TS output as ES modules and use the modern JSX runtime.For a browser React app that bundles via CRACO/CRA, forcing
"module": "commonjs"will downlevel allimport/exportsyntax torequire/module.exports, which conflicts with the bundler’s ESM pipeline and can break tree-shaking. Also,"jsx": "react"reverts to the legacy JSX transform, making every component importReactmanually even though React 17+ tooling (including CRA) expects"react-jsx".Switch these options to stay aligned with the default CRA tooling:
- "module": "commonjs", + "module": "esnext", @@ - "jsx": "react" + "jsx": "react-jsx"anchor-counter/app/app/LICENSE.md (1)
1-21: LGTM!Standard MIT License text is correctly formatted. Note that there's another LICENSE file at
anchor-counter/app/LICENSEwith identical content. Having license files at both the app root and app/app directory may be intentional for different distribution contexts, but consider if both are necessary.anchor-counter/app/app/public/manifest.json (1)
2-3: Update the app names to match the actual application.The
short_nameandnamefields still contain generic Create React App defaults. Consider updating them to reflect the actual application name ("Ephemeral Counter") for consistency with the HTML title and better PWA branding.Apply this diff to update the names:
- "short_name": "React App", - "name": "Create React App Sample", + "short_name": "Ephemeral Counter", + "name": "Ephemeral Counter App",anchor-counter/app/app/package.json (1)
7-7: Update @coral-xyz/anchor to ^0.31.1
Includes critical proc-macro2 build fixes, TypeScript Provider enhancements, and compatibility with Solana 2.1.0. No known vulnerabilities in 0.30.1, but upgrading ensures these improvements.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (5)
anchor-counter/app/app/package-lock.jsonis excluded by!**/package-lock.jsonanchor-counter/app/app/public/favicon.icois excluded by!**/*.icoanchor-counter/app/app/public/magicblock_white.pngis excluded by!**/*.pnganchor-counter/app/app/yarn.lockis excluded by!**/yarn.lock,!**/*.lockanchor-counter/app/yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (24)
anchor-counter/app/LICENSE(1 hunks)anchor-counter/app/README.md(1 hunks)anchor-counter/app/app/LICENSE.md(1 hunks)anchor-counter/app/app/README.md(1 hunks)anchor-counter/app/app/craco.config.js(1 hunks)anchor-counter/app/app/package.json(1 hunks)anchor-counter/app/app/public/index.html(1 hunks)anchor-counter/app/app/public/manifest.json(1 hunks)anchor-counter/app/app/src/App.tsx(1 hunks)anchor-counter/app/app/src/components/Active.scss(1 hunks)anchor-counter/app/app/src/components/Active.tsx(1 hunks)anchor-counter/app/app/src/components/Alert.tsx(1 hunks)anchor-counter/app/app/src/components/Button.tsx(1 hunks)anchor-counter/app/app/src/components/Square.scss(1 hunks)anchor-counter/app/app/src/components/Square.tsx(1 hunks)anchor-counter/app/app/src/components/Wallet.tsx(1 hunks)anchor-counter/app/app/src/index.css(1 hunks)anchor-counter/app/app/src/index.tsx(1 hunks)anchor-counter/app/app/tsconfig.json(1 hunks)anchor-counter/app/tsconfig.json(1 hunks)anchor-counter/programs/anchor-counter/src/lib.rs(1 hunks)anchor-counter/target/idl/anchor_counter.json(0 hunks)anchor-counter/tests/advanced-magic.ts(1 hunks)anchor-counter/tests/anchor-counter.ts(2 hunks)
💤 Files with no reviewable changes (1)
- anchor-counter/target/idl/anchor_counter.json
🧰 Additional context used
🧬 Code graph analysis (3)
anchor-counter/app/app/src/index.tsx (1)
anchor-counter/app/app/src/components/Wallet.tsx (1)
Wallet(27-44)
anchor-counter/app/app/src/components/Button.tsx (1)
roll-dice/app/components/ui/button.tsx (2)
ButtonProps(36-40)Button(56-56)
anchor-counter/app/app/src/App.tsx (1)
anchor-counter/app/app/src/components/Wallet.tsx (1)
SimpleProvider(17-25)
🔇 Additional comments (16)
anchor-counter/app/LICENSE (1)
1-21: LGTM!Standard MIT License text is correctly formatted with appropriate copyright notice for Magicblock Pte. Ltd.
anchor-counter/app/app/public/index.html (1)
1-22: LGTM!The HTML structure is well-formed with proper meta tags, favicon/manifest references, and a root element for React mounting. The external Google Fonts link for Roboto is appropriately included.
anchor-counter/app/README.md (1)
1-54: LGTM!The README provides clear documentation for the Ephemeral Counter UI, including prerequisites, installation steps, and environment variable configuration. The structure and content are well-organized and informative.
anchor-counter/app/app/package.json (1)
22-28: LGTM!The scripts section is correctly configured to use CRACO for CRA customization. The
devandstartscripts both map tocraco start, which is appropriate for the development workflow.anchor-counter/app/app/src/index.tsx (2)
7-8: LGTM!Excellent error handling for the missing root element. This prevents silent failures and provides a clear error message if the DOM structure is incorrect.
10-15: LGTM!The React 18 root creation and rendering is correctly implemented. Using
StrictModeand theWalletwrapper component follows best practices for wallet-enabled Solana applications.anchor-counter/tests/advanced-magic.ts (3)
5-7: LGTM!The import formatting change improves readability.
79-84: Verify that Anchor correctly infers the counter account.The
counteraccount is no longer explicitly passed toincrementAndCommitandincrementAndUndelegate, relying on Anchor's automatic account inference. With the addition ofcounter.exit()calls in the Rust program, ensure the PDA is correctly resolved from the seeds at runtime.Also applies to: 106-111
45-50: Counter PDA auto-derivation confirmed.Initializein anchor-counter/src/lib.rs defines thecounteraccount withseeds = [TEST_PDA_SEED]and the module is annotated with#[ephemeral], so Anchor will infer the PDA automatically.anchor-counter/programs/anchor-counter/src/lib.rs (3)
76-76: LGTM: Proper serialization before commit.The
counter.exit()call ensures the Anchor account is properly serialized before the CPI tocommit_accounts. This is the correct pattern when using mutable Anchor accounts with CPI operations in the ephemeral rollups SDK.
92-92: LGTM: Proper serialization before commit and undelegate.The
counter.exit()call ensures the Anchor account is properly serialized before the CPI tocommit_and_undelegate_accounts, following the same pattern asincrement_and_commit.
36-46: LGTM: Correct use of remaining_accounts for validator.The validator is correctly retrieved from
ctx.remaining_accountsinstead of a dedicated struct field, providing more flexibility for optional validator specification. This aligns with the test changes inanchor-counter.ts.anchor-counter/tests/anchor-counter.ts (4)
8-8: LGTM: Descriptive constant name.Renaming to
LOCAL_ER_VALIDATORbetter indicates this validator is used for local development environments.
96-113: LGTM: Correct validator handling via remainingAccounts.The validator is correctly passed via
remainingAccountsinstead of a dedicated account field, aligning with the Rust program changes. The conditional logic properly handles local vs. remote environments.
53-57: Verify that Anchor correctly infers the counter PDA.The
counteraccount is no longer explicitly passed, relying on Anchor's automatic PDA derivation from theInitializestruct's seeds. Ensure this works correctly with the program's initialization logic.
181-184: Verify empty accounts object with Anchor's account inference.The
incrementAndCommitcall passes an empty accounts object{}, relying entirely on Anchor's automatic account resolution. Confirm that the#[commit]macro and PDA seeds enable Anchor to correctly infer both thepayerandcounteraccounts at runtime.
| webpackConfig.resolve.fallback = { | ||
| http: require.resolve('stream-http'), | ||
| https: require.resolve('https-browserify'), | ||
| crypto: require.resolve('crypto-browserify'), | ||
| stream: require.resolve('stream-browserify'), | ||
| buffer: require.resolve('buffer-browserify'), | ||
| zlib: false, | ||
| url: false, | ||
| vm: false, | ||
| }; |
There was a problem hiding this comment.
Preserve existing fallbacks instead of overwriting them.
webpackConfig.resolve.fallback may already contain framework-provided entries (e.g., CRA polyfills). Replacing the object wholesale drops those defaults, which can reintroduce missing module errors elsewhere in the app. Merge with the existing fallback instead of clobbering it.
🤖 Prompt for AI Agents
In anchor-counter/app/app/craco.config.js around lines 8 to 17, the code
replaces webpackConfig.resolve.fallback wholesale which discards any existing
framework-provided fallbacks; instead, preserve and extend the existing object
by merging your custom entries into webpackConfig.resolve.fallback (ensure the
property exists first), so that you add
http/https/crypto/stream/buffer/zlib/url/vm entries without clobbering defaults.
| ```bash | ||
| npm run install | ||
| ``` |
There was a problem hiding this comment.
Fix the npm install command.
The command npm run install is incorrect. The standard npm command is npm install without the run prefix.
Apply this diff to fix the command:
```bash
-npm run install
+npm install
<details>
<summary>🤖 Prompt for AI Agents</summary>
In anchor-counter/app/app/README.md around lines 5 to 7, the README shows the
incorrect command "npm run install"; replace it with the standard "npm install"
command so users run the proper installer command (update the fenced code block
to contain "npm install" instead of "npm run install").
</details>
<!-- This is an auto-generated comment by CodeRabbit -->
| if (!publicKey) return; | ||
| if (!publicKey || Keypair.fromSeed(publicKey.toBytes()).publicKey.equals(tempKeypair.current?.publicKey || PublicKey.default)) return; | ||
| console.log("Wallet connected with publicKey:", publicKey.toBase58()); | ||
| // Derive the temp keypair from the publicKey | ||
| const newTempKeypair = Keypair.fromSeed(publicKey.toBytes()); | ||
| tempKeypair.current = newTempKeypair; | ||
| console.log("Temp Keypair", newTempKeypair.publicKey.toBase58()); | ||
| }, [connection, publicKey]); |
There was a problem hiding this comment.
Stop deriving private keys from public data
Keypair.fromSeed(publicKey.toBytes()) creates the temp private key from the wallet’s public key. Anyone who knows that public key can reproduce the same seed and steal the lamports or impersonate the user in delegated flows. Generate the temp keypair from a secret (e.g., a wallet-signed message or fresh randomness) and persist it securely if you need determinism.
| const submitTransaction = useCallback(async (transaction: Transaction, useTempKeypair: boolean = false, ephemeral: boolean = false, confirmCommitment : Commitment = "processed"): Promise<string | null> => { | ||
| if (!tempKeypair.current) return null; | ||
| if (!publicKey) return null; | ||
| if (!ephemeralConnection.current) return null; | ||
| //if (isSubmitting) return null; | ||
| setIsSubmitting(true); | ||
| setTransactionError(null); | ||
| setTransactionSuccess(null); | ||
| let connection = ephemeral ? ephemeralConnection.current : provider.current.connection; |
There was a problem hiding this comment.
Don’t gate every transaction on the ephemeral connection
submitTransaction returns early when ephemeralConnection.current is missing, even when ephemeral is false. If the auxiliary endpoint is down or still initializing, all actions (delegate, increment) silently fail. Only require the ephemeral connection in the ephemeral === true path.
🤖 Prompt for AI Agents
In anchor-counter/app/app/src/App.tsx around lines 152 to 160, the function
currently bail outs when ephemeralConnection.current is missing even for
non-ephemeral transactions; update the guard so the ephemeral connection is only
required when ephemeral === true. Replace the unconditional check "if
(!ephemeralConnection.current) return null" with a conditional that returns null
only when ephemeral is true and ephemeralConnection.current is falsy, leaving
other early returns (tempKeypair/publicKey) intact.
| useEffect(() => { | ||
| setOpacity(1); | ||
|
|
||
| const fadeOutTimer = setTimeout(() => { | ||
| setOpacity(0); | ||
| }, 3000); | ||
|
|
||
| const removeTimer = setTimeout(() => { | ||
| onClose(); | ||
| }, 3500); | ||
|
|
||
| return () => { | ||
| clearTimeout(fadeOutTimer); | ||
| clearTimeout(removeTimer); | ||
| }; | ||
| }, [onClose]); |
There was a problem hiding this comment.
Reset fade timers when the alert content changes
Line 12’s effect only depends on onClose, so if the parent shows a new message while the component stays mounted, the old timer keeps running and the new alert can disappear almost immediately. Add type and message to the dependency list (and restart the timers when either changes) so every alert gets its full display duration.
🤖 Prompt for AI Agents
In anchor-counter/app/app/src/components/Alert.tsx around lines 12 to 27, the
useEffect only lists onClose as a dependency so timers aren't reset when the
alert's message or type changes; update the effect to include type and message
in the dependency array and ensure the timers are created/cleared inside the
effect so they restart whenever either prop changes, giving each new alert its
full display duration.
| export class SimpleProvider implements Provider { | ||
| readonly connection: Connection; | ||
| readonly publicKey?: PublicKey; | ||
|
|
||
| constructor(connection: Connection, publicKey?: PublicKey) { | ||
| this.connection = connection; | ||
| this.publicKey = publicKey; | ||
| } | ||
| } |
There was a problem hiding this comment.
SimpleProvider doesn’t satisfy Anchor’s Provider contract
Line 17 claims to implement Provider, but the class only exposes connection and publicKey. The Anchor Provider type also requires methods like send/sendAndConfirm (and opts/wallet depending on the exact version). TypeScript will reject this implementation, and any code that relies on it will break at runtime. Either drop implements Provider or supply the full Provider surface (or simply extend AnchorProvider instead of re‑implementing it).
🤖 Prompt for AI Agents
In anchor-counter/app/app/src/components/Wallet.tsx around lines 17 to 25,
SimpleProvider is declared as implementing Anchor's Provider but only exposes
connection and publicKey; implement the full Provider surface or stop claiming
to implement it. Fix by either (A) remove "implements Provider" if you only need
those two fields, or (B) extend AnchorProvider (or construct an AnchorProvider)
and delegate all required members (send, sendAndConfirm, wallet, opts, etc.) to
that implementation, or (C) add the missing methods and properties with
signatures matching the Provider type and forward calls to an underlying
AnchorProvider/Connection/wallet instance. Ensure the chosen approach satisfies
the Provider interface types so TypeScript compiles.
| .join-game input[type="text"] ::placeholder { | ||
| color: rgba(255, 255, 255, 0.7); | ||
| } | ||
|
|
||
| .join-game input[type="text"] :focus { | ||
| outline: none; | ||
| border-color: #62fffc; | ||
| } |
There was a problem hiding this comment.
Fix the placeholder/focus selectors
Line 220 and Line 224 include a space before ::placeholder and :focus, so the browser interprets them as descendant selectors and the styling never applies. Remove the space to target the input itself.
-.join-game input[type="text"] ::placeholder {
+.join-game input[type="text"]::placeholder {
color: rgba(255, 255, 255, 0.7);
}
-.join-game input[type="text"] :focus {
+.join-game input[type="text"]:focus {
outline: none;
border-color: #62fffc;
}📝 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.
| .join-game input[type="text"] ::placeholder { | |
| color: rgba(255, 255, 255, 0.7); | |
| } | |
| .join-game input[type="text"] :focus { | |
| outline: none; | |
| border-color: #62fffc; | |
| } | |
| .join-game input[type="text"]::placeholder { | |
| color: rgba(255, 255, 255, 0.7); | |
| } | |
| .join-game input[type="text"]:focus { | |
| outline: none; | |
| border-color: #62fffc; | |
| } |
🤖 Prompt for AI Agents
In anchor-counter/app/app/src/index.css around lines 220 to 227, the selectors
have an extra space before ::placeholder and :focus so they act as descendant
selectors and never target the input; remove the space so the selectors directly
target the input element (i.e., change ".join-game input[type=\"text\"]
::placeholder" to ".join-game input[type=\"text\"]::placeholder" and ".join-game
input[type=\"text\"] :focus" to ".join-game input[type=\"text\"]:focus") and
keep the same declarations.
| { | ||
| "compilerOptions": { | ||
| "types": ["mocha", "chai"], | ||
| "typeRoots": ["./node_modules/@types"], |
There was a problem hiding this comment.
Don’t override typeRoots to a possibly nonexistent folder.
Pointing typeRoots to ./node_modules/@types makes TS ignore ancestor node_modules/@types. In this repo the dependencies (including @types/node, React, etc.) live at the workspace root, so the compiler will stop seeing them. Drop the override (let the defaults apply) or include the ancestor paths explicitly.
🤖 Prompt for AI Agents
In anchor-counter/app/tsconfig.json around line 4, the tsconfig overrides
"typeRoots" to ["./node_modules/@types"], which prevents TypeScript from picking
up ancestor workspace type packages (e.g. @types/node, React) and causes
resolution failures; remove the "typeRoots" setting entirely so TS uses its
defaults, or if you must keep it, expand it to include the workspace
root/ancestor node_modules/@types paths (e.g. "../../node_modules/@types" or the
appropriate relative paths) so all required @types packages remain visible to
the compiler.
Summary