Repository navigation
Refresh creator setup, minting, and verified IPFS publishing - #15
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 212f5d0aa6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const chain = getAppChain(getPrimaryAppChainId()); | ||
| // Rendering public pages must not require the entire contract deployment manifest. | ||
| const configured = process.env[`NEXT_PUBLIC_RPC_URLS_${chain.id}`] || process.env[`NEXT_PUBLIC_RPC_URL_${chain.id}`] || process.env.NEXT_PUBLIC_RPC_URL || ''; | ||
| const urls = configured.split(',').map(s=>s.trim()).filter(Boolean); | ||
| return createConfig({ chains: [chain], multiInjectedProviderDiscovery: true, connectors: [injected({ shimDisconnect: true }), ...(projectId ? [walletConnect({ projectId, metadata: { name: 'NFTFactory', description: 'Create and share your artwork', url: 'https://nftfactory.org', icons: [] }, showQrModal: true })] : [])], transports: { [chain.id]: urls.length > 1 ? fallback(urls.map(url=>http(url))) : http(urls[0]) }, ssr: true }); |
There was a problem hiding this comment.
Preserve configured secondary chains in Wagmi
Build the Wagmi configuration from all chains returned by getEnabledAppChains(), not only the primary chain. In deployments with multiple IDs in NEXT_PUBLIC_ENABLED_CHAIN_IDS, existing collection and listing management screens still enumerate secondary-chain inventory and call switchChainAsync for those chain IDs; Wagmi now knows only the primary chain, so those switches fail as unconfigured and transactions on every secondary chain become unusable.
Useful? React with 👍 / 👎.
| if (isIndexerProxyWriteMethod(method)) { | ||
| try { sessionToken = requireSession(request).token; } catch { return NextResponse.json({ error: "Sign in with your wallet before making changes." }, { status: 401 }); } |
There was a problem hiding this comment.
Exempt anonymous guestbook posts from session enforcement
Require a wallet session only for routes whose upstream mutation requires one. This blanket check rejects every POST before routing, including anonymous guestbook submissions from ProfileClient, which omit authorAddress; the indexer explicitly permits that case and only validates a session when an address is attached. Consequently, disconnected visitors always receive 401 even though the UI still presents the public guestbook composer.
Useful? React with 👍 / 👎.
| const receipt = await client.waitForTransactionReceipt({ hash: current.txHash!, timeout: 60_000 }); | ||
| if (receipt.status !== 'success') throw new Error('The transaction reverted. No NFT was created. Check its receipt before starting a new draft.'); |
There was a problem hiding this comment.
Let users clear a reverted mint transaction
Clear the stored transaction state or expose a reset action when a receipt reports reverted. Once txHash is saved, the form fields and Back button are disabled and the reset button is rendered only after a successful receipt; therefore a reverted transaction leaves the draft permanently pinned to that hash, and every retry merely checks the same failed receipt instead of allowing the user to correct or resubmit the mint.
Useful? React with 👍 / 👎.
| if (source !== "wallet") { | ||
| const verify = deps.verifyProfileIdentityImpl || (async (name: string, owner: string) => { | ||
| const rpc = process.env.ENS_IDENTITY_RPC_URL; | ||
| if (!rpc) return false; | ||
| const resolver = createPublicClient({ chain: mainnet, transport: http(rpc, { timeout: 8000, retryCount: 0 }) }); | ||
| const resolved = await resolver.getEnsAddress({ name: normalize(name) }); | ||
| return resolved?.toLowerCase() === owner; |
There was a problem hiding this comment.
Verify native subnames against their registrar
Handle nftfactory-subname separately from external ENS names. SubnameRegistrar.registerSubname records ownership only in the NFTFactory contract and does not create or update a mainnet ENS resolver record, so a subname created by the app will normally fail this getEnsAddress check and receive 403 when the UI tries to persist it. Verify native subname ownership against the configured registrar contract while retaining forward-resolution checks for actual ENS names.
Useful? React with 👍 / 👎.
| const manifest = { version: 1, publishedAt: new Date().toISOString(), profile: payload.profile }; | ||
| const result = await publishFile(new File([JSON.stringify(manifest)], 'profile.json', { type: 'application/json' }), 'profile.json'); |
There was a problem hiding this comment.
Restore throttling on profile publication
Apply a rate limit before publishing the profile manifest. Any user who signs one wallet challenge can currently repeat this endpoint without restriction; because publishedAt changes on every request, each call creates a distinct CID and performs primary pinning, replica pinning, and gateway verification, allowing inexpensive storage growth and pinning-service exhaustion. The previous route had a dedicated profile-publish limiter, but the replacement removed it.
Useful? React with 👍 / 👎.
| export default function CreatorSetupClient() { | ||
| const { address } = useAccount(); const { data: wallet } = useWalletClient(); | ||
| const [name,setName] = useState(''); const [bio,setBio] = useState(''); const [busy,setBusy] = useState(false); const [message,setMessage] = useState(''); const [saved,setSaved] = useState(false); | ||
| useEffect(()=> { if (!address) return; try { const value = JSON.parse(localStorage.getItem(`creator-draft:${address.toLowerCase()}`) || '{}'); setName(value.name || ''); setBio(value.bio || ''); } catch { /* Private browsing may disable draft storage. */ } setSaved(false); },[address]); |
There was a problem hiding this comment.
Load persisted profile data into the editor
Initialize the editor from the indexer's existing wallet profile rather than exclusively from localStorage. When a creator edits from another browser, clears site data, or returns after browser storage fails, this effect presents blank name and bio fields even though the public profile is still persisted server-side; the required-name check then forces the creator to reconstruct and potentially overwrite the existing display name before changing anything else.
Useful? React with 👍 / 👎.
| const isProfileWrite = req.method !== "GET" && req.url?.startsWith("/api/profiles/"); | ||
| if (isProfileWrite && pendingProfileWrites >= 50) { sendJson(res, 429, { error: "Profile saves are busy. Please retry." }); return; } | ||
| const run = () => handleRequest(req, res, deps, config); |
There was a problem hiding this comment.
Authenticate requests before entering the profile write queue
Do not enqueue unauthenticated requests before they have been bounded and authenticated. The directly reachable indexer has server.requestTimeout = 0, and the first queued /api/profiles/link or /api/profiles/transfer request does not call readJsonBody or validate its session until it reaches handleRequest; an attacker can therefore hold that request body open indefinitely, stall the single profileWrites chain, fill the 50-request queue, and make every legitimate profile save return 429.
Useful? React with 👍 / 👎.
| const params = searchParams ? await searchParams : undefined; | ||
| if (params?.advanced !== "1") return <CreatorSetupClient />; |
There was a problem hiding this comment.
Preserve advanced setup deep links
Route existing setup links carrying identity context to ProfileLandingClient, or update every caller to add advanced=1. Several existing profile actions still link to /profile/setup?label=..., and ProfileSelectorClient links with an identity mode and label; this unconditional branch now discards those parameters and opens the wallet-only editor, so actions such as “Open creator setup” no longer load the ENS identity they were invoked for.
Useful? React with 👍 / 👎.
| const limited = rateLimitRequest(request, { bucket: 'publish-v1', maxRequests: 5, windowMs: 300_000, errorMessage: 'Please wait before uploading again.' }); | ||
| if (limited) return NextResponse.json({ error: limited.error }, { status: 429, headers: limited.headers }); |
There was a problem hiding this comment.
Honor the configured metadata upload rate limit
Resolve this limiter from the documented IPFS_METADATA_RATE_LIMIT_* settings instead of hard-coding five requests. The environment templates and operations guidance still advertise these variables and a default of ten requests per five minutes, but the replacement route ignores both overrides and uses a different fixed limit, so operators cannot raise the ceiling for legitimate launch traffic and users are throttled earlier than configured.
Useful? React with 👍 / 👎.
| for (const baseUrl of this.apiBaseUrls) { | ||
| const target = buildIpfsAddUrl(baseUrl, { wrapWithDirectory }); | ||
| try { |
There was a problem hiding this comment.
Keep malformed endpoints inside the IPFS failover loop
Construct each add URL inside the try block so an invalid earlier entry does not abort failover. With IPFS_API_URLS containing a malformed first URL and a valid second URL, checkNodeHealth catches the first parse error and can report the node available, but addForm throws from buildIpfsAddUrl before its catch and never attempts the healthy endpoint, making publishing fail despite a successful preflight.
Useful? React with 👍 / 👎.
Creators previously faced a large mint form, ENS-led setup, duplicated upload code, and wallet dependency vulnerabilities. This release adds a three-step image mint flow and wallet-based creator pages, with a smaller navigation and responsive layout.
Publishing now requires a signed wallet session, bounded image upload, an independent pin, and exact-byte verification before returning metadata for minting. Submitted transaction hashes survive refresh, so retries check the receipt rather than minting twice. Profile mutations verify the signed owner, ENS links require forward-resolution verification, and an admin allowlist alone no longer authenticates requests. RainbowKit is removed; dependencies and Node 24/Vercel installation are updated.
Validation: web 203 tests, indexer 73 tests, contracts 121 tests, scripts 40 tests; TypeScript passes; production build and local startup pass; dependency audit has zero findings. Home/create/setup and wallet dialog inspected at desktop and 390px mobile widths. ESLint has no errors and existing warnings.
Operational limit: this is a protected release, not public-launch approval. Existing IPFS/indexer origins return HTTP 530, the offsite pinning provider is not configured, and no live wallet mint or outage/restore drill has passed. Publishing intentionally stays paused until storage is ready. Vercel has its private session key; the same key must be deployed to the indexer. No contract upgrade or mainnet transaction is included.
The ordered plan, implemented changes, remaining infrastructure work, and acceptance sequence are recorded in docs/wiki/Product-Refresh-Implementation-2026-09-11.md.