-
Notifications
You must be signed in to change notification settings - Fork 59
feat: Add Solana support to Hello frontend example #296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughConvert explorer URL strings to URL-builder functions and add chainType plus Solana Devnet; introduce a unified useHandleCall hook for EVM/SOL transactions; propagate an optional account prop and add wallet-switching on network change; memoize network→chain-id mapping; bump two dependencies. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant UI as MessageFlowCard
participant Hook as useHandleCall
participant EVM as EVM RPC
participant SOL as Solana RPC
User->>UI: Click "Send"
UI->>Hook: handleCall({receiver,message,...})
Note over Hook: Validate supportedChain, walletAddress
alt chainType == EVM
Hook->>Hook: onSigningStart()
Hook->>EVM: evmCall(tx, options)
EVM-->>Hook: txHash
Hook->>Hook: onTransactionSubmitted(txHash)
EVM-->>Hook: receipt
Hook->>Hook: onTransactionConfirmed(receipt)
else chainType == SOL
Hook->>SOL: solanaCall(ix, options)
SOL-->>Hook: sig
Hook->>Hook: onTransactionSubmitted(sig)
SOL-->>Hook: confirmation
Hook->>Hook: onTransactionConfirmed(confirmation)
end
Hook-->>UI: status callbacks
Hook->>Hook: onComplete()
sequenceDiagram
autonumber
actor User
participant Selector as NetworkSelector
participant Connected as ConnectedContent
participant Wallets as useUserWallets
participant SwitchW as useSwitchWallet
participant Chains as switchChain
User->>Selector: Choose target chain
Selector-->>Connected: onChange(target)
Connected->>Wallets: get primaryWallet
alt target.chainType != primaryWallet.chain
Connected->>SwitchW: switchWallet(to matching chain)
SwitchW-->>Connected: wallet switched
end
Connected->>Chains: switchChain(target)
Chains-->>Connected: result
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment @cursor review
or bugbot run
to trigger another review on this PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (8)
examples/hello/frontend/src/DynamicAppContent.tsx (1)
12-23
: Avoid hardcoded '103' mapping; centralize network→chainId logic.The special-case '103' → 901 is brittle. Prefer a shared utility (or derive from SUPPORTED_CHAINS) to map wallet network identifiers (string/number) to chainId, and optionally parse numeric strings.
Example:
- const decimalChainId = useMemo(() => { - if (typeof network === 'number') return network; - if (network === '103') return 901; - return null; - }, [network]); + const decimalChainId = useMemo(() => mapWalletNetworkToChainId(network), [network]);New helper (e.g., src/utils/networks.ts):
export function mapWalletNetworkToChainId(network: number | string | null | undefined): number | null { if (typeof network === 'number') return network; if (typeof network === 'string') { const n = Number(network); if (!Number.isNaN(n)) return n === 103 ? 901 : n; } return null; }examples/hello/frontend/src/constants/chains.ts (1)
2-6
: Function-based explorer URLs and chainType look solid.The move to typed explorerUrl(txHash) and adding chainType enable clean multi-chain handling; Solana Devnet entry is consistent.
Consider exporting named constants for chain ids (e.g., SOLANA_DEVNET_CHAIN_ID = 901) to avoid magic numbers elsewhere.
Also applies to: 12-18, 63-71, 78-79
examples/hello/frontend/src/ConfirmedContent.tsx (1)
82-82
: Disable links until hashes are available.Anchors render with incomplete URLs when hashes are missing. Prevent navigation and improve a11y by omitting href and marking as disabled.
- <a - href={supportedChain.explorerUrl(connectedChainTxHash)} + <a + href={connectedChainTxHash ? supportedChain.explorerUrl(connectedChainTxHash) : undefined} + aria-disabled={!connectedChainTxHash} + tabIndex={connectedChainTxHash ? 0 : -1} target="_blank" rel="noreferrer noopener"- <a - href={ZETACHAIN_ATHENS_BLOCKSCOUT_EXPLORER_URL(zetachainTxHash || '')} + <a + href={zetachainTxHash ? ZETACHAIN_ATHENS_BLOCKSCOUT_EXPLORER_URL(zetachainTxHash) : undefined} + aria-disabled={!zetachainTxHash} + tabIndex={zetachainTxHash ? 0 : -1} target="_blank" rel="noreferrer noopener"Also applies to: 98-100
examples/hello/frontend/src/MessageFlowCard.tsx (3)
39-55
: Receiver address for Solana path — verify correctnessYou pass HELLO_UNIVERSAL_CONTRACT_ADDRESS (EVM address) unconditionally. If solanaCall expects a Solana program ID or a chain-specific receiver, this will fail. Introduce a chain-aware receiver mapping or assert that the toolkit’s Solana route indeed accepts an EVM receiver for cross-chain delivery.
If a mapping is required, consider:
const receiver = supportedChain?.chainType === 'SOL' ? SOL_HELLO_PROGRAM_ID /* base58 */ : HELLO_UNIVERSAL_CONTRACT_ADDRESS;
137-142
: Disable the button unless the correct wallet type is readyPrevent avoidable runtime errors by gating on wallet readiness per chain:
- EVM: require selectedProvider, account, or a primaryWallet on EVM.
- SOL: require a primaryWallet on SOL.
Apply this diff for the disabled prop:
- disabled={ - !stringValue.length || - !supportedChain || - getStringByteLength(stringValue) > MAX_STRING_LENGTH - } + disabled={ + !stringValue.length || + !supportedChain || + getStringByteLength(stringValue) > MAX_STRING_LENGTH || + (supportedChain?.chainType === 'SOL' + ? !primaryWallet + : !(primaryWallet || selectedProvider || account)) + }Optionally, extract a walletReady boolean above for clarity.
39-55
: Capture tx hash on submission (optional UX improvement)Setting the hash on submission enables immediate explorer links while waiting for confirmation. Requires hook to pass hash/signature in onTransactionSubmitted.
Example (after hook change):
onTransactionSubmitted: (txHash: string) => { setIsTxReceiptLoading(true); setConnectedChainTxHash(txHash); },examples/hello/frontend/src/hooks/useHandleCall.ts (2)
68-74
: Avoid hardcoded gasLimit; rely on estimation/defaultsA fixed gasLimit of 1,000,000 can fail on some networks or waste gas. Let the call estimate gas (or provide an override only when necessary).
Apply this diff:
- const evmCallOptions = { - signer, - txOptions: { - gasLimit: 1000000, - }, - }; + const evmCallOptions = { + signer, + // Prefer provider estimation; specify overrides only if required + };
157-170
: Pass tx hash/signature to onTransactionSubmitted (optional)For parity with EVM and better UX, consider including the transaction hash/signature in onTransactionSubmitted and propagate it to the UI immediately.
Example:
- const callbacks = { - onSigningStart, - onTransactionSubmitted, - onTransactionConfirmed, - }; + const callbacks = { + onSigningStart, + onTransactionSubmitted: (hash?: string) => onTransactionSubmitted?.(hash), + onTransactionConfirmed, + };And in handleEvmCall/handleSolanaCall, invoke with the hash/signature returned by the toolkit.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
examples/hello/frontend/yarn.lock
is excluded by!**/yarn.lock
,!**/*.lock
📒 Files selected for processing (9)
examples/hello/frontend/package.json
(1 hunks)examples/hello/frontend/src/ConfirmedContent.tsx
(2 hunks)examples/hello/frontend/src/ConnectedContent.tsx
(7 hunks)examples/hello/frontend/src/DynamicAppContent.tsx
(3 hunks)examples/hello/frontend/src/Eip6963AppContent.tsx
(2 hunks)examples/hello/frontend/src/MessageFlowCard.tsx
(3 hunks)examples/hello/frontend/src/components/NetworkSelector.tsx
(2 hunks)examples/hello/frontend/src/constants/chains.ts
(1 hunks)examples/hello/frontend/src/hooks/useHandleCall.ts
(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-17T20:09:36.410Z
Learnt from: hernan-clich
PR: zeta-chain/example-contracts#280
File: examples/hello/frontend/src/App.tsx:8-9
Timestamp: 2025-09-17T20:09:36.410Z
Learning: In the zeta-chain/example-contracts repository, commit c9e419c6c8d6f045b06b2ba94c3c9f9b4ab05d0f addressed a runtime issue by splitting AppContent.tsx into DynamicAppContent.tsx and Eip6963AppContent.tsx components, providing better separation of concerns between dynamic wallet and EIP-6963 wallet implementations.
Applied to files:
examples/hello/frontend/src/DynamicAppContent.tsx
examples/hello/frontend/src/Eip6963AppContent.tsx
examples/hello/frontend/src/ConnectedContent.tsx
📚 Learning: 2025-09-18T17:59:04.889Z
Learnt from: hernan-clich
PR: zeta-chain/example-contracts#280
File: examples/hello/frontend/src/utils/ethersHelpers.ts:1-6
Timestamp: 2025-09-18T17:59:04.889Z
Learning: Commit 1c6cffd3d29499bf0544987a9116c7c6571ff895 in zeta-chain/example-contracts resolves the noble/hashes esbuild build failure by adding resolutions for "noble/hashes": "1.8.0" and "noble/curves": "1.9.7" to examples/hello/frontend/package.json.
Applied to files:
examples/hello/frontend/package.json
🧬 Code graph analysis (6)
examples/hello/frontend/src/ConfirmedContent.tsx (1)
examples/hello/frontend/src/constants/chains.ts (1)
ZETACHAIN_ATHENS_BLOCKSCOUT_EXPLORER_URL
(78-79)
examples/hello/frontend/src/hooks/useHandleCall.ts (3)
examples/hello/frontend/src/types/wallet.ts (1)
EIP6963ProviderDetail
(18-21)examples/hello/frontend/src/constants/chains.ts (1)
SupportedChain
(1-8)examples/hello/frontend/src/utils/ethersHelpers.ts (1)
getSignerAndProvider
(17-48)
examples/hello/frontend/src/components/NetworkSelector.tsx (2)
examples/hello/frontend/src/constants/chains.ts (1)
SUPPORTED_CHAINS
(10-72)examples/hello/frontend/src/constants/wallets.ts (1)
USE_DYNAMIC_WALLET
(2-2)
examples/hello/frontend/src/Eip6963AppContent.tsx (1)
examples/hello/frontend/src/hooks/useEip6963Wallet.ts (1)
useEip6963Wallet
(5-5)
examples/hello/frontend/src/MessageFlowCard.tsx (4)
examples/hello/frontend/src/types/wallet.ts (1)
EIP6963ProviderDetail
(18-21)examples/hello/frontend/src/constants/chains.ts (1)
SupportedChain
(1-8)examples/hello/frontend/src/hooks/useHandleCall.ts (1)
useHandleCall
(115-202)examples/hello/frontend/src/constants/contracts.ts (1)
HELLO_UNIVERSAL_CONTRACT_ADDRESS
(1-2)
examples/hello/frontend/src/ConnectedContent.tsx (1)
examples/hello/frontend/src/constants/chains.ts (1)
SupportedChain
(1-8)
⏰ 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). (13)
- GitHub Check: test (examples/token)
- GitHub Check: test (examples/hello)
- GitHub Check: test (examples/nft)
- GitHub Check: test (examples/swap)
- GitHub Check: test (examples/call)
- GitHub Check: test (examples/token)
- GitHub Check: test (examples/swap)
- GitHub Check: test (examples/nft)
- GitHub Check: test (examples/hello)
- GitHub Check: test (examples/call)
- GitHub Check: slither (examples/swap, swap.sarif)
- GitHub Check: slither (examples/hello, hello.sarif)
- GitHub Check: slither (examples/call, call.sarif)
🔇 Additional comments (4)
examples/hello/frontend/package.json (1)
13-14
: Deps bump looks good; resolutions retained.Upgrades align with new wallet hooks. Noble resolutions are preserved, preventing the known esbuild issue. Based on learnings.
Please verify the app runs end-to-end with the updated wallet/toolkit (connect, switch, send) across EVM and Solana paths.
examples/hello/frontend/src/Eip6963AppContent.tsx (1)
7-7
: Account property present in context Eip6963WalletContextType definesaccount: string | null
, so destructuring in useEip6963Wallet() is valid.examples/hello/frontend/src/ConnectedContent.tsx (1)
32-43
: Guard wallet switching and await async calls.In both
handleNetworkSelect
definitions:
- Early‐exit if
walletIds[chain.chainType]
is falsy to avoid callingswitchWallet('')
.- Declare the handler
async
andawait switchWallet(...)
beforeawait switchChain(...)
.- const handleNetworkSelect = (chain: SupportedChain) => { + const handleNetworkSelect = async (chain: SupportedChain) => { if (chain.chainType !== primaryWalletChain) { - switchWallet(walletIds[chain.chainType]); + const target = walletIds[chain.chainType]; + if (!target) { + console.warn(`No ${chain.chainType} wallet available to switch.`); + return; + } + await switchWallet(target); } - switchChain(chain.chainId); + await switchChain(chain.chainId); };examples/hello/frontend/src/hooks/useHandleCall.ts (1)
89-109
: Solana: remove hardcoded chainId, add onSigningStart, align confirmation semantics
- Derive chainId from supportedChain instead of hardcoding
'901'
.- Fire callbacks.onSigningStart before prompting the wallet.
- Ensure callbacks.onTransactionConfirmed runs after on-chain confirmation.
Apply this diff:
-async function handleSolanaCall( - callParams: CallParams, - primaryWallet: PrimaryWallet, - callbacks: { - onTransactionSubmitted?: UseHandleCallParams['onTransactionSubmitted']; - onTransactionConfirmed?: UseHandleCallParams['onTransactionConfirmed']; - } -): Promise<void> { +async function handleSolanaCall( + callParams: CallParams, + primaryWallet: PrimaryWallet, + chainId: string | number, + callbacks: { + onSigningStart?: UseHandleCallParams['onSigningStart']; + onTransactionSubmitted?: UseHandleCallParams['onTransactionSubmitted']; + onTransactionConfirmed?: UseHandleCallParams['onTransactionConfirmed']; + } +): Promise<void> { const walletAdapter = await getSolanaWalletAdapter(primaryWallet); const solanaCallOptions = { signer: walletAdapter, - chainId: '901', + chainId, }; + callbacks.onSigningStart?.(); const result = await solanaCall(callParams, solanaCallOptions); callbacks.onTransactionSubmitted?.(); - callbacks.onTransactionConfirmed?.(result); + // consider awaiting on-chain confirmation here before invoking: + // await walletAdapter.connection.confirmTransaction(result, 'confirmed'); + callbacks.onTransactionConfirmed?.(result); }And update the caller:
- await handleSolanaCall(callParams, primaryWallet, callbacks); + await handleSolanaCall( + callParams, + primaryWallet, + supportedChain.chainId, + callbacks + );Confirm:
- Should chainId be passed as a string or number per solanaCall’s signature?
- Does solanaCall resolve after submission or after on-chain confirmation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (2)
examples/hello/frontend/src/hooks/useHandleCall.ts (2)
68-73
: Consider making gasLimit configurable.The hardcoded
gasLimit: 1000000
may not be suitable for all transaction types. Consider accepting this as an optional parameter to allow callers to specify appropriate gas limits for different operations.Apply this diff to make gasLimit configurable:
async function handleEvmCall( callParams: CallParams, primaryWallet: PrimaryWallet | null, selectedProvider: EIP6963ProviderDetail | null, callbacks: { onSigningStart?: UseHandleCallParams['onSigningStart']; onTransactionSubmitted?: UseHandleCallParams['onTransactionSubmitted']; onTransactionConfirmed?: UseHandleCallParams['onTransactionConfirmed']; - } + }, + gasLimit?: number ): Promise<void> { // ... existing code ... const evmCallOptions = { signer, txOptions: { - gasLimit: 1000000, + gasLimit: gasLimit ?? 1000000, }, };
154-160
: Consider making onRevertGasLimit configurable.Similar to the EVM
gasLimit
, theonRevertGasLimit
is hardcoded to1000000
. Consider accepting this as an optional parameter to provide flexibility for different transaction requirements.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
examples/hello/frontend/src/components/NetworkSelector.tsx
(2 hunks)examples/hello/frontend/src/hooks/useHandleCall.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- examples/hello/frontend/src/components/NetworkSelector.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
examples/hello/frontend/src/hooks/useHandleCall.ts (3)
examples/hello/frontend/src/types/wallet.ts (1)
EIP6963ProviderDetail
(18-21)examples/hello/frontend/src/constants/chains.ts (1)
SupportedChain
(1-8)examples/hello/frontend/src/utils/ethersHelpers.ts (1)
getSignerAndProvider
(17-48)
⏰ 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). (12)
- GitHub Check: test (examples/nft)
- GitHub Check: test (examples/token)
- GitHub Check: test (examples/call)
- GitHub Check: test (examples/swap)
- GitHub Check: test (examples/nft)
- GitHub Check: test (examples/swap)
- GitHub Check: test (examples/token)
- GitHub Check: test (examples/call)
- GitHub Check: test (examples/hello)
- GitHub Check: slither (examples/swap, swap.sarif)
- GitHub Check: slither (examples/hello, hello.sarif)
- GitHub Check: slither (examples/call, call.sarif)
🔇 Additional comments (3)
examples/hello/frontend/src/hooks/useHandleCall.ts (3)
1-10
: LGTM!Imports are well-organized and all dependencies are appropriately used throughout the hook implementation.
12-42
: LGTM!Interface definitions provide comprehensive type safety with well-structured callbacks and flexible parameter types that accommodate various use cases.
144-148
: LGTM!The wallet type compatibility check properly prevents mismatched transactions and addresses the previous review concern.
Walkthroughs
EIP-6963 EVM Path
rec-ec-solpr-eip-1.mp4
Dynamic Wallet EVM Path
rec-ec-solpr-dynevm-1.mp4
Dynamic Wallet Solana Path
rec-ec-solpr-dynsol-1.mp4
Note
Adds Solana Devnet support with chain-aware wallet switching and a shared call hook, updates explorer URL handling, and bumps toolkit/wallet deps.
SUPPORTED_CHAINS
withSolana Devnet
, addchainType
and function-basedexplorerUrl
; makeZETACHAIN_ATHENS_BLOCKSCOUT_EXPLORER_URL
a function.network
('103') tochainId
901
and plumbaccount
through EIP-6963 path.NetworkSelector
filters to EVM-only when not using dynamic wallet.hooks/useHandleCall
to handle EVM (evmCall
) and Solana (solanaCall
) sending;MessageFlowCard
now useshandleCall
and supportsaccount
.ConfirmedContent
and links to use function-based explorer URLs.ConnectedContent
usesuseUserWallets
/useSwitchWallet
to auto-switch between EVM and SOL wallets based on selected network.@zetachain/toolkit
to16.1.4
and@zetachain/wallet
to1.0.13
; add Solana-related packages in lockfile.Written by Cursor Bugbot for commit bb826b0. This will update automatically on new commits. Configure here.
Summary by CodeRabbit
New Features
Improvements
Chores