Multichain ImageGen#295
Conversation
There was a problem hiding this comment.
Pull request overview
Prepares the ImageGen frontend and shared tooling for multichain operation by centralizing chain/address/ABI selection in @fretchen/chain-utils and updating the website to use CAIP-2 network identifiers.
Changes:
- Migrates website GenImNFT contract access from
getChain.ts/hardcoded configs to@fretchen/chain-utils(CAIP-2 +getGenAiNFTAddress+GenImNFTv4ABI). - Introduces
useAutoNetwork(deferred “switch at interaction”) anduseNFTListedStatushooks and updates components to use them. - Expands/updates tests and ABI export pipeline (adds GenImNFTv4 ABI artifacts; removes generated timestamps from ABI outputs).
Reviewed changes
Copilot reviewed 51 out of 55 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| website/utils/nodeNftLoader.ts | SSR/build-time NFT metadata loader now uses CAIP-2 default network + chain-utils addresses/ABI. |
| website/utils/nodeChainUtils.ts | Adds getDefaultNetwork() for SSR/build-time CAIP-2 selection; removes GenAI NFT legacy config exports. |
| website/utils/nftLoader.ts | NFT loader now requires an explicit CAIP-2 network and uses chain-utils for address/ABI. |
| website/utils/getChain.ts | Removes GenAI NFT legacy config; keeps legacy configs for other contracts and documents migration path. |
| website/hooks/useNFTListedStatus.ts | New hook to read isTokenListed with error handling for legacy tokens. |
| website/hooks/useConfiguredPublicClient.ts | Public client hook now takes a CAIP-2 network and derives chainId via fromCAIP2. |
| website/hooks/useAutoNetwork.ts | New hook to resolve CAIP-2 network and provide deferred switchIfNeeded() behavior. |
| website/components/PublicNFTList.tsx | Uses useAutoNetwork + chain-utils address/ABI for getAllPublicTokens(). |
| website/components/NFTList.tsx | Uses CAIP-2 network to query balance with GenImNFTv4 ABI. |
| website/components/NFTFloatImage.tsx | Uses CAIP-2 network and chain-utils address/ABI for tokenURI() reads. |
| website/components/NFTCard.tsx | Migrates reads/writes to chain-utils + adds listing-status hook + deferred chain switching on write actions. |
| website/components/MyNFTList.tsx | Migrates reads to chain-utils + CAIP-2 chainId/address selection. |
| website/components/ImageGenerator.tsx | Moves to CAIP-2 + deferred switching via switchIfNeeded(); adds hydration-safety handling. |
| website/components/EntryNftImage.tsx | Migrates tokenURI reads to chain-utils + CAIP-2 public client. |
| website/components/AgentInfoPanel.tsx | Uses useAutoNetwork and chain-utils for contract address display. |
| website/test/useNFTListedStatus.test.ts | Adds unit tests for the new useNFTListedStatus hook behavior. |
| website/test/useConfiguredPublicClient.test.ts | Updates tests for new CAIP-2 parameter requirement. |
| website/test/useAutoNetwork.test.ts | Adds integration-style tests for useAutoNetwork using wagmi mock connector. |
| website/test/setup.ts | Refactors wagmi mocks into exportable mock functions for per-test customization. |
| website/test/PublicNFTList.test.tsx | Updates component tests to the new hooks/chain-utils integration. |
| website/test/MyNFTList.test.tsx | Updates tests/mocks to the new chain-utils + useAutoNetwork pattern. |
| website/test/ImageGenerator.test.tsx | Updates test suite; one network-switch test is now skipped. |
| website/test/ContractChainSelection.test.ts | Updates to chain-utils address/ABI selection for contract read. |
| website/test/AgentInfoPanel.test.tsx | Adds rendering/hook-order regression tests for AgentInfoPanel. |
| website/package.json | Adds local workspace dependency on @fretchen/chain-utils. |
| website/package-lock.json | Locks the new local chain-utils dependency. |
| website/MULTICHAIN_EXPANSION_PROPOSAL.md | Updates migration plan/progress notes for multichain expansion phases. |
| shared/chain-utils/src/addresses.ts | Adds LLM_V1_NETWORKS list and centralizes network lists for deployed contracts. |
| shared/chain-utils/src/abi/GenImNFTv4.ts | Expands minimal GenImNFTv4 ABI to cover website needs (public tokens, listing, burn, etc.). |
| eth/test/SupportV2_Deployment.ts | Formatting cleanup in deployment tests. |
| eth/scripts/export-abi.ts | Adds GenImNFTv4 to ABI export list; removes generated timestamps from outputs. |
| eth/scripts/deploy-support-v2.config.json | Trailing newline/formatting fix. |
| eth/deployments/support-v2-optsepolia.json | Trailing newline/formatting fix. |
| eth/deployments/support-v2-optimisticEthereum.json | Trailing newline/formatting fix. |
| eth/abi/contracts/SupportV2.ts | Removes generated timestamp comment from ABI output. |
| eth/abi/contracts/SupportV2-summary.md | Removes generated timestamp section. |
| eth/abi/contracts/Support.ts | Removes generated timestamp comment from ABI output. |
| eth/abi/contracts/Support-summary.md | Removes generated timestamp section. |
| eth/abi/contracts/LLMv1.ts | Removes generated timestamp comment from ABI output. |
| eth/abi/contracts/LLMv1-summary.md | Removes generated timestamp section. |
| eth/abi/contracts/GenImNFTv4.ts | Adds exported GenImNFTv4 ABI TypeScript artifact. |
| eth/abi/contracts/GenImNFTv4.json | Adds exported GenImNFTv4 ABI JSON artifact. |
| eth/abi/contracts/GenImNFTv4-summary.md | Adds GenImNFTv4 ABI summary documentation. |
| eth/abi/contracts/GenImNFTv3.ts | Removes generated timestamp comment from ABI output. |
| eth/abi/contracts/GenImNFTv3-summary.md | Removes generated timestamp section. |
| eth/abi/contracts/EIP3009SplitterV1.ts | Removes generated timestamp comment from ABI output. |
| eth/abi/contracts/EIP3009SplitterV1-summary.md | Removes generated timestamp section. |
| eth/abi/contracts/CollectorNFTv1.ts | Removes generated timestamp comment from ABI output. |
| eth/abi/contracts/CollectorNFTv1-summary.md | Removes generated timestamp section. |
| eth/abi/contracts/CollectorNFT.ts | Removes generated timestamp comment from ABI output. |
| eth/abi/contracts/CollectorNFT-summary.md | Removes generated timestamp section. |
| eth/SUPPORT_V2_PROPOSAL.md | Markdown formatting cleanup. |
| eth/DEPLOY_SUPPORT_V2_GUIDE.md | Markdown formatting cleanup and minor formatting fixes. |
Files not reviewed (1)
- website/package-lock.json: Language not supported
| // SKIPPED: This test has two issues: | ||
| // 1. vi.mocked(useChainId).mockReturnValue() doesn't work due to module caching | ||
| // (see useAutoNetwork.test.ts header for detailed explanation) | ||
| // 2. With "switch at interaction" pattern, switchChain is called via switchIfNeeded() | ||
| // inside the submit handler, not directly on button click | ||
| it.skip("should call switchChain when user attempts to create artwork on wrong network", async () => { |
There was a problem hiding this comment.
There’s a skipped test here (it.skip(...)), which reduces coverage and can let regressions through. Please either fix the test (e.g., by using the centralized mocks from test/setup.ts or by rewriting to align with the new switchIfNeeded() behavior) or remove it if it’s no longer relevant.
| const freshBalance = await readContract(config, { | ||
| ...genAiNFTContractConfig, | ||
| address: contractAddress, | ||
| abi: GenImNFTv4ABI, | ||
| functionName: "balanceOf", | ||
| args: [address], | ||
| chainId: chain.id, | ||
| chainId, | ||
| }); |
There was a problem hiding this comment.
loadUserTokenIds captures contractAddress and chainId (derived from network) but its useCallback dependency list currently doesn’t include them. If the active GenAI network changes (e.g., Optimism ↔ Optimism Sepolia), the callback can keep querying the previous chain/address. Update the dependency array to include contractAddress and chainId (or network) so reads always target the current network.
| const CreateArtworkButton = () => { | ||
| const isDisabled = buttonState === "needsPrompt"; | ||
| const isLoadingState = buttonState === "loading" || buttonState === "switching"; | ||
| const isLoadingState = buttonState === "loading"; | ||
|
|
There was a problem hiding this comment.
isLoadingState is computed but the button isn’t disabled while loading, so users can click repeatedly and trigger multiple concurrent handleX402Generate() runs (and potentially multiple payment/signature flows). Use isLoadingState to disable the button (or add an early-return guard) while generation/processing is in progress.
| // === Chain Switch at Interaction === | ||
| // Ensure user is on the correct chain before making payment | ||
| if (currentChainId !== targetChain.id) { | ||
| console.log(`[x402] Chain mismatch: current=${currentChainId}, target=${targetChain.id} (${targetChain.name})`); | ||
| try { | ||
| await switchChainAsync({ chainId: targetChain.id }); | ||
| console.log(`[x402] Successfully switched to ${targetChain.name}`); | ||
| } catch (switchError) { | ||
| console.error("[x402] Chain switch failed:", switchError); | ||
| const errorMsg = `${chainSwitchFailedText}: ${targetChain.name}`; | ||
| setError(errorMsg); | ||
| onError?.(errorMsg); | ||
| return; | ||
| } | ||
| const switched = await switchIfNeeded(); | ||
| if (!switched) { | ||
| console.error("[x402] Chain switch rejected by user"); | ||
| const errorMsg = `${chainSwitchFailedText}: ${targetChain.name}`; | ||
| setError(errorMsg); | ||
| onError?.(errorMsg); | ||
| return; | ||
| } |
There was a problem hiding this comment.
switchIfNeeded() is awaited before setIsLoading(true), so the UI doesn’t enter a loading/disabled state while the wallet chain-switch prompt is pending. This can lead to repeated clicks and multiple switch prompts/actions. Consider setting a dedicated “switching”/loading state before awaiting switchIfNeeded() and clearing it on failure/success.
| // Mock useLocale hook | ||
| vi.mock("./hooks/useLocale", () => ({ | ||
| useLocale: vi.fn(({ label }: { label: string }) => label), | ||
| })); |
There was a problem hiding this comment.
This mocks "./hooks/useLocale", but components import "../hooks/useLocale", so the mock won’t apply. Either change the specifier to match the real import path (e.g. "../hooks/useLocale") or remove this mock if it’s unnecessary.
|
|
||
| // Get network and contract address from chain-utils | ||
| const { network, switchIfNeeded } = useAutoNetwork(GENAI_NFT_NETWORKS); | ||
| const contractAddress = getGenAiNFTAddress(network); |
There was a problem hiding this comment.
Unused variable chainId.
Prepare for multichain deployment of the Image Generator (also on Base in the future)