Z:Drive is an artist-first release platform on Zora where creators "coin" releases (not posts): images, videos, PDFs, 3D print files, GitHub releases, zips, etc. A release is a Zora content coin whose metadata describes a bundle of assets and a license policy. Z:Drive emphasizes viewing + interaction first (Are.na-like), with collecting/trading secondary.
- Make it easy for indie creators of “things” to publish releases as Zora content coins with arbitrary assets.
- Provide a beautiful release page with native preview for key formats.
- Support collection/series grouping that is stable forever (no migrations).
- Support a16z “Can’t Be Evil” (CBE) licensing with an optional hold threshold gate based on release coin balance.
- Provide an artist-first feed driven by holdings: creator coin holdings ≈ “following”; content coin holdings ≈ “likes/collects”.
- SDK-first: All coin creation, queries, and trading use Zora Coins SDK and Zora’s default/official endpoints. No custom token contracts, no custom AMMs, no bespoke licensing contracts, no bespoke bundle-checkout mechanics.
- No custom “pair with arbitrary community coin” mechanics (only what Zora supports via SDK).
- No DRM / download gating in MVP (licensing is public signaling + social/legal enforcement).
- Collections are metadata + UI only (no onchain collection contract in MVP).
Each release is a Zora content coin created via Zora Coins SDK. Creators can:
- Pair with their creator coin (preferred), or
- Create a standalone release paired with ETH if they don’t have/don’t want a creator coin.
A release can belong to at most one collection.
Collection identity must be stable and unspoofable:
collection.id = "<chainId>:<creatorAddress>:<collectionSlug>"
Example: 8453:0xabc123...:tyrolean-chair-studies
Collection page logic: list all releases where properties.zdrive.collection.id matches exactly.
License system = a16z Crypto “Can’t Be Evil” (CBE) NFT licenses. These license texts are published immutably (canonical references commonly point to Arweave) and are intended to be linkable/verifiable by reference (URI + license name/type).
- Baseline state: All rights reserved unless you qualify.
- Creator selects one CBE license type for the release.
- Optional gate: viewer must hold ≥ minBalance of the release coin to be shown as “Licensed under CBE X.”
- If viewer does not qualify (or gate enabled and not met): show “All rights reserved.”
No custom licensing contracts in MVP. Z:Drive stores:
cbe.type(which CBE license)cbe.textUrl(canonical license text URI, e.g., Arweave)- optional gate config
Use standard token metadata fields for display + preview, and store Z:Drive-specific data under properties.zdrive. Store “preview target” separately from attachments.
name(string)description(string)image(cover/thumbnail; required)properties.zdrive.schemaVersion = 1
-
contentfor the primary preview target file (image/video/PDF/GLB/GLTF/PLY), when applicable:content.mimecontent.uricontent.name(original filename — used for extension-based type detection when MIME is unreliable)
-
properties.zdrive.release.assets[]for attachments (any file types) -
properties.zdrive.release.external[]for offsite sources (GitHub, etc.) -
properties.zdrive.collectionfor grouping + ordering -
properties.zdrive.licensefor CBE + optional gate
{
"name": "Tyrolean Chair Studies #12",
"description": "Ongoing release series…",
"image": "ipfs://<cover>",
"content": { "mime": "model/gltf-binary", "uri": "ipfs://<glb-file>", "name": "model.glb" },
"properties": {
"zdrive": {
"schemaVersion": 1,
"release": {
"assets": [
{
"name": "source.zip",
"mime": "application/zip",
"uri": "ipfs://<zip>",
"sha256": "<hex>",
"size": 12345678
}
],
"external": [
{
"type": "github",
"url": "https://github.com/org/repo",
"ref": "v1.0.0"
}
]
},
"license": {
"baseline": "ALL_RIGHTS_RESERVED",
"cbe": {
"type": "CBE_COMMERCIAL",
"textUrl": "arweave://<canonical-license-uri>"
},
"gate": {
"enabled": true,
"token": "RELEASE_COIN",
"minBalance": "10000"
}
},
"collection": {
"id": "8453:0xCreator:tyrolean-chair-studies",
"slug": "tyrolean-chair-studies",
"title": "Tyrolean Chair Studies",
"ordering": { "index": 12 }
}
}
}
}- Always include
schemaVersion. - Never remove fields; only add new optional fields in future versions.
- Images: JPG, PNG, GIF, WebP, SVG — full-size viewer with zoom.
- Video: MP4 — native HTML5 video player.
- PDF: embedded viewer.
- 3D Models: GLB/GLTF — three.js viewer with orbit controls + error boundary.
- Point Clouds: PLY (ASCII format only) — three.js point cloud viewer with orbit controls.
- GitHub: render README + show repo/ref metadata (external link model).
- STL: Zora's IPFS endpoint does server-side magic-byte sniffing; binary STL has no recognizable magic bytes → rejected as
application/octet-stream. Use GLB/GLTF instead. - WebM: Failed empirical upload test against live Zora endpoint.
- ZIP: Explicitly blocked by Zora's IPFS endpoint even when correctly detected.
- Binary PLY / SPLAT: Zora rejects binary point cloud formats. Only ASCII PLY (detected as
text/plain) is accepted.
- Everything else: download link + metadata.
- Zora-only: All uploads go through Zora's native IPFS uploader (
ipfs-uploader.zora.co). No Pinata fallback. - Server-side MIME sniffing: Zora ignores client-sent MIME types and detects format from raw bytes.
ensureCorrectMime()still needed for GLB/GLTF because browsers assignapplication/octet-streamto these, and the multipart form boundary needs a recognizable MIME. - IPFS gateway:
magic.decentralized-content.com(Zora's gateway, CORS-friendly for binary files like GLB).
Zora's uploader is free but does server-side magic-byte validation, rejecting files it can't identify (STL, binary PLY, SPLAT, ZIP, etc.). Pinata accepts any bytes but costs ~$20+/month.
MVP decision: Stay with free Zora uploader. The supported formats (images, video, PDF, GLB/GLTF, ASCII PLY) cover most creator use cases. If user demand for exotic formats (STL, binary point clouds) is validated, add Pinata as a fallback — catch Zora's 400 errors and retry with Pinata.
-
Creator identity / avatar
-
Creator coin info (if exists)
-
Releases grid, filter by:
- Collection
- File type (Image / Video / PDF / 3D / GitHub / Zip / Other)
-
“Follow strength” indicator = creator coin balance tiers (UI-only)
Main column: preview (PDF/3D/font/specimen/README) Side panel:
- Release metadata (creator, created date, formats, hashes)
- License panel (CBE + gate status; shows qualification)
- Collect/trade widget (secondary CTA)
- Collection context (link to collection page + position/index)
-
Collection title + cover (use first release cover if none provided)
-
Releases list/grid
-
Ordering:
- If
ordering.indexexists, sort ascending - Else sort by created date
- If
Holdings-driven activity:
- New releases from creators whose creator coin viewer holds (weighted by balance)
- Collects by those creators
- Collects of those creators’ releases by others
Tabs:
- Feed (default)
- Explore (new releases chronologically)
- Markets (volume/mcap sorts; secondary)
-
Connect wallet (Base, EOA).
-
Choose pairing mode:
- “Pair with my creator coin” (default)
- “Standalone (ETH)” (fallback)
-
Upload:
- Cover image (required)
- Optional preview target file (image/video/PDF/GLB/GLTF) OR GitHub external preview
- Attachments (any)
-
Set collection (optional):
- Create new collection (slug/title)
- Or choose existing (by
collection.id) - Optional ordering index
-
Set license:
- Choose a16z CBE type
- Optional gate: min release-coin balance
-
Mint/create content coin via Zora Coins SDK (standard functions only):
- Metadata as
RAW_URI - Set platform referrer to Z:Drive (referral rewards)
- Metadata as
-
Confirm + land on Release Page.
- Fetch viewer’s release-coin balance.
- If balance ≥ minBalance (or no gate): show “Licensed under CBE X”
- Else: “All rights reserved”
- No download gating beyond what’s public.
- User login (preferred / MVP): wallet connect = login.
- Optional: SIWE for a server session if needed for drafts, preferences, saved views.
- Zora developer access: backend uses a Zora API key obtained via Zora’s official developer settings/login (for production reliability).
- Use Zora uploader for assets by default (no custom storage stack in MVP).
- Store asset URIs in metadata.
- For very large assets or GitHub-based releases, store as
external[]links.
- Platform referral rewards: set Z:Drive as platform referrer at coin creation (SDK-supported).
- Trade referral rewards: set when routing trades via the app (SDK-supported).
-
A creator can mint a release coin with:
- cover image,
- any number of attachments,
- optional preview target (image/video/PDF/3D) or GitHub external,
- optional collection membership (stable
collection.id), - a16z CBE license with optional gate (release coin balance).
-
A viewer can:
- browse creator pages, releases, collections,
- see a native preview for images/video/PDF/GLB/GLTF/GitHub,
- see license status based on their release-coin balance,
- collect/trade from the release page.
-
No schema migrations required to add richer collection pages later.
All six phases of the initial build plus post-merge fixes are complete and merged to main.
- Replaced Pinata IPFS with Zora native uploader (
createZoraUploaderForCreator) - New
lib/uploads/zoraUploader.ts:ZoraUploadServicewith parallel uploads, retry, SHA-256 hashing - Rewrote
lib/zora/createRelease.tsto use new service - Deleted
app/api/upload/,app/api/upload-json/,lib/uploads/ipfsUploader.ts - Fixed
publicClienttype cast for Base OP Stack chain types - Tests:
upload-pipeline.test.ts,metadata.test.ts,zdrive-types.test.ts
- Created
PreviewRendererdispatcher: routes by MIME type → ImageViewer, VideoPlayer, PDFViewer, ThreeDViewer, GitHubPreview, or fallback - Created
ImageViewer(click-to-zoom fullscreen) andVideoPlayer(HTML5 with poster frame) - Rewrote
app/[creator]/[releaseAddress]/page.tsx: two-column responsive layout with preview, trade widget, license panel, collection context, metadata details - Updated
CollectButtonwith inlineCoinTradeWidgetinstead of external Zora link - Removed
FontViewer(fonts no longer a supported preview type) - Tests:
preview-renderer.test.ts
- Created
hooks/useCreatorCollections.ts: scans existing releases for collection metadata - Updated
CollectionPicker: "Existing / Create New" toggle with auto-populated fields - Added upload progress state with phase indicators to create page
- Updated preview file accept types: added image/video, removed fonts
- Deleted
FontViewer.tsx - Updated
PREVIEWABLE_MIMES(removed fonts, added image/video) - Bounded metadata cache:
MAX_CACHE_SIZE = 200with LRU eviction - Verified all Pinata references removed
- Created
ErrorBoundaryclass component (components/ui/ErrorBoundary.tsx) - Wrapped
PreviewRendereron release page - Added
retry: 2touseReleaseanduseCoinhooks - Added
staleTime: 30_000touseRelease
- Fixed unescaped apostrophe build error
- Added
Suspenseboundary for search page (useSearchParams) - Type safety audit: only 3 justified
as unknowncasts remain - Updated marketing copy (fonts → images/videos)
- Production build passes, 85 tests passing across 6 test files
formatTokenBalancenow correctly handles 18-decimal ERC-20 balances- API returns raw integers like
"990000000000000000000"(= 990 tokens); previouslyparseFloattreated the whole string as a regular number - Now strips API decimal noise (
.split('.')[0]), converts from 18-decimal, formats with K/M/B abbreviations - Tests:
format-balance.test.ts(11 tests)
- Empirically tested 15+ MIME/magic-byte combinations against live Zora IPFS endpoint
- Dropped STL (no magic bytes), WebM (failed test), ZIP (explicitly blocked)
- Simplified to Zora-only upload (removed Pinata fallback code)
- Fixed Collect button ticker: uses real on-chain symbol from
useCoin()instead of name truncation - Switched IPFS gateway from
ipfs.ioto Zora'smagic.decentralized-content.com(better CORS) - Added
GLTFErrorBoundaryto ThreeDViewer for graceful failure with download fallback - Removed STL viewer code, STLLoader import, and all STL/WebM/ZIP references across codebase
- 90 tests passing, clean production build
- Quote failure resilience:
useTradeQuotenow retries 2x with exponential backoff. Quote errors categorized into transient (retrying), quote_unavailable, and unknown. TradePanel shows contextual error messages with a manual Retry button. CTA disabled when no quote available. - Post-creation seed buy: After successful coin creation, users see a "Seed your coin" prompt offering a 0.0001 ETH buy via
tradeCoinSDK. Fully skippable — Skip redirects to release page normally. - Creator coin pairing:
CreateReleaseInputacceptscurrencyfield (ETH|CREATOR_COIN_OR_ZORA). NewCoinPairingPickercomponent in Options step with radio toggle. Auto-defaults to creator coin when wallet has one (detected viauseCreatorProfile). ETH always available as fallback. Shows zora.co link when no creator coin exists. - Error categorization module: New
lib/trade/quoteErrors.tswith pattern-based error categorization for trade quote failures. - 114 tests passing across 9 test files, clean production build
- PointCloudViewer component: New
components/preview/PointCloudViewer.tsxusing Three.js PLYLoader for ASCII PLY files - Empirical testing: Binary PLY and SPLAT formats rejected by Zora; only ASCII PLY (detected as
text/plain) works - Filename-based routing: Added
content.namefield to metadata schema to store original filename for extension-based type detection - MIME fallback chain: Fixed empty string MIME handling — Zora uploader returns
""notundefined, so changed from??to||in fallback chain - PreviewRenderer fix: Now routes by filename extension when MIME is empty/missing
- Promise.withResolvers polyfill: Added for Node 20 compatibility (Three.js 0.169+ requires Node 22 API)
- Files modified:
createRelease.ts,metadata.ts,PreviewRenderer.tsx,zdrive.ts,zoraUploader.ts,instrumentation.ts,polyfills.ts - 109 tests passing, clean production build
- Framework: Next.js 14 (App Router,
'use client'pages) - Language: TypeScript strict
- Styling: Tailwind CSS with
zdrive-*design tokens - Auth: Privy (
@privy-io/react-auth) - Wallet: wagmi v2 + viem (Base chain)
- Blockchain: Zora Coins SDK v0.4.0 (
@zoralabs/coins-sdk) - 3D: Three.js via
@react-three/fiber+@react-three/drei - State: React Query (
@tanstack/react-query) - Testing: Vitest
- Linting: ESLint + Prettier
- Pinata fallback for exotic file types (STL, WebM, ZIP etc.) — add when user demand is validated
- Optional server-side conversion pipeline (OBJ/STL → GLB)
- UX polish pass + design system / branding
- Stronger feed via event indexing
- Optional "license receipt" proof page for qualifying holders
- Optional onchain "collection coin" as an augmentation only (must not replace
collection.idgrouping)