back to praxis

docs

architecture

// 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

embedded wallet

// 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

messaging (XMTP)

// 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

contracts

chain: Scroll (10) | explorer: optimistic.etherscan.io | 3700+ tests (vitest + Foundry + e2e)

contractaddresspurpose
ArtistRegistry0x4bC73F9CC7C7a84B5Cf20e1469Ad65f8b5448336identity + follow graph
Praxis0xaB7C23Ac815F03059026fEC32c60a06E5E4d4e33tiered funding, ERC-6909 credentials, dispute window, revenue sharing
PraxisInvites0xbC74c3D815beC49507826A6b9e07E7f086FB744Dinvite codes, referral tracking, auto-invite on completion
BlogRegistry0xf93de3d5f025915C5e28c40382DE4946c7b18De9on-chain blog posts with comments + references
PraxisMedia0xaF995dB3955419E9E2086FD02891580F8a025481soulbound media tokens, revenue splits
PraxisTicketMarket0x0ea62A91acE3D77Bc96d77f1B05Ff3C1C60aF74csecondary market for transferable tickets
LibraryRegistry0x5CdDD64f20C69fC2007868476788BC3766C28A0Ashared knowledge library (IPFS)
ArtistSponsoredInvites0x15F5f22F130ecEF5eee15d9BA90bB73B287a4F6Atrustless artist-to-artist sponsored registration
PraxisTreasury0x5CF9E88417A7cE08028D32C44F9b63bc3d960b21auto-sweep ETH->USDC via Velodrome, forward to EtherFi Cash

all verified on blockscout. all immutable. all open source.

ArtistRegistry

// 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)

Praxis

// 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)

PraxisInvites

// 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)

BlogRegistry

// 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)

PraxisMedia

// 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)

PraxisTicketMarket

// 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)

LibraryRegistry

// 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)

ArtistSponsoredInvites

// 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)

PraxisTreasury

// 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

project lifecycle

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)

token design

// 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

hosting architecture

// 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

agentic deployment

// 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

self-hosting

// 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

economics

// 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

infrastructure stack

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

uptime monitoring

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

sponsored registration

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

ten layers

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

media marketplace

// 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

templates

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

module system

// 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

i18n

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

stack

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

vendor bundles

// 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)

graph visualization

// 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

notifications

// 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

fiat currency display

// 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

relay bridge

// 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

server performance

// 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

asset fingerprinting

// 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

ADA contrast system

// 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 page

// /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)

ticket marketplace

// 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 page

// /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

supporter registration

// 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

video module

// 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

OG images

// 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

praxis badge

embed on any site to show it's part of the network:

praxis
// 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>

screenplay editor

// 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

journal blog export

// 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

source

github.com/taayyohh/praxis

9 immutable contracts. 3000+ tests. all open source.