SPECTRA Master Contract Integration Guide
This is the master document for the current SPECTRA contract stack and its app integration.
It explains:
- what each contract does
- how contracts connect to the Next.js app
- how
content/episodes.jsonbecomes metadata and onchain state - how owner/admin/ai-agent access is enforced
- the full operating flow from submission to founder mint to event sync
- the recommended deployment and ops model
- PlantUML diagrams you can render into architecture graphs
System at a glance
The repo currently has four main onchain modules:
SpectraSubmissionRegistrySpectraFounderSeason1_1155SpectraEventAccessRegistrySpectraOwnerAccess1155
And four main offchain integration layers:
content/episodes.json- episode metadata builders in
src/lib/episodes.ts - owner/admin routes and dashboard in
app/api/admin/*andapp/owner/episodes - deployment and sync scripts in
scripts/*
Contract inventory
1. SpectraSubmissionRegistry
Purpose:
- records founder applications
- stores applicant wallet, submission metadata URI, approval status, and mint linkage
- acts as the gatekeeper for founder mint eligibility
Core responsibilities:
- create a submission
- let the original applicant update metadata before mint
- let an approver mark a submission approved
- let the founder ERC-1155 contract mark a submission as minted
Important state:
nextSubmissionIdfounderMembershipContractsubmissions[submissionId]submissionsByApplicant[address]
Important role model:
DEFAULT_ADMIN_ROLEAPPROVER_ROLEPAUSER_ROLE
Important invariants:
- only the applicant can edit their own metadata
- once a submission is minted, it cannot be edited again
- only the configured founder contract can call
markMinted
2. SpectraFounderSeason1_1155
Purpose:
- mints Season 1 founder membership ERC-1155 tokens
- ties each minted token to a specific approved submission
- supports airdrops and season closure
Core responsibilities:
- mint one founder token per approved submission
- map
submissionId <-> tokenId - expose wallet mint history
- support admin airdrops
- let the season be closed and supply capped
Important state:
seasonId = 1maxSupplyseasonClosedsubmissionRegistrytokenIdToSubmissionIdsubmissionIdToTokenId
Important roles:
DEFAULT_ADMIN_ROLEPAUSER_ROLEURI_MANAGER_ROLEAIRDROP_ROLECLOSER_ROLE
Important invariants:
- only approved submissions can mint
- each submission mints only once
- the applicant must be the caller for self-mint flow
- minting stops if the season is closed or supply limit is reached
3. SpectraEventAccessRegistry
Purpose:
- stores event definitions onchain
- stores wallet-level access for each event
- acts as the event/access authority for future event-gated experiences
Core responsibilities:
- create and update events
- toggle event active state
- assign attendee or artist access per wallet
- clear access
Important state:
nextEventIdeventsById[eventId]accessByEventAndWallet[eventId][wallet]
Important roles:
DEFAULT_ADMIN_ROLEEVENT_MANAGER_ROLEACCESS_MANAGER_ROLEPAUSER_ROLE
Important invariants:
- access can only be assigned to an existing active event
AccessRole.Noneis not assignable throughsetAccess- writes are role-gated and pausable
4. SpectraOwnerAccess1155
Purpose:
- provides soulbound operational access keys for humans and agents
- gates the
/owner/episodesadmin experience - separates identity classes across token IDs
Default role token IDs:
1 = owner2 = admin3 = aiagent
Core responsibilities:
- mint owner/admin/ai-agent access keys
- revoke compromised keys by burning them
- enforce non-transferability
- support pause and metadata updates
Important roles:
DEFAULT_ADMIN_ROLEPAUSER_ROLEMINTER_ROLEURI_MANAGER_ROLE
Important invariants:
- tokens are non-transferable
- tokens can only move through mint or burn
- admin can burn compromised tokens
- initial minter can be separated from root admin
App integration map
Episode catalog as source of truth
content/episodes.json is the canonical source for episode UI and event metadata.
That file feeds:
- frontend episode cards
- token-style metadata JSON
- event registry payloads
- Luma-linked event references
- owner dashboard editing
Main helper layer:
src/lib/episodes.ts
Key outputs from that helper:
- episode cards for UI
- metadata payloads for
/api/episodes/[slug]/metadata - event registry payloads for
SpectraEventAccessRegistry
Owner/admin control room
The dashboard at /owner/episodes is wallet-gated.
The gate is:
- wallet connects
- wallet signs a challenge
- server verifies signature
- server checks ERC-1155 ownership in
SpectraOwnerAccess1155 - session cookie is issued
Main files:
src/lib/owner-session.tsapp/api/admin/session/route.tscomponents/admin/EpisodesAdminPanel.tsx
Onchain event sync
The owner dashboard and scripts can push episode data into SpectraEventAccessRegistry.
Main files:
src/lib/episode-sync.tsscripts/syncEpisodeEvent.ts
Current mapping from episode catalog to registry payload:
name = episode.titlemetadataURI = /api/episodes/[slug]/metadataactive = episode.status === "open"
End-to-end flows
Flow A: founder application to founder token
- applicant submits founder metadata
- app writes into
SpectraSubmissionRegistry - admin reviews and approves submission
- applicant calls
mintFounder(submissionId) SpectraFounderSeason1_1155checks approval and ownership- founder token mints
- founder contract calls
submissionRegistry.markMinted(...) - submission becomes permanently linked to the minted token
Flow B: episode editing to onchain event update
- owner/admin wallet unlocks
/owner/episodes - episode row is edited in the admin panel
content/episodes.jsonis updated- the same episode record drives UI and metadata output
- sync action sends catalog-derived payload into
SpectraEventAccessRegistry - returned
registryEventIdis stored back into the episode catalog
Flow C: owner key operations
- deploy
SpectraOwnerAccess1155 - mint
owner,admin, and optionallyaiagenttokens - configure
EPISODES_OWNER_ERC1155_ADDRESS - configure
EPISODES_OWNER_ERC1155_TOKEN_ID - wallet holders can sign in to the admin dashboard if their token IDs are allowed
Recommended operating model
Human and machine separation
Recommended default:
- owner token holders = strategic/root operators
- admin token holders = human day-to-day operators
- aiagent token holders = automation wallets only
Recommended dashboard config:
EPISODES_OWNER_ERC1155_TOKEN_ID=1,2
Why:
- owners can always enter
- human admins can operate the dashboard
- ai-agent keys stay separated unless they truly need dashboard access
Root admin vs minter separation
Recommended pattern:
OWNER_ACCESS_ADMIN_ADDRESS= hardware wallet or SafeOWNER_ACCESS_INITIAL_MINTER_ADDRESS= temporary deployer or ops wallet
Why:
- root admin can revoke compromised minters
- one-command bootstrap still works
- day-to-day minting risk is reduced
Failure and recovery model
If an admin token wallet is compromised
- burn the token with
contracts:revoke:owner-access - mint a replacement to a fresh wallet
- remove any temporary allowlist entries if used
If a minter wallet is compromised
- revoke
MINTER_ROLE - burn any unauthorized keys it minted
- rotate to a fresh minter wallet
If root admin is compromised
- consider the owner-access contract compromised
- deploy a new
SpectraOwnerAccess1155 - mint fresh keys
- update app env to the new contract address
If an event registry writer is compromised
- rotate
EPISODES_SYNC_PRIVATE_KEY - review recent
createEventandupdateEventcalls - restore canonical event data from
content/episodes.json
Deployment and ops commands
Owner access
Deploy:
npm run contracts:deploy:owner-access
Deploy and mint first key:
npm run contracts:bootstrap:owner-access
Mint another key:
npm run contracts:mint:owner-access
Revoke a compromised token:
npm run contracts:revoke:owner-access
Grant or revoke contract roles:
npm run contracts:roles:owner-access
Event registry sync
Create or update event from catalog:
npm run contracts:sync:event
Environment variable groups
Owner dashboard auth
EPISODES_ADMIN_SESSION_SECRETEPISODES_OWNER_ERC1155_ADDRESSEPISODES_OWNER_ERC1155_TOKEN_IDEPISODES_OWNER_RPC_URLEPISODES_OWNER_ALLOWLIST
Event sync
EPISODES_SYNC_RPC_URLEPISODES_SYNC_PRIVATE_KEYSPECTRA_EVENT_ACCESS_REGISTRY_ADDRESS
Owner-access deploy and mint
OWNER_ACCESS_DEPLOY_RPC_URLOWNER_ACCESS_DEPLOY_PRIVATE_KEYOWNER_ACCESS_ADMIN_ADDRESSOWNER_ACCESS_INITIAL_MINTER_ADDRESSOWNER_ACCESS_BASE_URIOWNER_ACCESS_CONTRACT_METADATA_URIOWNER_ACCESS_CONTRACT_ADDRESSOWNER_ACCESS_RECIPIENTOWNER_ACCESS_ROLEOWNER_ACCESS_TOKEN_IDOWNER_ACCESS_AMOUNT
Emergency/admin commands
OWNER_ACCESS_ADMIN_RPC_URLOWNER_ACCESS_ADMIN_PRIVATE_KEYOWNER_ACCESS_REVOKE_ACCOUNTOWNER_ACCESS_REVOKE_TOKEN_IDOWNER_ACCESS_REVOKE_AMOUNTOWNER_ACCESS_ROLE_ACTIONOWNER_ACCESS_CONTRACT_ROLEOWNER_ACCESS_ROLE_ACCOUNT
Contract interaction matrix
| Layer | Reads | Writes | Notes |
|---|---|---|---|
SpectraSubmissionRegistry | founder contract, app/admin tooling | applicants, approvers, founder contract | source of founder submission truth |
SpectraFounderSeason1_1155 | submission registry | applicants, admins | mints Season 1 membership |
SpectraEventAccessRegistry | future event-gated clients, app sync tools | admin sync path | stores event and access state |
SpectraOwnerAccess1155 | owner auth server | admin/minter ops | soulbound operator identity |
content/episodes.json | UI, metadata APIs, sync layer | owner dashboard | canonical offchain episode source |
PlantUML: full architecture
@startuml
title SPECTRA Full Contract + App Architecture
skinparam componentStyle rectangle
skinparam packageStyle rectangle
actor User
actor Applicant
actor OwnerAdmin as "Owner / Admin"
actor Agent as "AI Agent"
package "Next.js App" {
[Episodes UI]
[Owner Episodes Dashboard]
[Admin Session API]
[Episodes Metadata API]
[Episode Sync Service]
[Owner Session Service]
database "episodes.json" as EpisodesJson
}
package "Contracts" {
[SpectraSubmissionRegistry]
[SpectraFounderSeason1_1155]
[SpectraEventAccessRegistry]
[SpectraOwnerAccess1155]
}
Applicant --> [Episodes UI]
Applicant --> [SpectraSubmissionRegistry] : createSubmission()
[SpectraFounderSeason1_1155] --> [SpectraSubmissionRegistry] : getSubmission()\nmarkMinted()
OwnerAdmin --> [Owner Episodes Dashboard]
[Owner Episodes Dashboard] --> [Admin Session API]
[Admin Session API] --> [Owner Session Service]
[Owner Session Service] --> [SpectraOwnerAccess1155] : balanceOf()
[Owner Episodes Dashboard] --> EpisodesJson : edit/save
[Episodes Metadata API] --> EpisodesJson : read
[Episode Sync Service] --> EpisodesJson : read/write registryEventId
[Episode Sync Service] --> [SpectraEventAccessRegistry] : createEvent()/updateEvent()
Agent --> [Admin Session API] : optional wallet auth
[Episodes UI] --> [Episodes Metadata API]
User --> [Episodes UI]
OwnerAdmin --> [SpectraOwnerAccess1155] : mint/revoke via ops scripts
OwnerAdmin --> [SpectraEventAccessRegistry] : event management
@enduml
PlantUML: founder mint lifecycle
@startuml
title Founder Submission to Founder Mint
actor Applicant
actor Approver
participant "SpectraSubmissionRegistry" as Submission
participant "SpectraFounderSeason1_1155" as Founder1155
Applicant -> Submission : createSubmission(emailHash, metadataURI)
Submission --> Applicant : submissionId
Applicant -> Submission : updateSubmissionMetadata(submissionId, metadataURI)
Approver -> Submission : setSubmissionApproval(submissionId, true)
Applicant -> Founder1155 : mintFounder(submissionId)
Founder1155 -> Submission : getSubmission(submissionId)
Submission --> Founder1155 : submission data
Founder1155 -> Founder1155 : validate approved\nvalidate applicant\nvalidate not minted
Founder1155 -> Applicant : mint tokenId=submissionId
Founder1155 -> Submission : markMinted(submissionId, tokenId)
Submission --> Founder1155 : ok
@enduml
PlantUML: owner auth lifecycle
@startuml
title Owner Dashboard Wallet Authentication
actor WalletHolder
participant "EpisodesAdminPanel" as Panel
participant "Admin Session API" as SessionApi
participant "Owner Session Service" as SessionSvc
participant "SpectraOwnerAccess1155" as Owner1155
WalletHolder -> Panel : connect wallet
Panel -> SessionApi : POST mode=challenge
SessionApi -> SessionSvc : createOwnerWalletChallenge(address)
SessionSvc --> SessionApi : challenge message
SessionApi --> Panel : challenge message
WalletHolder -> Panel : sign message
Panel -> SessionApi : POST mode=wallet\naddress + signature
SessionApi -> SessionSvc : verifyOwnerWalletSignature(...)
SessionApi -> SessionSvc : issueOwnerWalletSession(address)
SessionSvc -> Owner1155 : balanceOf(address, tokenId)
Owner1155 --> SessionSvc : balance > 0
SessionSvc --> SessionApi : session issued
SessionApi --> Panel : authenticated
@enduml
PlantUML: episode sync lifecycle
@startuml
title Episode Catalog to Event Registry Sync
actor OwnerAdmin
participant "Owner Episodes Dashboard" as Dashboard
database "episodes.json" as Catalog
participant "Episode Sync Service" as SyncSvc
participant "SpectraEventAccessRegistry" as Registry
OwnerAdmin -> Dashboard : edit episode
Dashboard -> Catalog : save record
OwnerAdmin -> Dashboard : sync onchain
Dashboard -> SyncSvc : syncEpisodeToRegistry(slug)
SyncSvc -> Catalog : read episode
SyncSvc -> SyncSvc : build registry payload
alt registryEventId exists
SyncSvc -> Registry : updateEvent(eventId, name, metadataURI, active)
else first sync
SyncSvc -> Registry : createEvent(name, metadataURI)
Registry --> SyncSvc : EventCreated(eventId)
SyncSvc -> Catalog : persist registryEventId
end
SyncSvc --> Dashboard : action + tx hash + registryEventId
@enduml
Recommended doc map
Use this master doc as the top-level index, then keep the focused docs below it:
docs/master-contract-integration.mddocs/owner-access-ops.mddocs/episodes-automation-architecture.mddocs/founder-membership-season1.md
Current implementation summary
Right now the system is strongest in these areas:
- owner wallet auth is separated from public UI
- founder mint provenance is explicit
- episode metadata is generated from one canonical catalog
- owner access keys are soulbound and revocable
The next likely hardening step would be:
- split machine-only token ID
3access away from the human dashboard and bind it only to automation endpoints that actually need it