Raisonne
One install equals one artist. The site shows every series and work with the on-chain record behind it, and keeps the CV, exhibitions, press and the rest of the practice around the work.
Catalogue data is files under src/fixtures, not a required database. Wallet sign-in, collectors, insights and a shop are optional modules — off until you configure them. When something is missing, pages name the variable. They do not invent a number or fail quietly.
src/fixtures/local/ and never ship in the public repo.git clone https://github.com/orkhan-art-web/raisonne-os.git
cd raisonne-os
pnpm install
cp .env.example .env.local # optional
pnpm dev # http://localhost:3000Needs Node 20+ and pnpm. Production-shaped: pnpm build && pnpm start. Checks: pnpm exec tsc --noEmit and pnpm lint.
| Path | Command | Writes |
|---|---|---|
| From an existing site | pnpm snapshot | local/site.json (+ guild/store when present) |
| From the chain | pnpm snapshot:chain | local/chain.json holders, events, insights |
| Import tool (replay) | /import | UI flow; live importer still shipping |
| Hand edit | JSON under fixtures | Shapes in src/lib/types.ts |
A full walkthrough of standing up your own Raisonne. Each chapter is its own clip so you can skip to the step you need. Draft voice for now; the picture is the real screens and terminal frames.
Landing blocks are optional in the data: hero, showreel, stats, featured work, catalogue strip, partners, news, newsletter. Empty blocks are simply omitted — a fresh install is not forced to fill every slot.
| Feature | Where | Notes |
|---|---|---|
| Home | / | Artist, featured series, selected press |
| Works index | /works | Series and one-of-ones; filter by chain and kind |
| Series | /works/[series] | Record, attribution, works grid |
| Series about | /works/[series]/about | Long-form series essay |
| Work | /works/[series]/[token] | Media, facts, provenance |
| About | /about | Bio, studio, minting addresses |
| CV | /cv | Print-ready full record |
| Exhibitions / awards / press / writings | Matching routes | Only if data exists |
| Installations / physical works / collaborations | Matching routes | Non-token records |
| Privacy / terms | /privacy, /terms | From settings.legal; empty text stays out of nav |
Three kinds in the type system: series (a contract the artist deployed), one-of-one (a single-work contract), and shared-platform (the artist’s tokens on a marketplace contract many people use). Filters on /works use chain and kind from that data.
A series can be hidden: it stays out of the public index, sitemap and catalogue API while remaining in the fixture for the artist. Sub-series nest under a parent via parentSlug when the record says so — the parent page lists children first.
/works/paste-grounds.Open the series
Read the record
Check attribution
Browse works
/works/[series]/about when the data has one.Identity is chain:contract:tokenId in the catalogue API; the human URL is series slug plus a token segment. Media can be still, video, or interactive HTML — live HTML is off by default in settings and sandboxed when on.
Traits, categories, file facts and marketplace links come from the fixture. Holder names on the work page stay off unless settings.showOwners is on — collectors’ privacy is the default. A work can be featured, one-of-one, or hidden the same way a series can.
Raisonne does not ask visitors to trust a spreadsheet. Series pages and import review show the signals that tied a contract to the artist:
deployer — wallet deployed the contractownership-log — wallet appeared as owner in logs (including factory deploys)owner — current owner() matchtoken-creator — creator pattern on the tokenstorefront-decode — marketplace storefront decodingminted-to — mint destination evidenceDuring import, contracts that look unrelated are labelled clearly (including “probably not yours”) so the artist can leave them out of the catalogue with the reason still in view.
In development the workbench tools are on. In production set RAISONNE_TOOLS=1 for both build and start to serve /import (and the design system). Routes that are off answer 404. Setting this on a public host publishes the workbench URLs to anyone who types them — switch it off when the work is done. The public docs at /docs do not need this flag.
Only an owner session can open the importer. Configure RAISONNE_OWNER_ADDRESSES and RAISONNE_SESSION_SECRET, then sign in from the account menu. Everyone else sees a lock screen that says so. Docs and design-system share the tools flag but do not require an owner session.
Paste wallets
Watch the passes
Review series
Preview works and summary
| Pass | Looks for | Why |
|---|---|---|
| Deployed contracts | Contracts a wallet deployed itself | Strongest ownership signal |
| Ownership logs | Contracts that ever made a wallet their owner | Catches factory deploys |
| Held contracts | Contracts a wallet holds tokens in | Plus who deployed them |
| Owner check | owner() on every candidate today | Current on-chain owner |
| Shared-contract mints | Works created on marketplace shared contracts | Many artists, one contract |
| Works and media | Tokens and stills for every series found | Feeds the preview step |
Series review is the decision surface. Evidence badges (deployer, ownership log, owner, token-creator, storefront decode, minted-to) explain each attribution. Contracts that are not yours stay visible so you can reject them with context, not hide them.
Today the page replays a recorded run (demo demo-import.ndjson or your local/import-events.ndjson) at original timing through /import/replay, so the UI matches a live stream. The reducer and components do not change when a live source is wired in — only the event feed does.
| File | Role | Shipped? |
|---|---|---|
demo.json | Demo Artist for a fresh clone | Yes |
local/site.json | Your SiteData — replaces demo when present | Gitignored |
local/chain.json | Holders, events, leaderboard, insights | Gitignored |
local/guild.json | Tiers and badges | Gitignored |
local/store.json | Products, variants, shipping | Gitignored |
local/import-events.ndjson | Recorded import run | Gitignored |
local/snapshot-report.json | What snapshot skipped and why | Gitignored |
demo-import.ndjson | Short demo import replay | Yes |
src/fixtures/index.ts is the only reader. Pages call getSiteData(). Components take domain types from src/lib/types.ts, never raw CMS shapes. Edit a file and the next request reloads it when the mtime changes.
RAISONNE_CMS_URL=https://x.art pnpm snapshot
pnpm snapshot -- --max-works=200
pnpm snapshot -- --skip=works,press
ALCHEMY_API_KEY=… pnpm snapshot:chain
pnpm snapshot:chain -- --series=paste-grounds
pnpm snapshot:chain -- --max-events=2000
pnpm snapshot:chain -- --dry-runnext/image only loads hosts discovered from the fixtures at startup, plus optional RAISONNE_IMAGE_HOSTS. Restart after changing hosts. Public IPFS gateways and marketplace CDNs make this install an open image proxy for those hosts — fine on a private install; pin and self-host if that matters on yours.
SIWE (EIP-4361) with viem. No email, no password, no third-party auth service. The server issues a nonce, verifies the message, and sets an httpOnly signed cookie. Smart-contract wallets need ALCHEMY_API_KEY for ERC-1271 / ERC-6492; without it only key-pair wallets work, and the page says so.
| Variable | What it does | Need |
|---|---|---|
RAISONNE_SESSION_SECRET | ≥32 characters; signs the cookie | Often |
RAISONNE_OWNER_ADDRESSES | Comma-separated owner wallets | Often |
RAISONNE_SITE_URL | Public origin; SIWE domain default | Often |
RAISONNE_SIWE_DOMAIN | Override SIWE domain | Optional |
RAISONNE_SESSION_TTL_MINUTES | Default 60, max 1440 | Optional |
ALCHEMY_API_KEY | Holdings + smart-wallet verify | Optional |
A collector is an address. Nothing about a person is stored: no name, no email, no profile database. Holdings are read from the chain (cached a few minutes) with a re-sync control. Leaderboard and insights come from local/chain.json, never by replaying every transfer on page load.
| Feature | Where | Notes |
|---|---|---|
| My collection | /collector | Signed-in holdings, tier, badges |
| Public collector page | /collector/[address] | Only if settings.publicCollectorProfiles |
| Collectors directory | /collectors | Module collectors |
| Leaderboard | /leaderboard | From chain snapshot |
| Guild / tiers | /leaderboard/guild | When guild data exists |
| Orders | /orders | Shop purchases for this wallet/email flow |
RAISONNE_TOOLS: Docs, Import, Design system.Module insights. Pages under /insights summarise what the chain snapshot says about the work: activity, collections, sales stats when prices are known, and gaps when history was truncated or a sale price could not be read. Missing Alchemy or snapshot → designed panel with the command to run, not a blank chart.
Module store. Cart in local storage holds slugs, variant ids and quantities only. Every price a visitor sees and every amount Stripe charges is computed on the server from fixtures. Orders default to JSON files under .data/orders (persist across deploys). Print-on-demand provider dispatch is a stub in this release — flow and pages exist; the provider call does not.
| Feature | Where | Notes |
|---|---|---|
| Shop | /shop | — |
| Product | /shop/product/[slug] | — |
| Collection / category | /shop/collection/… | /shop/[category] |
| Cart | /cart | — |
| Checkout | /checkout | — |
| Orders | /orders | — |
Commissions module exposes /commissions and a request form when configured. Drops announce releases with phases, countdown, notify dialog and calendar — data-driven from the fixture, linked from series when seriesSlug is set.
In settings.modules inside the site data. Off modules drop out of nav, sitemap and the catalogue API. Known ids:
Related settings (not modules): liveHtml, showOwners, publicCollectorProfiles, analytics, maintenance, allowAiCrawlers, redirects, legal.
| Name | Route / entry | Notes |
|---|---|---|
| About | /about | Artist, studio, minting addresses |
| Activity (insights) | /insights/activity | Module insights |
| Awards | /awards | List + detail |
| Cart | /cart | Module store |
| Checkout | /checkout | Stripe server prices |
| Checkout complete | /checkout/complete | Post-Stripe return |
| Collaborations | /collaborations | — |
| Collector (mine) | /collector | Signed in |
| Collector (public) | /collector/[address] | Opt-in setting |
| Collectors directory | /collectors | — |
| Commissions | /commissions | + /commissions/request |
| CV | /cv | Print; footer off |
| Design system | /design-system | Artist tool |
| Docs | /docs | This page; artist tool |
| Drops | /drops/[slug] | Module drops |
| Exhibitions | /exhibitions | + /[slug] |
| Guild / tiers | /leaderboard/guild | — |
| Home | / | — |
| Import | /import | Owner-gated tool; wallet → series |
| Import replay feed | /import/replay | NDJSON event stream |
| Insights | /insights | + /activity, /collections |
| Installations | /installations | + /[slug] |
| Leaderboard | /leaderboard | — |
| Legal | /privacy | /terms |
| Maintenance | /maintenance | Whole-site gate |
| Newsletter API | POST /api/newsletter | Needs forward URL |
| Order detail | /orders/[id] | Buyer session |
| Orders | /orders | — |
| Physical works | /physical-works | + /[slug] |
| Press | /press | + /[slug] |
| Series | /works/[series] | + /about |
| Shop | /shop | product, collection, category |
| Sign in / out | /auth | /signout; /signin redirects |
| Update app | /update | Owner; code not data |
| Work detail | /works/[series]/[token] | — |
| Works index | /works | — |
| Writings | /writings | Module writings; + /[slug] |
Full list lives in .env.example. Nothing is required for the bare catalogue. Copy to .env.local (gitignored). Never put secrets in fixtures.
| Variable | What it does | Need |
|---|---|---|
RAISONNE_SITE_URL | Canonical origin, sitemap, share cards, SIWE default host | Often |
RAISONNE_SESSION_SECRET | Sign-in cookie signing (≥32 chars) | Often |
RAISONNE_OWNER_ADDRESSES | Owner wallets for artist tools | Often |
ALCHEMY_API_KEY | Chain reads and smart-wallet sign-in | Optional |
RAISONNE_TOOLS | 1 serves /import and /design-system in production (/docs is always public) | Optional |
RAISONNE_FIXTURES | demo forces demo artist even with local data | Optional |
RAISONNE_IMAGE_HOSTS | Extra next/image hosts | Optional |
RAISONNE_CMS_URL | Source for pnpm snapshot | Optional |
RAISONNE_NEWSLETTER_URL | Forward newsletter POSTs | Optional |
RAISONNE_MAINTENANCE | 1 closes the site regardless of data | Optional |
RAISONNE_TRUSTED_PROXY | Trust X-Forwarded-For from your proxy | Optional |
STRIPE_SECRET_KEY | Checkout (test keys exercised) | Optional |
STRIPE_WEBHOOK_SECRET | Required if webhooks are enabled | Optional |
RAISONNE_ORDERS_DIR | Order JSON directory (default .data/orders) | Optional |
RAISONNE_UPDATE | 1 allows /update apply in production | Optional |
RAISONNE_UPDATE_REPO | GitHub owner/name for releases | Optional |
GET /api/catalogue
GET /api/catalogue/series?limit=100
GET /api/catalogue/series/<slug>
GET /api/catalogue/works/<chain:contract:tokenId>
GET /api/catalogue/globals/artistGET and HEAD only. Domain shapes the pages already render. Anything that sounds like personal data (collector, customer, order, commission, credential) answers 404 whether or not this install has the type. Off modules contribute no records. Also published: /sitemap.xml, /robots.txt, /manifest.webmanifest, Open Graph image routes.
Data refresh is pnpm snapshot / pnpm snapshot:chain. Code refresh is /update or:
pnpm update:check
pnpm update:raisonne
pnpm build && pnpm startApply needs a clean git checkout of the upstream repo (or RAISONNE_UPDATE_REPO), owner session on the web path, and RAISONNE_UPDATE=1 in production. Never touches src/fixtures/local/, .data/, or .env*. Restart after apply — the running Node process does not hot-reload.
Agents installing or extending the app: read CLAUDE.md and AGENTS.md in the repo root. Security reports: SECURITY.md.