Skip to content

Refresh creator setup, minting, and verified IPFS publishing - #15

Merged
bobofbuilding merged 5 commits into
mainfrom
draft/nftfactory-independence-product
Sep 11, 2026
Merged

bobofbuilding merged 5 commits into
mainfrom
draft/nftfactory-independence-product

Conversation

@bobofbuilding

@bobofbuilding bobofbuilding commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

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.

@vercel

vercel Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
nftfactory Ready Ready Preview Sep 11, 2026 3:33am UTC

Request Review

@bobofbuilding bobofbuilding changed the title Draft: NFTFactory independence product Refresh creator setup, minting, and verified IPFS publishing Sep 11, 2026
@bobofbuilding
bobofbuilding marked this pull request as ready for review September 11, 2026 03:30
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-11T03:41:10.318516Z 212f5d0 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@bobofbuilding
bobofbuilding merged commit e7dde58 into main Sep 11, 2026
5 checks passed
@bobofbuilding
bobofbuilding deleted the draft/nftfactory-independence-product branch September 11, 2026 03:34

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread apps/web/lib/wagmi.ts
Comment on lines +8 to +12
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 });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines 63 to +64
if (isIndexerProxyWriteMethod(method)) {
try { sessionToken = requireSession(request).token; } catch { return NextResponse.json({ error: "Sign in with your wallet before making changes." }, { status: 401 }); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +57 to +58
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.');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +7861 to +7867
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +12 to +13
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');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +9001 to +9003
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines 14 to +15
const params = searchParams ? await searchParams : undefined;
if (params?.advanced !== "1") return <CreatorSetupClient />;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +9 to +10
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 });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +107 to +109
for (const baseUrl of this.apiBaseUrls) {
const target = buildIpfsAddUrl(baseUrl, { wrapWithDirectory });
try {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

This branch was successfully deployed

1 active deployment
Preview — c6986242 Deployed Sep 11, 2026 by vercel[bot]
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.

1 participant