// every artist's site is a node in the network // no central server owns the data chain Optimism (chain ID 10) // Ethereum L2 block explorer: optimistic.etherscan.io contracts (optimism, 9 total — verified on optimistic.etherscan.io) 0x4bC73F9C...336 ArtistRegistry // identity + follow graph 0xaB7C23Ac...e33 Praxis // projects + ERC-6909 credentials 0xbC74c3D8...44D PraxisInvites // invite codes + EIP-712 redeem + referral chain 0xf93de3d5...De9 BlogRegistry // on-chain blog posts 0xaF995dB3...481 PraxisMedia // soulbound media tokens + splits 0x0ea62A91...74c PraxisTicketMarket // secondary ticket market 0x59a4f01b...Aa2 LibraryRegistry // shared knowledge (IPFS-backed) 0x15F5f22F...F6A ArtistSponsoredInvites // EIP-712-protected sponsored registration 0xE37C4f22...8Ef PraxisTreasury // auto-sweep ETH->USDC, forward to EtherFi Cash wallet embedded wallet (BIP-39) // created in browser, no extension needed AES-256-GCM + PBKDF2 encryption // private key never leaves browser unencrypted exportable // import into MetaMask, Rabby, Rainbow anytime messaging XMTP v7 // peer-to-peer, end-to-end encrypted // DMs + group chats for project teams indexer Ponder v0.7 // GraphQL API for all on-chain data (15 tables) hosting multi-tenant (default) // one Node.js process serves all artists via Host header Docker containers (auto-promoted) // dedicated container for high-traffic artists Traefik // auto TLS, reverse proxy domain lifecycle purchase: 2 years included // domain cost passed through from registrar renewal: annual, at actual cost // no markup -- pay what the registrar charges self-host: always free // fork the repo, run your own // what happens if you don't renew: 60 days before expiry // banner warning on your site domain expires // site stays up during 30-day grace period 30 days past expiry // container suspended (docker stop) 90 days past expiry // data archived, container removed // you can always export your data or self-host before any deadline navigation soft-SPA // fetch+replace, preserves player state persistent audio player // bottom bar, survives page navigation i18n 18 languages // EN ES FR PT DE AR ZH JA KO HI SW RU BN FA ID TR UR VI auto-detect + manual switcher // RTL support for Arabic, Farsi, Urdu domain purchase NameSilo API integration // search, purchase, DNS config in one flow; free WHOIS privacy, JSON API, payment_id for direct billing cost pass-through, no markup // artist pays registrar price Traefik dynamic routing // custom domains auto-routed with TLS validation Zod runtime validation // all API endpoints validated at runtime JSDoc type annotations // server + orchestrator fully documented feed server-side timeline endpoint // /api/feed/timeline -- aggregated, cached 30s replaces client-side multi-query // single request, server-materialized testing 3700+ tests total // vitest (3185) + Foundry (481) + e2e (48) + Playwright EIP-712 front-running regression tests // recipient substitution, nonce replay, expiry, paused
// no browser extension required // wallet created instantly during signup architecture BIP-39 mnemonic (12 words) // standard recovery phrase, works in any wallet HD wallet derivation (m/44'/60'/0'/0/0) // Ethereum standard path private key stays in browser // never sent to server unencrypted encryption PBKDF2 key derivation // password -> 256-bit encryption key 600,000 iterations, SHA-256 // resistant to brute force AES-256-GCM authenticated encryption // encrypts private key + mnemonic unique salt + IV per encryption // no key reuse recovery 12-word recovery phrase (BIP-39) // download during setup recovers wallet if password is lost // re-derive private key from mnemonic compatible with MetaMask, Rabby, etc // import your phrase anywhere server backup encrypted blob stored server-side // AES-256-GCM ciphertext only server cannot decrypt // no plaintext key ever leaves browser restores wallet on new device // enter password to decrypt biometric WebAuthn / credential API // fingerprint or face to confirm transactions fallback to password confirmation // if biometric not available portability export private key or recovery phrase // from settings → account import into MetaMask, Rabby, Rainbow // standard BIP-39 compatible credentials travel with your keys // not locked to praxis
// end-to-end encrypted messaging via XMTP v7 // peer-to-peer -- no praxis server can read messages protocol XMTP v7 // Extensible Message Transport Protocol MLS (Messaging Layer Security) // group encryption standard (RFC 9420) end-to-end encrypted // messages encrypted on device before sending features DMs (mutual follows only) // follow graph gates who can message you project group chats // auto-created for project collaborators replies + reactions // threaded conversations with emoji reactions send ETH in-chat // crypto payments embedded in messages installation management multi-device support // each device registers as an XMTP installation static revoke // revoke all old installations at once individual revoke // revoke specific installations by ID // prevents stale installations from accumulating bundles vendor-xmtp.js 165KB // esbuild bundle, lazy-loaded (messages page only) vendor-xmtp-reaction.js 1.6KB // reactions codec, lazy-loaded WASM workers // xmtp-workers/client.js + opfs.js (loaded from esm.sh) privacy no server-side message storage // praxis infrastructure never sees plaintext mutual follow filter // only artists who follow each other can DM inbox ID resolved per-wallet // XMTP identity tied to Ethereum address
chain: Scroll (10) | explorer: optimistic.etherscan.io | 3700+ tests (vitest + Foundry + e2e)
| contract | address | purpose |
|---|---|---|
| ArtistRegistry | 0x4bC73F9CC7C7a84B5Cf20e1469Ad65f8b5448336 | identity + follow graph |
| Praxis | 0xaB7C23Ac815F03059026fEC32c60a06E5E4d4e33 | tiered funding, ERC-6909 credentials, dispute window, revenue sharing |
| PraxisInvites | 0xbC74c3D815beC49507826A6b9e07E7f086FB744D | invite codes, referral tracking, auto-invite on completion |
| BlogRegistry | 0xf93de3d5f025915C5e28c40382DE4946c7b18De9 | on-chain blog posts with comments + references |
| PraxisMedia | 0xaF995dB3955419E9E2086FD02891580F8a025481 | soulbound media tokens, revenue splits |
| PraxisTicketMarket | 0x0ea62A91acE3D77Bc96d77f1B05Ff3C1C60aF74c | secondary market for transferable tickets |
| LibraryRegistry | 0x5CdDD64f20C69fC2007868476788BC3766C28A0A | shared knowledge library (IPFS) |
| ArtistSponsoredInvites | 0x15F5f22F130ecEF5eee15d9BA90bB73B287a4F6A | trustless artist-to-artist sponsored registration |
| PraxisTreasury | 0x5CF9E88417A7cE08028D32C44F9b63bc3d960b21 | auto-sweep ETH->USDC via Velodrome, forward to EtherFi Cash |
all verified on blockscout. all immutable. all open source.
// identity + follow graph 0x4bC73F9CC7C7a84B5Cf20e1469Ad65f8b5448336 public functions register(domain, signature) payable // register wallet with verified domain unregister() // delete account, remove from registry updateDomain(newDomain, signature) // change domain (orchestrator-signed) transferRegistration(newWallet, signature) // move registration to new wallet registerSupporter(handle) // register as supporter (no fee, no signature) follow(target) // follow another artist or supporter unfollow(target) // unfollow an artist view functions isUser(addr) -> bool // check if address is registered (artist or supporter) artists(addr) -> (string domain, uint256 registeredAt) // get artist profile isFollowing(a, b) -> bool // does a follow b? isMutual(a, b) -> bool // mutual follow check getFollowers(addr) -> address[] // list of followers getFollowing(addr) -> address[] // list of following events Registered(address indexed wallet, string domain) Unregistered(address indexed wallet, string domain) RegistrationTransferred(address indexed oldWallet, address indexed newWallet, string domain) Followed(address indexed follower, address indexed followed) Unfollowed(address indexed follower, address indexed followed)
// unified projects + ERC-6909 credentials 0xaB7C23Ac815F03059026fEC32c60a06E5E4d4e33 project functions proposeProject(title, description, projectType, collaborators[], splits[], fundingGoal, deadline, tierNames[], tierPrices[], tierMaxSupplies[], tierTransferable[], revenueSharePercent, location, disputeWindowDays, autoComplete, confirmationMode) // create project proposal (16 params) fundTier(projectId, tierId, quantity) // fund a tier, mints TICKET or PRODUCER withdrawFunding(projectId) // withdraw before goal met (liquid market) confirmProject(projectId) // proposer + majority collaborators completeProject(projectId) // starts 3-day dispute window dispute(projectId) // funders dispute during window finalizeProject(projectId) // after 3 days, distributes + mints cancelProject(projectId) // cancel before completion claimRefund(projectId) // refund if cancelled or goal not met claimFunds() // claim all pending withdrawals for msg.sender revenue sharing distributeRevenue(projectId) payable // team sends revenue back, mints REVENUE_SHARER badge claimRevenue(projectId) // funders claim proportional share auto-invite on project completion // 5 bonus invites per participant lifecycle PROPOSED -> FUNDED -> CONFIRMED -> COMPLETING -> COMPLETED // dispute during COMPLETING: >50% -> CANCELLED -> refunds autoComplete autoComplete: true // instant purchase without confirmation/dispute // funds distribute immediately on purchase -- no dispute window confirmation modes proposer-only // only proposer confirms completion majority // proposer + majority of collaborators all // every collaborator must confirm dispute window configurable: 0-30 days per project // set by proposer at creation // 0 days = no dispute period (trust-based projects) token types (ERC-6909) 1 = TICKET // transferable 2 = PRODUCER // soulbound 3 = CONTRIBUTOR // soulbound 4 = REVENUE_SHARER // soulbound, minted on distributeRevenue() token id packing [type 8] [projectId 64] [tierId 32] [serial 152] events ProjectProposed(uint projectId, address proposer, string title) TierFunded(uint projectId, uint tierId, address funder, uint qty) FundingWithdrawn(uint projectId, address funder, uint amount) ProjectConfirmed(uint projectId, address confirmer) ProjectCompleted(uint projectId) ProjectFinalized(uint projectId) ProjectCancelled(uint projectId) Disputed(uint projectId, address disputer, uint amount) FundsClaimed(uint projectId, address claimant, uint amount) RefundClaimed(uint projectId, address claimant, uint amount) RevenueDistributed(uint projectId, address sender, uint amount) RevenueClaimed(uint projectId, address claimant, uint amount)
// invite-only registration + referral tracking 0xbC74c3D815beC49507826A6b9e07E7f086FB744D public functions createInvite(codeHash) // registered artist creates invite createInvites(codeHash[]) // batch create useInvite(code, expiry, nonce, orchSig) // EIP-712 sig binds (codeHash, msg.sender, expiry, nonce) claimInitialInvites() // any registered artist self-claims 10 once grantInvites(artist, count) // deployer bootstraps view functions invitesRemaining(addr) -> uint // how many invites left (mapping getter) invitedBy(addr) -> address // who invited this artist (mapping getter) events InviteCreated(address creator, bytes32 codeHash) InviteUsed(address artist, address inviter, bytes32 codeHash) InvitesGranted(address artist, uint count)
// on-chain blog posts with comments + references 0xf93de3d5f025915C5e28c40382DE4946c7b18De9 public functions post(title, content) // standalone blog post postWithRef(title, content, refType, refId) // linked post // refType: 0=none, 1=project, 2=portfolio, 3=reply view functions postCount() -> uint // total post count (posts stored as events) events Posted(uint postId, address author, string title, string content, uint timestamp, uint8 refType, uint refId)
// soulbound media tokens with revenue splits 0xaF995dB3955419E9E2086FD02891580F8a025481 public functions list(title, ipfsCid, metadataCid, price, maxSupply) list(title, ipfsCid, metadataCid, price, maxSupply, collaborators[], splits[]) // with revenue splits purchase(mediaId) payable // mint soulbound token to buyer setPrice(mediaId, newPrice) // artist updates price setMaxSupply(mediaId, newMax) // artist updates supply cap withdraw() // pull accumulated revenue events Listed(uint mediaId, address artist, string title, uint price) Purchased(uint mediaId, address buyer, uint price) PriceChanged(uint mediaId, uint newPrice) SupplyChanged(uint mediaId, uint newMax) Withdrawn(address artist, uint amount)
// secondary market for transferable TICKET tokens 0x0ea62A91acE3D77Bc96d77f1B05Ff3C1C60aF74c public functions list(tokenId, price) // list ticket for resale (requires operator approval) purchase(tokenId) payable // buy listed ticket, ETH credited to seller cancel(tokenId) // cancel listing updatePrice(tokenId, newPrice) // update listing price withdraw() // seller pulls payment constraints only TICKET type (type=1) can be listed // PRODUCER and CONTRIBUTOR are soulbound events TicketListed(uint tokenId, address seller, uint price) TicketPurchased(uint tokenId, address buyer, uint price) TicketCancelled(uint tokenId, address seller) TicketPriceChanged(uint tokenId, uint newPrice) Withdrawn(address seller, uint amount)
// shared knowledge library (IPFS-backed) 0x5CdDD64f20C69fC2007868476788BC3766C28A0A public functions addItem(title, author, ipfsCid, url, tags) // add PDF/article/essay (tags: comma-separated string) tagItem(itemId, tags) // contributor-only tag updates (comma-separated string) view functions itemCount() -> uint // total item count (items stored as events) events ItemAdded(uint itemId, address contributor, string title, string author, string ipfsCid, string url, string tags, uint timestamp) TagsAdded(uint itemId, address tagger, string tags, uint timestamp)
// trustless artist-to-artist sponsored registration // any registered artist can sponsor new artists by depositing ETH into escrow 0x15F5f22F130ecEF5eee15d9BA90bB73B287a4F6A flow sponsor deposits 0.0052 ETH per invite // 0.005 deploy fee + 0.0002 gas allowance (gas buffer is dynamic on the orchestrator side) domain cost additional (paid on-chain) // forwarded to treasury, no markup new artist redeems invite // contract sends funds directly to new artist sponsor can revoke unredeemed invites // reclaims full deposit trust model funds held by smart contract // not the orchestrator, not the sponsor fully trustless -- no admin key // sponsor deposits, contract releases no intermediary touches the ETH // on-chain escrow only front-running protection (v2) EIP-712 orchestrator signature // binds (codeHash, msg.sender, expiry, nonce) recipient locked in the signature // mempool watcher cannot replace msg.sender per-signature nonce + 1h max validity // replay-proof, time-bounded emergency setPaused() if orch key leaked // blocks new redemptions, deposits/refunds keep working public functions deposit(count) payable // deposit ETH for N sponsored slots sponsorInvite(codeHash) // link a code hash to a deposited slot redeem(code, expiry, nonce, orchSig) // EIP-712-protected, binds caller as recipient revokeInvite(codeHash) // sponsor reclaims unredeemed deposit refundSlots(count) // refund unassigned slots view functions availableSlots(addr) -> uint // unassigned sponsored slots sponsorOf(codeHash) -> address // who sponsored this invite activeSponsor(codeHash) -> address // sponsor if unredeemed, zero if redeemed events Deposited(address artist, uint count, uint totalValue) SponsoredInviteCreated(address artist, bytes32 codeHash) SponsorshipRedeemed(bytes32 codeHash, address sponsor, address recipient, uint amount) InviteRevoked(address artist, bytes32 codeHash)
// self-sustaining treasury: auto-converts deploy fees to infrastructure payments 0x5CF9E88417A7cE08028D32C44F9b63bc3d960b21 auto-payment loop artist registers (0.005 ETH) // ArtistRegistry forwards to PraxisTreasury orchestrator calls swapAndForward(ethAmount, minUsdcOut) // triggered after each registration PraxisTreasury swaps ETH -> USDC // via Velodrome on Optimism USDC forwarded to EtherFi Cash // account 0x306d... EtherFi Cash card auto-pays bills // NameSilo domains + Hetzner servers // zero human intervention -- fully automated infrastructure funding public functions swapAndForward(ethAmount, minUsdcOut) // swap ETH to USDC, forward to EtherFi Cash swapETHToUSDC(ethAmount, minUsdcOut) // swap ETH to USDC (kept in treasury) forwardToCash(amount) // forward USDC to EtherFi Cash account withdrawETH(to, amount) // owner emergency ETH withdrawal sweepToken(token, to, amount) // owner sweep any ERC-20 token immutables etherFiCashAccount // set at deploy, cannot be changed syncSwapRouter // Velodrome Router on Optimism owner // deployer address access control owner-gated functions // only owner can swap, forward, withdraw reentrancy guard // nonReentrant on all state-changing functions pausable // owner can pause swaps in emergency 7 rounds of security audit // hardened against exploits
PROPOSED // artist proposes project with team + splits + tiers + location // tiers: tickets (transferable) + backer credits (soulbound) // optional: revenue sharing commitment to funders | v fundTier(projectId, tierId, qty) withdrawFunding(projectId) // liquid pre-threshold FUNDED // goal met. team confirms. | v confirmProject(projectId) // proposer + majority of collaborators CONFIRMED | v completeProject(projectId) COMPLETING // 3-day dispute window // funders can call dispute() -- if >50% of funded amount objects: // -> CANCELLED -> everyone refunds | v finalizeProject(projectId) // after 3 days, no majority dispute COMPLETED // funds distribute per splits (pull pattern) // soulbound CONTRIBUTOR tokens mint to team // soulbound PRODUCER tokens already minted to funders // 5 bonus invites granted per participant (auto-invite) REVENUE SHARING (optional) // if enabled, team sends revenue back to the contract: distributeRevenue(projectId) // funders claim proportional share: claimRevenue(projectId)
// ERC-6909 multi-token (cheaper than ERC-1155, no callbacks) // same standard as Uniswap v4 token id (256-bit packed) [type 8] [projectId 64] [tierId 32] [serial 152] types (Praxis contract) 1 = TICKET // transferable -- "i'm going to this show" 2 = PRODUCER // soulbound -- "i funded this project" 3 = CONTRIBUTOR // soulbound -- "i worked on this project" 4 = REVENUE_SHARER // soulbound -- "i shared revenue with funders" types (PraxisMedia contract) MEDIA = soulbound // "i own this media" -- no transfers, no secondary market soulbound enforcement transfer() checks tokenId >> 248 type > 1 -> revert // single bit-shift, no storage lookup
// multi-tenant by default -- one process serves all artists // high-traffic artists auto-promote to dedicated containers multi-tenant (default) one Node.js process, all artists // Host header routing, ~$4/mo for 10K artists per-artist data in /data/artists/{handle}/ // site.json, journal/, content/ shared JS/CSS/modules (read-only) // build.js, server.js, modules/, public/ auto-promotion >200 connections on one artist // triggers dedicated Docker container resource limits: 512MB RAM, 1.0 CPU // hard caps per container up to 5 replicas behind Traefik LB // for sustained high traffic auto-demote when traffic drops // <20 connections for 5min -> back to multi-tenant routing Traefik reverse proxy // auto TLS via Let's Encrypt one route per domain // Host(`yourdomain.com`) -> multi-tenant or container dynamic config reload // new routes added without restart auto-scaling monitor checks every 5 minutes // memory usage + container count per server threshold: 80% memory or 50 containers // triggers new VPS provisioning Hetzner Cloud API (primary) // CAX11 ARM servers, ~$4/month BitLaunch (fallback) // crypto-payable VPS new node added to pool automatically // DNS + Traefik configured on provision idle servers reaped after 30 minutes // Hetzner server destroyed, zero waste portability runs anywhere Node.js or Docker runs // no proprietary runtime, no cloud lock-in export data anytime // /data/artists/{handle}/ is yours self-host with zero dependencies on us // fork, build, run
// what happens when a new artist joins 1. register enter invite code + handle // on-chain: useInvite(code,expiry,nonce,orchSig) + register() embedded wallet created automatically // no MetaMask required search domains (NameSilo API) // or bring your own domain 2. pay 0.005 ETH deploy fee // on-chain fee, paid in ETH on Optimism domain cost pass-through // no markup, actual NameSilo price Venmo/PayPal onramp via Peer.xyz // if you don't have ETH 3. verify orchestrator checks on-chain registration // wallet registered in ArtistRegistry orchestrator verifies payment to treasury // ETH received at treasury address DNS configured automatically // NameSilo API sets A record BYO domain: point A record manually // orchestrator verifies before deploy 4. deploy per-artist site.json generated // handle, domain, wallet, contract addresses data dir created: /data/artists/{handle}/ Docker container spun up // bind-mounts shared code, exposes unique port Traefik route added // domain -> container, TLS auto-provisioned site live in seconds // no human intervention required 5. domain lifecycle first 2 years included with deploy fee renewal: annual, at actual domain cost // paid in ETH, no markup 60 days before expiry // warning banner on site 30 days before expiry // second warning 7 days before expiry // final warning expired // site stays up during 30-day grace 30 days past expiry // container suspended (docker stop) 90 days past expiry // data archived, container removed // you can export your data or self-host at any point
// the free path -- no dependency on Praxis infrastructure setup git clone https://github.com/taayyohh/praxis cd praxis npm install // edit site.json with your info node build.js && PORT=3000 node server.js dns point your domain's A record to your server add TLS yourself (certbot, caddy, etc) // or run behind Traefik what works all 9 modules // music, credits, gallery, writing, video, audio, etc all 6 templates // default, musician, visual, writer, performer, filmmaker on-chain features // projects, feed, media, library embedded wallet // works without MetaMask XMTP messaging // peer-to-peer, no server needed IPFS uploads (run your own Kubo node or use any pinning service) persistent audio player // soft-SPA navigation i18n (18 languages) // auto-detect + manual switcher + RTL what you control your data // site.json, journal, content -- all local files your keys // wallet is your identity, not our auth server your uptime // no dependency on ourpraxis.network your code // fork and modify anything
// transparent pricing -- no hidden fees, no platform tax deploy fee 0.005 ETH one-time // on-chain fee, paid in ETH on Optimism covers: container setup, Traefik route, initial config domain cost pass-through from NameSilo // no markup -- you pay what the registrar charges; free WHOIS privacy first 2 years included // bundled with deploy fee annual renewal at actual domain cost // paid in ETH infrastructure cost Hetzner CAX11 ARM: ~$4/month // 2 vCPU, 4GB RAM 50 artists per server // 512MB + 0.5 CPU per container = $0.08/artist/month at capacity // actual hosting cost platform fees media sales: 0% // 100% to artist (+ collaborator splits) ticket resales: 0% // 100% to seller project funding: 0% // 100% distributes per team splits // no platform fee on any transaction -- ETH goes directly between wallets self-host $0 // free forever, fork the repo
proxy Traefik // TLS termination, per-domain routing, Let's Encrypt isolation Docker // one container per artist, resource limits process PM2 (praxis) // landing page, orchestrator, Ponder process management scaling Hetzner Cloud API // auto-provision ARM VPS when servers hit capacity fallback BitLaunch // crypto-payable VPS, secondary provider domains NameSilo API // search, purchase, DNS configuration, renewal; free WHOIS privacy, JSON API storage Kubo (self-hosted IPFS) // images, audio, PDFs -- content-addressed, permanent indexer Ponder v0.7 // GraphQL API over all on-chain data (15 tables) chain Optimism (10) // 9 smart contracts, ERC-6909 tokens messaging XMTP v7 // peer-to-peer encrypted DMs + group chats payments Peer.xyz // Venmo/PayPal -> ETH onramp wallet embedded (BIP-39) // no extension required, AES-256-GCM encrypted bridge Relay Protocol // cross-chain ETH from 6 chains (Ethereum, Optimism, Arbitrum, Base, Polygon, zkSync) biometric WebAuthn // fingerprint / Face ID for transaction confirmation monitor Hetzner Helsinki // external uptime checks every 30s, 24h rolling history, /status page compress brotli + gzip // text asset compression, ETag caching video ffmpeg // thumbnail generation, Range request streaming a11y contrast.js // WCAG auto-correction, accessible palette derivation frontend vanilla JS, ES modules // no React, no framework, esbuild vendor bundles
external monitor independent server (Hetzner Helsinki) // separate from production -- catches real outages checks 4 services every 30 seconds // server, orchestrator, Ponder indexer, Optimism RPC 24-hour rolling history (2880 checks) // persisted to disk, survives restarts status page /status // public system status dashboard per-service health dots // green/orange/red with uptime percentage response latency tracking // milliseconds per service per check uptime bar visualization // last 30 minutes of checks header indicator "live" dot in top bar // green = all systems operational orange = 1 service degraded // red = 2+ services down
deployer-sponsored invites deployer creates invite codes // POST /orchestrator/sponsored-invites (deployer-only) sponsored code covers 0.005 ETH fee // orchestrator sends ETH to new artist's wallet invite link: ?invite=CODE&from=HANDLE // OG tags show "{handle} invited you to praxis" artist-sponsored invites (ArtistSponsoredInvites) any registered artist can sponsor // no deployer permission needed deposit 0.0052 ETH per invite // 0.005 deploy fee + 0.0002 gas allowance domain cost additional (paid on-chain) // forwarded to treasury funds held by smart contract // trustless escrow, not orchestrator new artist redeems -> receives deposit // contract sends ETH directly sponsor can revoke unredeemed invites // full deposit returned audience gas sponsorship supporters register for free // registerSupporter() -- no msg.value required gas covered by orchestrator // POST /orchestrator/sponsor-gas sends 0.0001 ETH rate limited + anti-abuse // daily cap, per-IP cap, nonce check, balance check
1. identity register on-chain, embedded wallet or browser wallet, permanent + portable 2. social follow graph, mutual follows unlock messaging 3. messaging XMTP encrypted DMs + group chats, send ETH in-chat 4. projects propose + fund with tiers, revenue sharing, dispute window 5. library shared knowledge base, IPFS-backed PDFs/articles, tagging 6. journal encrypted private writing, AES-256-GCM, wallet-derived key 7. feed on-chain blog posts, comments, activity timeline, infinite scroll 8. media list for sale, soulbound tokens, collaborator revenue splits 9. tickets secondary market for transferable project tickets (resale, pricing) 10. earnings /earnings -- earned, contributed, unclaimed funds overview
// PraxisMedia: soulbound media tokens with revenue splits // artists list media for sale, collectors purchase, no resale listing list(title, ipfsCid, metadataCid, price, maxSupply) list(..., collaborators[], splits[]) // with revenue splits // set price, max supply, optional collaborator splits purchasing purchase(mediaId) payable // mints soulbound token to buyer // distributes ETH per split percentages // no transfers -- your collection is yours alone management setPrice(mediaId, newPrice) setMaxSupply(mediaId, newMax) withdraw() // pull accumulated revenue personal collection /collection page shows all owned media + credentials // soulbound tokens prove ownership without speculation // support artists directly, no middlemen, no secondary market
default minimal text, clean CV layout musician larger album art, prominent name, inline player visual image grid, wide canvas, gallery-first writer generous typography, reading-optimized, narrow measure performer stage/event-oriented, credits-first, resume layout filmmaker video-forward, project stills, cinematic // 6 templates -- onboarding auto-selects based on artist type // changeable anytime via settings panel
// 10 content modules, each with CV + highlights rendering // artists enable/disable/reorder via settings panel music aliases, albums, tracks (IPFS), streaming links credits theater, film, tv, dance, comedy, opera, music technology engineering projects, roles, descriptions gallery images (IPFS), exhibitions, series writing publications, reading list, PDF upload + card layout film works, festivals, video audio episodes, tracks (IPFS) education events, workshops, residencies video generic video module, any artist type (lazy load, ffmpeg thumbnails) demos demo reels, showreels, audition tapes (video + metadata) // 10 content modules + 37+ JS modules (wallet, network, projects, etc) // settings panel: identity, modules, highlights, theme, AI editor // all edits go through PUT /api/site -> rebuild
languages EN ES FR PT DE AR ZH JA KO HI SW RU BN FA ID TR UR VI detection localStorage('praxis-lang') > navigator.language > 'en' switcher dropdown in top bar (landing + all artist sites) RTL full layout flip for Arabic, Farsi, Urdu architecture landing: landing/i18n/*.json (173 keys) sites: public/i18n/*.json (512 keys) total: 685+ translation keys // across landing + artist sites module: public/js/i18n.js t(key, vars), data-i18n attrs // UI chrome translates, user content does not // artists write in whatever language they want
frontend vanilla JS, ES modules, no React, esbuild vendor bundles blockchain viem (local bundle, 283KB), Optimism (10) wallet embedded BIP-39 + MetaMask compatible messaging XMTP v7 (local bundle, 165KB), DMs + group chats indexer Ponder v0.7 (GraphQL, 15 tables) payments Peer.xyz (Venmo/PayPal onramp, sell via extension sidebar) server Node.js containers Docker (per-artist, bind-mounted code) proxy Traefik (auto TLS, Let's Encrypt) testing Foundry + vitest (jsdom) -- 3000+ tests domains NameSilo API (search + purchase + DNS) storage IPFS via Kubo (self-hosted) (images, audio, PDFs) i18n 18 languages, auto-detect, RTL (Arabic, Farsi, Urdu) player persistent audio player, bottom bar, survives navigation navigation soft-SPA (fetch+replace main content, preserves player) images dynamic resizer (sharp), thumbnails for lists/grids cache client-side sessionStorage, TTL tiers, immutable IPFS cache bridge Relay Protocol (cross-chain ETH to Optimism) compress brotli + gzip (text assets), ETag caching, Range requests build asset fingerprinting (content-hash filenames, immutable cache) a11y ADA contrast system (WCAG auto-correction, deriveFullPalette) video ffmpeg thumbnails, lazy loading, Range request streaming
// esbuild bundles external deps into local vendor files // zero esm.sh imports at runtime vendor.js 283KB viem (wallet, contracts, ABI) // every page vendor-xmtp.js 165KB XMTP browser SDK // lazy (messages only) vendor-zkp2p.js 930KB ZKP2P SDK (Venmo onramp) // lazy (pay only) vendor-xmtp-reaction.js 1.6KB XMTP reactions codec // lazy (messages only) vendor-relay.js Relay SDK (cross-chain bridging) // lazy (bridge only) vendor-wallet.js @scure/bip39 + @scure/bip32 // lazy (embedded wallet)
// force-directed network graph rendered on canvas data nodes = registered artists // from ArtistRegistry edges = follows + collaborations + funding // from follow/project/funding events rendering HTML5 Canvas // no SVG, no D3 dependency force-directed layout // ego-graph centered on current artist interactive: pan, zoom, click // click a node to visit that artist's site access /network page (list|graph toggle) // switch between directory list and graph view landing page live graph // real-time visualization from on-chain data
// per-recipient notifications indexed by Ponder triggers new follower // someone followed you project funded // your project received funding project status change // confirmed, completing, completed, cancelled blog reply // someone replied to your post media purchased // someone bought your media architecture Ponder notification table // per-recipient, indexed by (recipient, timestamp) unread badge on dock // visual indicator for new notifications
// all ETH amounts can be displayed in fiat currencies ETH (default), USD, EUR, GBP, JPY, CNY, BRL, NGN, KES, INR, KRW // switcher in top bar architecture /api/eth-price endpoint // server-side price fetch, cached fiat.js module // converts all ETH amounts on page real-time updates // prices refresh on navigation // ETH is always the underlying currency -- fiat is display only
// cross-chain ETH bridging via Relay Protocol // move ETH from any chain to Optimism without leaving praxis how it works user has ETH on Ethereum mainnet // or Optimism, Arbitrum, Base, etc click "bridge to Optimism" in wallet // auto-detects source chain Relay SDK fetches quote // best route, estimated time one-click bridge execution // funds arrive on Optimism in ~30 seconds architecture relay-bridge.js // lazy-loaded only when bridge is needed vendor-relay.js // esbuild bundle (@reservoir0x/relay-sdk) ensureFundsForPurchase() // checks Optimism balance, prompts bridge if insufficient flow check balance on Optimism // enough ETH? proceed normally insufficient? detect other chains // check mainnet, Optimism, Arbitrum balances prompt bridge // show amount needed, estimated time bridge + retry original action // seamless -- user doesn't leave the page
// production optimizations built into the node.js server compression brotli (preferred) // best compression ratio for text assets gzip (fallback) // universal browser support applied to: HTML, CSS, JS, JSON, SVG // all text-based content types caching ETag headers // content-based cache validation Cache-Control // immutable for fingerprinted assets IPFS response cache // in-memory cache for proxied IPFS content image cache // resized images cached on disk range requests HTTP 206 Partial Content // seek within audio/video without downloading entire file Content-Range headers // standard byte-range serving Accept-Ranges: bytes // advertised on all static files resilience connection semaphore // limits concurrent IPFS proxy connections circuit breaker pattern // stops cascading failures to upstream services
// content-hash filenames for cache-busting build step build.js runs fingerprintAssets() // hash all JS/CSS referenced in HTML style.css -> style.a3f2c1.css // content hash appended to filename HTML references updated automatically // src/href paths rewritten to hashed versions cache benefit Cache-Control: immutable // browsers cache forever (hash changes on update) no cache-busting query strings // clean URLs, CDN-friendly deploy = instant cache invalidation // new hash = new file = fresh download
// WCAG-compliant color accessibility // automatic contrast correction for all theme colors how it works artist sets theme colors in settings // foreground, background, accent, muted contrast.js evaluates WCAG contrast ratio // minimum 4.5:1 for normal text (AA) auto-corrects insufficient contrast // adjusts lightness while preserving hue deriveFullPalette() // generates accessible variant for every CSS variable coverage all text on all templates // headings, body, links, muted text all interactive elements // buttons, inputs, badges dark + light themes // correction works in both directions testing 19 vitest tests // contrast ratio, ensureContrast, deriveFullPalette
// /earnings -- unified financial overview for artists earned media sales // revenue from PraxisMedia purchases project funding // claimed funds from completed projects ticket resales // revenue from PraxisTicketMarket sales contributed project funding // ETH sent to fund project tiers media purchases // ETH spent buying media tokens unclaimed pending funds with claim buttons // one-click claim for each source history paginated transaction list // all financial activity, timestamped fiat display // amounts shown in selected currency (ETH/USD/EUR/GBP)
// PraxisTicketMarket: secondary market for transferable TICKET tokens // only type=1 (TICKET) tokens can be resold -- PRODUCER and CONTRIBUTOR are soulbound seller flow approve PraxisTicketMarket as operator // ERC-6909 setOperator() list(tokenId, price) // set resale price in ETH updatePrice(tokenId, newPrice) // change price while listed cancel(tokenId) // remove from market withdraw() // pull accumulated sale revenue buyer flow browse listed tickets // filterable by project, price purchase(tokenId) payable // ETH credited to seller, ticket transferred fiat price display // see price in USD/EUR/GBP alongside ETH platform fee 0% // 100% of sale goes to seller
// /collection -- personal collection of owned tokens contents purchased media (soulbound) // from PraxisMedia -- art, music, writing you own TICKET tokens (transferable) // from Praxis project tiers -- can be resold PRODUCER credentials (soulbound) // proof you funded a project CONTRIBUTOR credentials (soulbound) // proof you worked on a project display lazy-loaded grid // IntersectionObserver for performance owned state badges // visual indicator on items you own link to media/project detail // click through to full context
// non-artist audience members can join the network supporters can register with wallet // no invite code required for supporters follow artists // build a feed of artists they care about fund projects // back project tiers, earn TICKET/PRODUCER tokens purchase media // buy soulbound media tokens from artists buy/sell tickets // participate in secondary ticket market view collection // see all owned tokens in one place supporters cannot create projects // only registered artists propose projects list media for sale // only registered artists list media get a hosted site // supporters have a profile page, not a full site profile /supporter?addr=0x... // public supporter profile on ourpraxis.network credentials + funded projects // shows their participation in the network
// generic video module for any artist type // works across all 6 templates features lazy loading // video only loads when scrolled into view auto-thumbnails // ffmpeg generates poster frames on upload aspect ratio preservation // responsive container, no layout shift Range request streaming // seek without downloading entire file (HTTP 206) architecture modules/video/ // self-contained module directory IPFS-backed storage // content-addressed, permanent hosting ffmpeg integration // server-side thumbnail generation
// automatic social sharing previews generation dynamic OG images per artist // artist name, domain, template-styled project OG images // title, status, funding progress landing page OG image // praxis branding meta tags og:image, twitter:image // rich previews on all platforms og:title, og:description // automatic from page content twitter:card = summary_large_image // large image preview on Twitter/X
embed on any site to show it's part of the network:
// html <a href="https://ourpraxis.network" style="display:inline-block; padding:0.2em 0.8em;border:1px solid #1a1a1a;font-family:monospace; font-size:0.75em;color:#666;text-decoration:none; letter-spacing:0.05em">praxis</a>
// Fountain-based screenplay editor built into journal // write screenplays, teleplays, and stage plays with industry-standard formatting format modes screenplay // film -- INT./EXT. scene headings, standard elements teleplay // television -- same elements as screenplay stage play // theater -- adds act/scene labels, stage directions Fountain spec scene headings: INT. / EXT. / I/E // auto-detected by prefix forced markers: . (scene) @ (character) > (transition) // full Fountain markup compliance for interop with other tools element types scene heading // INT. COFFEE SHOP - DAY action // narrative description (default element) character // ALL CAPS name above dialogue dialogue // spoken lines under character name parenthetical // (beat), (to herself) -- actor direction transition // CUT TO:, FADE OUT., right-aligned stage play extras act / scene labels // ACT ONE, SCENE 3 -- structural markers stage directions // italicized, distinct from action blocks smart editing Tab cycles element types // Tab on empty line rotates: action -> scene -> character Enter infers next element // after character -> dialogue, after dialogue -> character auto-capitalization // scene headings + character names auto-uppercase export publish to blog (on-chain) // formatted screenplay stored in BlogRegistry PDF export via browser print // Cmd+P with industry-standard page layout storage AES-256-GCM encrypted // same as prose journal entries Fountain marker prefix // <!-- fountain:screenplay --> for mode detection wallet-derived encryption key // only you can read your scripts testing 167 vitest tests (fountain.test.js) // parser, element detection, dual dialogue, notes
// publish journal entries as on-chain blog posts // prose and scripts export differently prose entries publish as markdown blog posts // standard BlogRegistry.post(title, content) rendered with markdown parser // headings, lists, links, images script entries publish as formatted screenplays // HTML with fountain-* CSS classes <!-- screenplay --> marker in content // signals post.js to use screenplay renderer Courier font + industry-standard margins // correct formatting on the blog page detection post.js checks for screenplay marker // <!-- screenplay --> at start of content marker present: render with fountain-* CSS // scene headings, dialogue, transitions styled marker absent: render as markdown // standard blog post rendering