Status: Draft (spec-driven design)
Owner: Token Host
Domain: tokenhost.com
Last updated: 2026-03-23
Scope: Production system. All existing repos are legacy prototypes and are not binding.
This document defines the intended production design of the Token Host platform: a managed schema-to-dapp builder that generates and deploys smart contracts, generates a hosted UI, optionally provisions indexing, and publishes apps under a dedicated hosted-app origin (default *.apps.tokenhost.com, plus optional custom domains).
Normative language: The keywords MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as described in RFC 2119.
Token Host is a “Firebase for Web3”: a platform for building blockchain-backed applications using a CRUD-first mental model.
Unlike traditional CRUD databases, smart contract storage and events are publicly readable on most chains. Token Host can generate gateway/API visibility controls and “read access” checks for its generated UI and hosted APIs, but these do not provide confidentiality. Apps MUST NOT store secrets or sensitive PII on-chain.
A user defines an app schema (collections, fields, constraints, and access rules). Token Host:
- validates the schema,
- generates an on-chain data contract (“App contract”),
- generates a static web UI for CRUD and browsing,
- deploys and verifies the contract on a selected chain,
- (optionally) deploys/attaches an indexer for scalable queries,
- hosts the generated UI at
https://<app-slug>.apps.tokenhost.com.
Token Host also supports a developer/automation mode via a CLI that runs the same validation/generation/deploy steps locally or in CI and can export a complete app bundle.
The original Token Host product intent (predating modern rollups) was a “cloud-hosted chain”: a middle ground between cloud databases and public blockchains where developers get:
- a developer-friendly creator experience (schemas, CRUD, indexing),
- a trusted operator (Token Host) who can run the infrastructure like a cloud provider,
- and a cryptographic audit trail because the authoritative state machine is an EVM.
In today’s ecosystem, the practical implementation is a low-cost EVM rollup/appchain operated by Token Host (“Token Host Chain”) plus a path to graduate onto external EVM chains (including OP Stack-based “OP Chains”).
Token Host’s primary recommended workflow is:
- Launch on Token Host Chain in full on-chain mode (records + indexes) for fast iteration, low fees, and a cloud-like managed experience where the entire backend of the app runs on the EVM (no custom servers required for correctness).
- Graduate to a dedicated appchain / external rollup when the app needs more sovereignty, scale, or ecosystem alignment. Token Host supports this by:
- deploying the same schema-backed contracts to the destination chain,
- switching the release’s primary write deployment,
- preserving continuity by reading legacy deployments and optionally copying state.
Advantages of “EVM as the database backend” (especially on a low-cost rollup):
- Audit trail: all writes are transactions with receipts and events; history is inspectable.
- Deterministic correctness: business rules, access control, constraints, and payments live in contracts.
- Portability: the same generated contracts can run on any EVM chain (subject to chain configs), enabling migration and multi-chain releases.
- Operational simplicity: a static UI + RPC is sufficient to operate a functional app (indexers enhance UX but do not define correctness).
Token Host explicitly targets a “trusted operator with auditability” model:
- Early-stage apps can accept Token Host as the operator (analogous to “IBM-style” managed infrastructure) while benefiting from a verifiable ledger.
- As apps mature, the operator trust model can be reduced over time by migrating to a more decentralized rollup/appchain deployment.
- Given the same canonical schema (
schemaHash), generator version, and toolchain digest, Token Host MUST produce deterministic contract sources and MUST produce artifacts with stable, verifiable digests recorded in the manifest. - Token Host MUST define determinism in terms of canonical artifact digests rather than byte-for-byte identity of bundler output directories; the manifest is the verification surface.
- Every build MUST emit a signed manifest that records schema identity, toolchain versions, and produced artifact digests.
- The platform MUST support a chain registry and per-chain deployment adapters.
- A single schema version MAY be deployed to multiple chains; the manifest MUST track deployments per chain.
- Token Host MUST NOT require users to upload, store, or share wallet private keys to use generated apps.
- Managed transaction improvements (gas sponsorship, batching, “session keys”) MUST be implemented without persistent user key custody (see EIP-7702 section).
- In the default configuration (with on-chain indexing enabled), the generated app MUST work using on-chain reads and writes without relying on an off-chain indexer.
- Token Host MAY support a “Lite mode” that disables heavy on-chain indexes for cost reasons; in that mode, list/search UX MAY require an indexer, but core create/update/delete and direct
getby id MUST remain supported. - When indexing is enabled, the UI SHOULD transparently switch to indexer-backed queries for scalability while preserving correctness (falling back to on-chain if the indexer is unavailable).
- Secrets MUST be provided only via secure configuration (environment variables or secret stores). No hard-coded secrets in repo, templates, or generated output.
- Uploaded assets MUST use signed upload URLs; arbitrary server file paths MUST NOT be accepted.
- Token Host MUST treat all on-chain storage and emitted events as publicly readable.
- Token Host MUST NOT describe “read access” as privacy, encryption, or confidentiality; it is gateway/API visibility only.
- Studio MUST display an explicit warning when designing schemas and before publishing: “On-chain data is public. Do not store PII, secrets, or sensitive business data.”
- If an app requires confidentiality, Token Host MUST support privacy-preserving patterns such as:
- client-side encryption (store ciphertext/commitments on-chain; manage keys off-platform), and/or
- storing sensitive fields off-chain with on-chain references/commitments.
- Token Host SHOULD be positioned and optimized for L2s/high-throughput chains (e.g., Base, Arbitrum, Optimism, Polygon) where on-chain CRUD is economically viable.
- Studio MUST warn users when deploying schemas that store large
stringfields or maintain heavy on-chain indexes on expensive chains. - For L1 deployments, Studio SHOULD default to Lite mode and MUST clearly communicate the gas/storage economics tradeoffs.
- Token Host SHOULD offer a first-party, low-cost EVM rollup/appchain (“Token Host Chain”) optimized for full on-chain mode (records + indexes).
- Token Host Chain and Token Host-provisioned appchains SHOULD be based on a standardized EVM rollup/appchain stack (e.g., OP Stack) to simplify provisioning, operations, and migration workflows.
- Token Host MUST support a first-class “launch on Token Host Chain, migrate to external chain” workflow via releases that can reference legacy deployments and switch the primary write target over time (see Section 11.8).
- Token Host MUST keep generated contracts portable across EVM chains (no hidden dependencies on Token Host Chain-specific precompiles); chain differences are expressed only through adapters and configuration.
Token Host’s managed-chain model is designed to support a deliberate, staged reduction of trust in the operator over time.
- Token Host Chain and Token Host-provisioned appchains MAY begin with a centralized operator (sequencer + ops) for performance and DX.
- The system MUST be designed so that decentralization can increase over time without changing application schemas or contract semantics (only the chain environment changes).
- Studio MUST represent chain “trust posture” clearly (e.g., managed vs external vs decentralized) and MUST avoid implying decentralization where it does not exist.
Token Host apps are intended to feel “alive” despite being backed by blockchain state.
- Generated apps SHOULD use chain events and (where available) websocket subscriptions to trigger refreshes and keep list/detail pages up to date.
- Real-time UX MUST NOT change correctness semantics: contracts remain authoritative, and reorg/finality behavior MUST be respected (see Sections 4.5.5 and 8.9).
- Studio: the web UI at
https://app.tokenhost.comwhere users create schemas, build, deploy, and manage hosted apps. - Schema: versioned JSON document that defines collections, fields, constraints, and access rules.
- App: a Token Host project with a chosen slug and one or more schema versions.
- Collection: a schema-defined entity (formerly called “contract” in prototypes) that maps to an on-chain record set.
- Record: an instance in a collection.
- Build: a deterministic generation + compilation + packaging run for a schema version.
- Deployment: a chain-specific deployment of a build’s contracts.
- Release: a published UI + contract address set mapped to a domain (subdomain or custom domain).
- Manifest: a machine-readable artifact describing build inputs, outputs, and deployments.
- Indexer: optional off-chain service that consumes on-chain events to enable search and advanced queries.
- Token Host Chain: a Token Host-operated EVM rollup/appchain intended to provide a cloud-like default environment for full-mode apps.
- Appchain: a dedicated EVM rollup/appchain provisioned for a specific app (or org) with its own RPC endpoints and chain configuration, intended as a “graduation” step from the shared Token Host Chain.
- Chain migration: switching a release’s primary deployment from one chain to another (while optionally continuing to read legacy deployments and optionally copying state).
Token Host consists of the following production services:
-
Studio (Web)
- Handles user interactions: schema editing, build/deploy actions, domain settings, viewing logs and status.
-
Token Host API (
api.tokenhost.com)- Multi-tenant API for authentication, schema storage, builds, deployments, domains, templates, and billing.
-
Build Orchestrator
- Accepts build/deploy requests, enqueues jobs, manages concurrency, and exposes job state transitions.
-
Workers
- Stateless workers that run validation, code generation, compilation, packaging, deployment, verification, and indexer provisioning.
-
Artifact Store
- Stores build outputs: manifests, generated sources, ABIs, UI bundles, checksums, and logs.
-
Hosting + CDN
- Serves static releases under
*.apps.tokenhost.com(or a separate hosted-app domain). - Provides a “Building…” status page when a release is not yet published.
- Serves static releases under
-
Chain Adapter Layer
- Provides deploy/verify operations for supported chains with chain registry configuration.
-
Indexer Layer (Optional)
- Provides deployable/queryable indexers (e.g., The Graph or a managed event indexer).
-
Token Host Chain (Optional, first-party network)
- A Token Host-operated EVM rollup/appchain providing low-cost execution, stable RPC endpoints, and (optionally) standardized AA/sponsorship infrastructure for hosted apps.
-
Appchain Provisioner (Optional)
- Service that provisions dedicated EVM appchains (e.g., OP Stack-based “OP Chains”) for apps/orgs, manages chain configuration, and integrates new chains into Token Host’s deploy/index/host workflows.
- The user’s wallet (EOA or smart account) is outside Token Host trust boundaries.
- Token Host MUST treat RPC nodes, explorers, and OAuth providers as external dependencies.
- Token Host MUST assume schema input is untrusted and MUST validate/sanitize before using it to generate code.
- If Token Host operates Token Host Chain and/or managed appchains, those networks are part of Token Host’s operational trust boundary; Studio MUST disclose their trust posture to users (managed vs external vs decentralized).
- Managed: Token Host hosts the UI and orchestrates builds and deployments; users interact via Studio + generated app.
- Export/Self-host: Users can download a complete app bundle (schema + generated source + UI bundle + manifest) and deploy/host themselves.
Token Host is designed around the idea that an application’s backend can live entirely on an EVM execution layer:
- data (records),
- integrity constraints (uniqueness),
- access control (create/update/delete/transfer),
- optional on-chain indexes for browsing (full mode),
- and optional payment/reward mechanics.
In this model, “hosting the app” is primarily:
- hosting a static UI bundle, and
- operating reliable RPC/indexing infrastructure for the chain(s) the app targets.
Token Host provides two managed chain deployment flavors:
-
Token Host Chain (shared)
A Token Host-operated EVM rollup/appchain intended as the default “cloud chain” environment for new apps. It SHOULD be optimized for:- low, predictable fees suitable for CRUD workloads,
- stable RPC endpoints and operational SLOs,
- standardized account-abstraction/sponsorship capabilities (where feasible),
- easy migration out to external EVM chains.
-
Appchains (dedicated)
Token Host MAY provision dedicated EVM rollups/appchains per app or org (e.g., OP Stack-based “OP Chains”). These are the “growth path” for apps that need more sovereignty or dedicated throughput while keeping the same EVM contract backend model.
Token Host’s “trusted authority with audit trail” positioning implies:
- The operator (Token Host) MAY control sequencing, fees, and infrastructure in managed environments.
- The system SHOULD provide verifiable history and auditability so that developers/users can independently inspect state transitions.
- Studio MUST clearly communicate when an app is running on a Token Host-managed chain vs an external chain and MUST avoid implying trustlessness when a trusted operator exists.
This section specifies the chain-layer assumptions and artifacts required to support Token Host’s primary lifecycle (launch on a managed “cloud chain”, then migrate to an appchain/external EVM chain) without changing application contract semantics.
Token Host Chain and Token Host-provisioned appchains are “trusted operator + audit trail” environments: the operator may be centralized, but state transitions are recorded on an EVM ledger and are independently inspectable.
- Provide a low-cost, high-throughput EVM execution environment suitable for full-mode apps (records + indexes) and creator workflows.
- Provide cloud-like operational guarantees (reliable RPC, predictable fees, observability) while keeping application correctness in contracts.
- Preserve portability: Token Host-generated contracts MUST remain deployable on external EVM chains without semantic changes.
- Provide a credible “graduation” path: from shared Token Host Chain -> dedicated appchain -> external EVM chain(s).
Token Host MUST treat chain trust posture as a first-class, explicit property of every chain configuration.
At minimum, Token Host MUST support the following trust posture labels:
managed: Token Host operates the sequencer and chain infrastructure.co-managed: Token Host and customer/dev share governance and/or operational control via multi-party controls.external: Token Host does not operate the chain (external EVM chain).decentralized: chain governance/sequencing is not controlled by Token Host (must not be used unless defensible).
For managed and co-managed chains, Token Host MUST assume (and disclose) that:
- The operator MAY be able to censor or delay transactions.
- The operator MAY perform chain software upgrades that affect performance/availability.
- The operator’s infrastructure is part of the app’s operational dependency graph.
“Audit trail” in this context means:
- all successful writes are transactions with receipts and emitted events,
- the chain exposes sufficient public RPC access for independent inspection of history,
- Token Host provides signed configuration and change logs for chain lifecycle actions (see below).
Audit trail MUST NOT be described as censorship resistance or privacy.
Token Host Chain/appchains MUST provide:
- stable, documented RPC endpoints for reads and writes,
- access to transaction receipts and logs for indexing,
- consistent chain identifiers (
chainId) and network metadata.
Token Host MUST provide a chain configuration artifact that can be independently verified for integrity (Section 4.5.5).
For managed chains, Token Host SHOULD provide additional verification affordances, such as:
- a public “chain status” endpoint (uptime/incident history),
- published hashes/digests of chain configuration and upgrades,
- a public archive RPC endpoint (where feasible) or a documented data retention policy.
Token Host MUST distinguish between:
- app immutability (schema versions and deployed app contracts), and
- chain upgrades (rollup/appchain software upgrades, sequencer infrastructure, DA configuration).
Chain upgrades are sometimes necessary. Therefore:
- Token Host MUST publish an explicit chain upgrade/maintenance policy for Token Host Chain and Token Host-managed appchains.
- Token Host MUST log and audit all chain lifecycle actions (provisioning, upgrades, parameter changes, key rotations) with immutable records and timestamps.
- High-impact chain actions SHOULD require multi-party approval controls (see Section 15.7).
Studio MUST surface chain upgrade status separately from app build/deploy status.
Every chain used by Token Host (Token Host Chain, Token Host-provisioned appchains, and user-configured external chains) MUST be representable as a versioned chain configuration artifact.
- The canonical JSON Schema for this artifact is
schemas/tokenhost-chain-config.schema.json. - Token Host MUST store and serve chain config artifacts as immutable, versioned documents.
- Chain config artifacts MUST be signed by the issuer (Token Host for managed chains; user/org for BYO chain configs).
At minimum, the chain config artifact MUST include:
chainId, name, and native currency metadata,- trust posture label and operator identity (for
managed/co-managed), - expected finality assumptions (e.g., recommended confirmations, typical finality time),
- data availability / settlement metadata for rollups (L1 origin chain, bridge entry points, DA mode/provider where applicable),
- sequencer/proposer identity metadata for managed chains (at minimum, operator name; optionally key IDs),
- RPC endpoints and explicit capability flags (archive, batching, trace/debug availability),
- websocket/log-subscription capability where available (for real-time UIs; see Section 8.9),
- recommended limits/caps for generated contracts and UIs where available (e.g., list page size and realtime polling intervals),
- operational metadata (rate limits, status/SLO URL pointers where available),
- explorer URLs (if applicable),
- AA/sponsorship capability flags (if applicable),
- issuer identity and signatures.
For real-time UIs, chain configs SHOULD include at least one wss:// RPC endpoint and SHOULD mark it with rpc.endpoints[].capabilities.subscriptions=true when eth_subscribe log subscriptions are supported.
Token Host MUST model chain/appchain lifecycle actions as jobs with audit logs, similar to build/deploy jobs:
- provision Token Host-managed appchain,
- update chain configuration,
- rotate operator keys (where applicable),
- deprecate chain endpoints and migrate apps off a chain.
When Token Host provisions an appchain, the provisioning result MUST include:
- a chain config artifact that can be exported/self-hosted,
- explicit trust posture and operator identity,
- a clear “exit path” recommendation (how to migrate to another EVM chain).
This section defines how Token Host communicates and executes a “managed -> co-managed -> decentralized” growth path for Token Host Chain and Token Host-managed appchains.
Token Host MUST represent chain trust posture clearly and consistently across Studio and hosted apps.
Studio MUST:
- show the chain’s trust posture label (
managed|co-managed|external|decentralized) alongside every chain selector, - display the chain operator identity (e.g., “Operated by Token Host”) for
managed/co-managedchains, - link to the chain config artifact (and its signatures) for the selected chain,
- show a warning banner before deploying to a
managedchain explaining the authority model (possible censorship/delay) and the auditability model (verifiable history), - never label a chain as
decentralizedunless Token Host can justify that claim with documented criteria.
Hosted apps (generated UI) SHOULD:
- display the primary chain trust posture in an “About/Network” panel,
- link to release manifests and chain config artifacts where discoverable,
- display migration context when legacy deployments exist (see Section 8.8).
This is a non-normative reference progression that Token Host can use to communicate maturity and guide engineering.
Stage 0: Managed (default launch posture)
- Centralized operator runs sequencer + infra.
- Focus: low fees, reliability, and developer velocity.
- Requirements: strong operational security, clear disclosure, robust audit logs, stable RPC.
Stage 1: Co-managed
- Introduce multi-party controls for governance/admin actions (e.g., multi-sig for upgrades).
- Clarify separation of duties between Token Host and the customer/dev org.
- Improve transparency: published change logs, signed config versions, scheduled maintenance windows.
Stage 2: Decentralization-ready
- Reduce single-operator risk through stronger guarantees around configuration integrity, retention, and recoverability.
- Expand independent verification affordances (archive access, consistent indexing surfaces, documented DA/finality assumptions).
- Formalize and test migration/exit procedures as a first-class capability.
Stage 3: Decentralized (aspirational)
- Governance and sequencing are not controlled by Token Host.
- Token Host’s role becomes tooling + hosting + service integration.
- Warning: Token Host MUST treat “decentralized” as a claim requiring rigor and MUST avoid marketing-driven redefinitions.
Every app MAY be published to a Token Host subdomain:
https://<app-slug>.apps.tokenhost.com
app-slug MUST be unique platform-wide and MUST conform to:
- lowercase letters, digits, hyphen
- length 3–63
- no leading/trailing hyphen
- not in the reserved slug list (
app,api,www,admin,support, etc.)
Slug uniqueness MUST be enforced at app creation time and also at publish time (to prevent takeover of released subdomains).
Before an app is published (or while a new release is building), the subdomain MUST serve a static status page indicating:
- build state (queued/running/failed/succeeded),
- last updated timestamp,
- links to Studio for authorized users (not publicly exposing sensitive logs).
Apps MAY be published to custom domains owned by the user.
Token Host MUST implement DNS-based ownership verification (e.g., TXT record with a signed token) and MUST provide:
- required DNS instructions,
- verification status,
- automatic TLS provisioning via ACME.
- Token Host MUST version UI bundles by content hash and serve immutable assets with long cache lifetimes.
- Publishing a new release MUST update the subdomain routing to point at the new versioned bundle without needing cache invalidation for immutable assets.
Token Host hosts user-generated applications under Token Host-controlled domains. As the hosting provider, Token Host MUST implement a trust & safety mechanism that can unpublish the hosted UI even if the underlying contracts remain live.
- Token Host MUST support placing an app (or specific release) into a
suspended/takedownstate. - In takedown state, Token Host MUST disconnect domain routing for the app (or serve a generic “unavailable” page) and MUST NOT serve the user-generated UI bundle from Token Host-controlled domains.
- Takedown actions MUST be audited and access-controlled (admin-only).
- Takedown MUST NOT attempt to “delete” on-chain data; it only affects hosted content and Token Host services.
Serving untrusted, user-generated apps under the same origin or cookie scope as platform control planes increases security and reputation risk.
- Hosted apps MUST be served from a dedicated hosted-app origin distinct from Studio and API.
- Studio/API session cookies MUST NOT be scoped to
.tokenhost.comand MUST NOT be readable by hosted apps. - Studio and API cookies SHOULD be host-only (no
Domain=attribute) and SHOULD use__Host-cookie prefixes. - Token Host SHOULD use a separate registrable domain (different eTLD+1) for hosted apps in production. If hosted apps are served under a
tokenhost.comsubdomain (e.g.,*.apps.tokenhost.com), Token Host MUST harden cookie scope and isolation as above. - Hosted apps MUST be treated as untrusted content: strict CSP, no privileged cookies, and safe escaping of user-provided content.
The Token Host Schema (THS) is a JSON document that fully describes the user-facing and on-chain behavior of a Token Host app. It is the single source of truth for generation, builds, and deployments.
At minimum, a schema contains:
- a required
thsVersion(the THS document format version), - a
schemaVersion(the app’s semantic version) for human-readable versioning, - an
appsection (metadata, features, theming), - a list of
collections(CRUD data models).
thsVersion identifies how to parse/validate the schema document. schemaVersion identifies the app’s semantic version and MUST NOT be used to infer schema document format.
In addition to schemaVersion, Token Host computes a canonical schemaHash for identity and reproducibility. The schemaHash MUST be computed over the entire schema document (including thsVersion) by:
- canonicalizing JSON using RFC 8785 (JSON Canonicalization Scheme), and
- hashing the canonical bytes with SHA-256,
- representing the output as
sha256:<lowercase-hex>.
The schemaHash is used to:
- uniquely identify build inputs,
- deduplicate identical schemas,
- key build caches safely,
- sign manifests.
Schema versions are immutable once published. Editing a published schema MUST create a new schema version.
The canonical root fields are:
thsVersion: string, required (THS document format version)schemaVersion: string, required (app semver)app: object, requiredname: string, requiredslug: string, required (must equal or derive from hosted slug rules)description: string, optionaltheme: object, optional (colors, typography, logos)features: object, optional (indexer, delegation, uploads, on-chain indexing)
collections: array, requiredmetadata: object, optional (createdBy, createdAt, tags)
If present, app.features MAY include:
indexer: boolean, default false (enable indexer integration)delegation: boolean, default false (enable delegation UX where supported; see Section 10)uploads: boolean, default false (enable managed upload flows; see Section 8.3)onChainIndexing: boolean, default true (when false, disable optional on-chain query indexes; see Section 7.8)
The app.slug is the durable identifier used for:
- hosted routing (
<slug>.apps.tokenhost.comby default), - release ownership and access checks in Studio,
- template selection defaults.
Token Host MUST treat slug as immutable once an app has been published to a hosted origin. If renaming is supported, it MUST be implemented as “new slug + redirect” rather than reassigning ownership of an existing slug.
Each collection defines:
name: string, required (PascalCase)plural: string, optional for UI (defaults toname + "s")fields: array of Field Definitions (user-defined fields)createRules,visibilityRules,updateRules,deleteRules,transferRules: access and field policiesindexes: uniqueness and query indexesrelations: references to other collectionsui: per-collection UI overrides (default list columns, sort, forms)
Fields define on-chain storage and UI behavior. A field includes:
name: string, required (camelCase, unique in collection)type: enum, requiredrequired: boolean, default false (UI-only; on-chain requiredness is defined bycreateRules.required)decimals: number, required whentype="decimal"(0–18 recommended; defines the scale factor10^decimals)default: optional default value (off-chain only unless representable on-chain)validation: optional validation rules; Token Host MUST clearly label which validations are enforced on-chain (authoritative) vs off-chain (UX-only)ui: optional display metadata (label, placeholder, widget, helpText)
Studio SHOULD auto-derive field.required from createRules.required and MUST warn (or auto-correct) when they diverge.
Supported v1 field types:
string: UTF-8 string, stored on-chainuint256: numeric, stored on-chainint256: numeric, stored on-chaindecimal: fixed-point decimal stored as a scaleduint256(schema MUST specifydecimals; UI MUST handle parse/format)bool: stored on-chainaddress: stored on-chainbytes32: stored on-chainimage: stored asstringURL/CID; upload handled off-chain (see uploads)reference: foreign key to another collection (stored asuint256record ID)externalReference: address pointer to an external contract/account (stored asaddress)
Field names MUST NOT conflict with:
- system field names (
id,createdAt,createdBy,owner,updatedAt,updatedBy,isDeleted,deletedAt,version), - reserved Solidity keywords or global names (
address,mapping,contract, etc.), - reserved UI/runtime identifiers used by the generated app.
Token Host collections always include system fields in the generated contract, even if not declared in the schema:
id(uint256): record IDcreatedAt(uint256): block timestamp at createcreatedBy(address):_msgSender()at createowner(address): current owner at create (defaults to_msgSender()); updated by transfers if enabledupdatedAt(uint256): block timestamp at update (if updates enabled)updatedBy(address):_msgSender()at update (if updates enabled)isDeleted(bool): soft delete flag (if soft delete enabled)deletedAt(uint256): timestamp at delete (if soft delete enabled)version(uint256): optimistic concurrency counter (optional feature)
The generated UI MUST hide system fields by default but MAY expose them in “advanced” views.
Each collection defines operation policies:
Create rules
required: list of field names required at createauto: map of system/custom fields to expressions (limited safe set)payment: optional payment requirement for create (v1 supports native currency only)access: one of:public: any addressowner: shorthand for “any address; recordowneris set to_msgSender()at create”allowlist: only addresses in an on-chain allowlistrole: addresses with RBAC roles (OpenZeppelin-style AccessControl)
If createRules.payment is present, it MUST have:
asset:"native"(v1)amountWei: string (uint256 in wei)
Studio MAY accept human-readable inputs (e.g., “0.01 ETH”) but MUST convert and store the canonical amountWei in the schema to preserve determinism and chain-agnostic behavior.
Visibility rules (UI/gateway visibility; not confidentiality)
gets: list of fields displayed by default in the generated UI and returned by Token Host-hosted gateway/indexer APIs (if applicable)access:public|owner|allowlist|role(enforced in UI and Token Host-hosted APIs; does not provide confidentiality for on-chain data)
Visibility rules MUST be described as gateway/API visibility only. They MUST NOT be presented as privacy or confidentiality, because on-chain storage and events remain publicly readable.
Update rules
mutable: list of user fields that may be updatedaccess:owner|allowlist|role(default SHOULD beowner)optimisticConcurrency: boolean (if enabled, caller supplies expected version)
Delete rules
softDelete: boolean (default true)access:owner|allowlist|role
Transfer rules
Transfers enable asset-like ownership changes (tickets, coupons, items) where the current owner can assign the record to a new owner address.
- If
transferRulesis absent, the generator MUST NOT emit transfer methods for the collection. - If
transferRulesis present, it MUST define:access:owner|allowlist|role(default SHOULD beowner)
Transfers MUST:
- change
record.owner(but MUST NOT changerecord.createdBy), - be disabled for deleted records (soft or hard deleted),
- reject
to == address(0).
Example (transfer-enabled asset collection):
{
"name": "Ticket",
"fields": [{ "name": "eventName", "type": "string", "required": true }],
"createRules": { "required": ["eventName"], "access": "public" },
"visibilityRules": { "gets": ["eventName"], "access": "public" },
"updateRules": { "mutable": [], "access": "owner" },
"deleteRules": { "softDelete": true, "access": "owner" },
"transferRules": { "access": "owner" },
"indexes": { "unique": [], "index": [] }
}Access rules are enforced on-chain. UI MUST reflect access (disable/hide actions), but on-chain enforcement is authoritative.
In managed mode, Studio MAY expose convenience “roles” (e.g., “Only my team”) that map to explicit on-chain allowlists or RBAC role assignments. The contract MUST remain the final arbiter of permission.
Token Host defines two families of constraints:
- Uniqueness constraints (
unique)- Enforced on-chain at create and update.
- Each uniqueness constraint MUST specify a
fieldand MAY specify ascope:
Schema shape (normative):
indexes.unique: array of objects{ "field": string, "scope": "active"|"allTime" }(scopedefaults toactive).active(default): unique among non-deleted records; values MAY be reused after soft delete.allTime: values are never reusable once claimed (recommended for handles/slugs where reuse enables impersonation).
- Query indexes (
index)- Used to support equality-based listing and reverse reference listing.
- Indexes MAY be implemented as append-only lists (see on-chain scaling notes).
Relations are expressed through reference fields and relation metadata:
field: the local reference field nameto: target collection nameenforce: boolean; if true, create/update MUST ensure referenced record exists and is not deletedreverseIndex: boolean; if true, Token Host MUST maintain a reverse lookup index for the relation
Schema evolution requires explicit migrations and an explicit data continuity strategy:
- Breaking schema changes MUST bump
schemaVersion(semver major). - Token Host MUST provide a migration framework that can transform schema JSON between versions.
- On-chain data migration is not automatic. By default, Token Host treats contracts as immutable deployments and preserves historical data at historical addresses.
- Each schema version deployment MUST be treated as immutable: contract code and storage are not upgraded in place.
- Deploying schema v2 on a chain produces a new App deployment for that chain (one or more contract addresses, depending on deployment mode).
- A release MAY point to multiple deployments across schema versions to preserve continuity in the user experience.
To avoid “migration cliffs” where historical data becomes stranded at an old address, Token Host’s default continuity strategy is UI aggregation:
- A release manifest MUST be able to reference:
- a primary deployment (the latest schema version the app writes to), and
- legacy deployments (previous schema versions the UI can still read from).
- The generated UI MUST support presenting legacy data as read-only (unless the legacy deployment is also configured as writable, which SHOULD NOT be the default).
- When displaying records across deployments, the UI MUST treat a record’s durable identity as
(chainId, deploymentEntrypointAddress, collectionId, recordId)(not justrecordId).deploymentEntrypointAddressis the canonical address for a deployment: the single App contract address in single-contract mode, or the App Router contract address in modular mode.
- If schema v2 adds fields that did not exist in v1, the UI MUST render missing fields for v1 records as “not available” (not as empty strings) and MUST NOT pretend those fields ever existed historically.
Token Host MAY provide a user-driven “upgrade record” flow that copies a legacy record into the new deployment (creating a new record ID in v2), but MUST clearly label it as a copy/migration action (not a transparent in-place update).
Token Host MAY support advanced continuity modes, but they MUST be explicitly selected (they are not assumed in the core product model):
- Link pattern (“fallback contract”): schema v2 deployment stores an immutable
fallbackContractaddress. Read methods check v2 first, then consult v1 for missing records/fields. This increases read complexity and can increase gas/RPC costs; it MUST be carefully bounded and paginated. - Proxy upgrades (EIP-1967): upgradeable proxies can preserve a single address across logic upgrades, but introduce additional trust/upgrade risk and complicate Token Host’s “deterministic + immutable artifacts” philosophy. If supported, Token Host MUST surface proxy risks clearly in Studio and MUST provide strict admin controls and auditability.
{
"thsVersion": "2025-12",
"schemaVersion": "1.0.0",
"app": { "name": "Job Board", "slug": "job-board" },
"collections": [
{
"name": "Candidate",
"fields": [
{ "name": "handle", "type": "string", "required": true },
{ "name": "bio", "type": "string" },
{ "name": "photo", "type": "image" }
],
"createRules": { "required": ["handle"], "access": "public" },
"visibilityRules": { "gets": ["handle", "bio", "photo"], "access": "public" },
"updateRules": { "mutable": ["bio", "photo"], "access": "owner" },
"deleteRules": { "softDelete": true, "access": "owner" },
"indexes": {
"unique": [{ "field": "handle", "scope": "allTime" }],
"index": []
}
},
{
"name": "JobPosting",
"fields": [
{ "name": "title", "type": "string", "required": true },
{ "name": "description", "type": "string" },
{ "name": "salary", "type": "decimal", "decimals": 2 }
],
"createRules": {
"required": ["title"],
"payment": { "asset": "native", "amountWei": "10000000000000000" },
"access": "public"
},
"visibilityRules": { "gets": ["title", "description", "salary"], "access": "public" },
"updateRules": { "mutable": ["description", "salary"], "access": "owner" },
"deleteRules": { "softDelete": true, "access": "owner" },
"indexes": { "unique": [], "index": [] }
}
]
}Token Host validates schemas in two phases:
-
Structural validation (JSON Schema)
- Ensures types and required keys exist.
- Rejects unknown keys unless explicitly allowed by the schema version.
-
Semantic validation (Token Host lint rules)
Token Host MUST reject schemas that are structurally valid but unsafe or non-portable. Examples include:- duplicate names (collections/fields),
- references to non-existent collections,
- circular reference constraints that would require unbounded on-chain checks,
- indexes on unsupported field types,
- contradictory rules (e.g., updateRules references fields not declared),
- contract-size risk (too many collections/fields for a single deployment target).
Validation taxonomy (authoritative vs UX-only)
Token Host MUST clearly distinguish:
- On-chain authoritative constraints: enforced by the generated contracts (e.g., access control, required fields at create, uniqueness, reference existence when
enforce=true, payment requirements, pagination bounds). - Off-chain UX-only validations: enforced only by Studio / the generated UI (e.g., regex patterns, rich formatting rules, complex field interdependencies).
If a schema includes UX-only validations, Studio MUST label them as “UI-only” and MUST NOT imply they are enforced for callers who interact with the contracts directly.
To keep generated contracts deployable and usable, Token Host SHOULD enforce configurable safety limits, such as:
- maximum number of collections per schema,
- maximum number of fields per collection,
- maximum number of indexed fields per collection,
- maximum string length (enforced off-chain; optionally enforced on-chain with
bytes(s).lengthchecks), - maximum pagination
limitin list methods.
Studio MUST surface validation failures as actionable, field-scoped errors (not generic “failed” messages).
The schema can request automatic field assignment at create/update time via createRules.auto. Because these values are executed on-chain, Token Host MUST restrict this feature to a safe, deterministic expression set.
In v1, Token Host supports the following auto expressions:
block.timestamp(for timestamp fields)_msgSender()(for actor fields; equivalent tomsg.senderunless meta-tx/forwarding is configured)- constant literals representable on-chain (e.g.,
0,1, fixedbytes32)
Token Host MUST NOT allow arbitrary Solidity expressions in the schema (e.g., loops, external calls, or dynamic computation), because that would turn schema input into code execution.
For each supported field type, Token Host defines a canonical mapping that is consistent across:
- Solidity storage types,
- Solidity function parameter types (
calldata/memory), - ABI encoding,
- UI widgets and validation.
Examples:
stringandimage:- Solidity storage:
string - Create/update parameters:
string calldata - UI widget: single-line or multi-line text; image uses upload flow and stores URL/CID
- Solidity storage:
reference:- Solidity storage:
uint256 - Create/update parameters:
uint256 - UI widget: reference picker (search by unique field if available) or direct ID input in advanced mode
- Solidity storage:
decimal(fixed-point):- Solidity storage:
uint256(scaled by10^decimals) - Create/update parameters:
uint256scaled integer - UI widget: decimal/currency input that parses to scaled integer and formats by dividing by
10^decimals
- Solidity storage:
Token Host MUST treat image as a string at the contract layer and MUST NOT attempt to store binary blobs on-chain.
Token Host’s “CRUD builder” is the contract generation strategy for turning collections into on-chain storage and methods.
A schema version deployment is called an App deployment. An App deployment MUST be either:
- Single-contract mode: one generated App contract address per deployment, or
- Modular mode: multiple immutable contracts per deployment (e.g., an App Router + per-collection module contracts) to avoid EVM bytecode size and deployment gas limits.
The generator MAY choose the mode automatically based on schema size and target-chain constraints, but the release manifest MUST fully describe the resulting contract set.
- In single-contract mode, all collections are implemented in one contract.
- In modular mode, Token Host MUST generate per-collection module contracts and MUST provide a discoverability mechanism (either an App Router contract that lists module addresses, or a manifest URL published in contract metadata).
- In modular mode, the generated UI/client MUST be able to route calls to the correct contract per collection based on the manifest.
- Reference enforcement (when
relations[].enforce=true) in modular mode MUST be implemented as bounded external calls to the referenced collection module (no unbounded iteration).
For each collection C, the generated contract maintains:
nextIdC: uint256starting at 1recordsC: mapping(uint256 => RecordC)storing system fields and user fieldsactiveCountC: uint256count of non-deleted records (O(1) reads)- If on-chain listing/enumeration is enabled, the contract MUST be able to enumerate records deterministically. In v1, because IDs are sequential, enumeration MAY be derived from
nextIdC(range1..nextIdC-1) and does not strictly require storing anallIdsCarray. IfallIdsC: uint256[]is stored, it MUST be append-only and MUST reflect created IDs. - If soft delete is enabled, records MUST include an
isDeletedflag and the contract MUST maintainactiveCountCaccurately. - The contract MAY additionally maintain an active set to accelerate “browse active records” views without scanning tombstones:
activeIdsC: uint256[]list of active (non-deleted) record IDsactivePosC: mapping(uint256 => uint256)mapping record ID to position+1 inactiveIdsC(0 means “not active”), enabling O(1) removal via swap-and-pop.
The record struct MUST include all system fields plus user-defined fields. Soft deletion MUST NOT physically remove the record struct; it marks isDeleted.
- Generated contracts MUST use OpenZeppelin’s
Contextand MUST use_msgSender()for attribution and access checks (future-proofing for meta-transactions and account abstraction patterns). createdByMUST equal_msgSender()at record creation and MUST be immutable thereafter.ownerMUST equal_msgSender()at record creation. If transfers are enabled for the collection,ownerMAY change via an explicit transfer method.updatedByMUST equal_msgSender()at update.- The system MUST NOT use
tx.originfor attribution.
Create operations MUST:
- check create access (public/allowlist/role),
- if
createRules.paymentis configured, enforce the payment requirement (see below), - validate required fields (on-chain representable validation),
- enforce uniqueness constraints for configured unique fields,
- optionally enforce reference existence constraints,
- assign ID =
nextIdCand incrementnextIdC, - populate system fields,
- store the record,
- update indexes,
- emit events.
Create MUST be a single transaction and MUST be deterministic for identical inputs.
If createRules.payment is configured for a collection:
- the generated create function MUST be
payable, - the function MUST revert unless
msg.value == amountWei, - collected value SHOULD remain in the App contract balance (pull pattern).
If createRules.payment is not configured for a collection, the generated create function SHOULD be non-payable.
Treasury and withdrawal
- Deployments MUST specify a
treasuryAddress(see Section 7.13) that is the default recipient of withdrawn native fees. - The generated App contract MUST include an admin-controlled withdrawal mechanism for accumulated native fees (exact shape generator-defined), and MUST avoid reentrancy hazards (e.g.,
nonReentrant+ effects-before-interactions on withdrawals).
Read operations MUST include:
getC(id): returns the record (or reverts if not found or deleted, unlessincludeDeletedis requested)existsC(id): returns true if record exists and is not deletedgetCountC(includeDeleted=false): returns count (computed from counters; may exclude deleted)
The generated UI MUST prefer getC for detail pages.
To distinguish “never created” from “created but empty values”, Token Host MUST include an existence signal in storage. In v1, existence SHOULD be derived from createdBy != address(0) (because createdBy is set on create and is never the zero address on live chains).
List operations MUST be paginated and MUST avoid oversized responses.
For each collection C, the generator MUST emit a paginated ID listing method:
listIdsC(cursorIdExclusive, limit, includeDeleted=false)returns up tolimitrecord IDs (uint256[]) in descendingidorder (newest-first).
Semantics:
cursorIdExclusiveis an exclusive upper bound. IfcursorIdExclusive==0, the listing starts fromnextIdC(i.e., “from the newest record”).- The function MUST scan downward from
min(cursorIdExclusive, nextIdC)and return record IDs in descending order. - If
includeDeleted=false, the function MUST skip deleted records. - The function MUST be bounded. The generator MUST enforce a maximum scan budget per call (
maxScanSteps) so listings cannot become unbounded due to tombstones.- If the scan budget is exhausted before
limitresults are found, the function MUST return the results it has found (which MAY be fewer thanlimit).
- If the scan budget is exhausted before
This listing intentionally models a live view over a changing dataset (see Section 8.9). Page boundaries may shift as new records are created, deleted, or restored.
To reduce RPC payload size, returning full records from list methods SHOULD be avoided. If the generator emits a list-of-records convenience method (e.g., listSummariesC), it MUST be bounded, paginated, and MUST return only system fields plus a schema-configured subset of “summary fields” (recommended default: visibilityRules.gets).
Implementation note: list methods MUST cap limit to a safe maximum (configurable per deployment), and MUST cap scan steps (see above).
Recommended defaults (non-normative):
- If chain config provides
limits.lists.maxLimitand/orlimits.lists.maxScanSteps, the generator SHOULD use those values (or stricter). - Otherwise, a safe default is
maxLimit=50andmaxScanSteps=1000. - The UI SHOULD handle “short pages” (fewer than
limitresults) gracefully and SHOULD allow loading more using the next cursor (the smallest returned ID).
Update operations are optional per collection (enabled when updateRules.mutable is non-empty).
Update MUST:
- check update access,
- validate record exists,
- validate only mutable fields are changed,
- enforce uniqueness if unique fields are updated,
- enforce reference existence if reference fields are updated and
enforce=true, - set
updatedAtandupdatedBy, - increment
versionif optimistic concurrency is enabled, - update indexes for changed indexed fields,
- emit events.
Delete operations MUST support:
- soft delete (default): mark
isDeleted=trueand setdeletedAt - hard delete (optional): only if enabled; MUST delete unique mappings and mark record as deleted; full removal from arrays is NOT REQUIRED in v1 (append-only lists are acceptable, with filtering at read time)
Soft delete affects constraints and counters:
activeCountCMUST decrement on soft delete and increment on restore (if restore is supported).- If
activeIdsCis present, soft delete MUST remove the record ID fromactiveIdsCusing swap-and-pop and MUST clearactivePosCfor that record ID. - If an owner index is present, soft delete MUST remove the record ID from the current owner’s owner index set.
- Uniqueness constraints MUST declare a scope:
active(default; value reusable after soft delete) orallTime(value never reusable once claimed). If scope isactive, unique mappings SHOULD be cleared on soft delete; if scope isallTime, they MUST NOT be cleared.
Transfers are optional per collection (enabled when transferRules is present). If enabled, a transfer operation MUST:
- check transfer access (
owner|allowlist|role), - validate record exists and is not deleted,
- validate
to != address(0), - update
record.ownerfromfromtoto, - update owner index sets (remove from
from, add toto) whenonChainIndexing=true, - set
updatedAtandupdatedBy, - emit transfer events.
Token Host supports several on-chain indexes to make the app usable without an off-chain indexer:
On-chain indexes materially increase write gas costs. Token Host MUST support a schema-level “Lite mode” switch (e.g., app.features.onChainIndexing=false) that disables optional on-chain query indexes. When Lite mode is enabled:
- the contract MUST still support CRUD by id,
- the generator MUST omit optional query indexes (owner index, equality indexes, reverse-reference indexes),
- uniqueness constraints MAY remain on-chain (they are integrity constraints, not query acceleration),
- list/search UX SHOULD rely on the indexer when present,
- the generated UI MUST clearly communicate when an indexer is required for browsing/search.
Owner index (when onChainIndexing=true)
For each collection C:
ownedIdsC: mapping(address => uint256[])list of active record IDs currently owned by an address.ownedPosC: mapping(address => mapping(uint256 => uint256))mapping(owner, id) -> position+1inownedIdsC[owner](0 means “not present”), enabling O(1) removal via swap-and-pop.
Owner index maintenance MUST:
- add the record ID to
ownedIdsC[owner]at create, - move the record ID between owner sets on transfer (if enabled),
- remove the record ID from the owner set on soft delete (and add it back on restore if supported).
The generator MUST NOT expose an unbounded “return the full array” getter for this index. Any owner-index accessor MUST be paginated (offset + limit) and MUST cap limit.
Unique index
For each unique constraint on field f:
uniqueC_f: mapping(bytes32 => uint256)mapshash(value)to record ID.- Update MUST clear old hash mapping if the value changes.
- If the uniqueness scope is
active, soft delete SHOULD clear the mapping so the value becomes reusable among active records. If the scope isallTime, soft delete MUST NOT clear the mapping.
Equality index (secondary index; when onChainIndexing=true)
For each indexed field f:
indexC_f: mapping(bytes32 => uint256[])mapping ofhash(value)to append-only list of record IDs.- If a record’s field changes, the record ID MAY appear in multiple buckets historically; readers MUST validate current field value when interpreting results.
- Index accessors MUST be paginated (offset + limit) and MUST cap
limit.
- Index accessors MUST be paginated (offset + limit) and MUST cap
Tokenized index (secondary index; when onChainIndexing=true)
For each tokenized indexed field f:
indexC_f: mapping(bytes32 => uint256[])mapping of normalized token key to append-only list of candidate record IDs.- Tokenized indexes MUST apply deterministic extraction and normalization rules defined by the configured tokenizer.
- In v1, the only supported tokenizer is
hashtag:- source field type MUST be
string, - tokens begin with
#, - the token body is limited to ASCII letters, digits, and underscore,
- normalized token content is lowercased ASCII without the leading
#, - empty markers (e.g. a bare
#) MUST be ignored, - duplicate tokens from the same write MUST be de-duplicated before bucket append.
- source field type MUST be
- If a record’s field changes, the record ID MAY appear in multiple token buckets historically; readers MUST validate current field value when interpreting results.
- Token extraction during create/update MUST be bounded. In v1, a safe default is:
- maximum extracted tokens per indexed field:
8 - maximum normalized token length:
32bytes - writes that exceed those bounds MUST revert.
- maximum extracted tokens per indexed field:
- If chain config provides stricter tokenized-index guidance (for example
limits.indexing.tokenized.maxTokensandlimits.indexing.tokenized.maxTokenLength), the generator SHOULD use those values or stricter values. - Index accessors MUST be paginated (offset + limit) and MUST cap
limit.
Reference reverse index (when onChainIndexing=true)
For reference fields ref:
refIndexC_ref: mapping(uint256 => uint256[])mapping of referenced ID to append-only list of record IDs.- Index accessors MUST be paginated (offset + limit) and MUST cap
limit.
- Index accessors MUST be paginated (offset + limit) and MUST cap
Index accessor semantics (normative)
- Index accessors MUST return candidate IDs only. Callers MUST validate record existence, deletion status, and current field values via
getC/existsCwhen correctness matters. - For append-only equality/reference indexes, ordering MUST be insertion order (oldest to newest) within the bucket.
- For append-only tokenized indexes, ordering MUST be insertion order (oldest to newest) within the bucket.
- For swap-and-pop sets (e.g., owner index sets), ordering MUST NOT be relied upon.
- Index accessors MUST be paginated and MUST cap
limitto avoid RPC timeouts.
Indexes require a stable key derivation so that:
- the UI can compute lookups deterministically,
- indexers can mirror index behavior off-chain,
- upgrades do not silently change semantics.
Token Host MUST derive index keys as follows:
- For
string/image:key = keccak256(bytes(value)) - For
uint256,int256,decimal(scaleduint256),bool,address,bytes32,reference(uint256):key = keccak256(abi.encode(value))
This produces a uniform bytes32 key space.
Normalization note: Token Host does not implicitly normalize string values on-chain. If an app requires case-insensitive uniqueness or trimmed matching, the schema/UI MUST enforce canonicalization by storing the canonical form as the field value.
For tokenized hashtag indexes:
- the lookup input is the normalized token body without the leading
#, - normalization MUST lowercase ASCII
A-Ztoa-z, - only ASCII letters, digits, and underscore are preserved in the token body,
- the key is
keccak256(bytes(normalizedToken)).
This allows generated UIs, self-hosted runtimes, and external indexers to derive the same lookup key without replaying contract internals.
Events are the primary integration surface for indexers and external analytics.
Token Host MUST emit, per collection:
RecordCreated(collectionId, recordId, actor, timestamp, dataHash)RecordUpdated(collectionId, recordId, actor, timestamp, changedFieldsHash)RecordDeleted(collectionId, recordId, actor, timestamp, isHardDelete)RecordTransferred(collectionId, recordId, fromOwner, toOwner, actor, timestamp)(only for collections with transfers enabled)
dataHash and changedFieldsHash SHOULD be keccak256 of ABI-encoded values to allow indexers to detect mismatches without storing full payloads in events. In v1, changedFieldsHash MAY carry the post-update record hash rather than a minimal delta-only hash, so long as the generator applies the rule deterministically and documents it. Token Host MAY additionally emit field-level events for frequently queried fields.
To support real-time UIs without “event storms,” Token Host-generated contracts MUST structure events so that clients can subscribe narrowly using topic filters.
In Solidity:
- For
RecordCreated,RecordUpdated, andRecordDeleted, the generator MUST markcollectionId,recordId, andactorasindexedevent parameters. - For
RecordTransferred, the generator MUST markcollectionId,fromOwner, andtoOwnerasindexedevent parameters, and MUST includerecordIdas a non-indexed parameter in the event data.
Rationale:
- Collection-scoped pages can filter by
collectionId. - Record detail pages can filter by
recordId(for create/update/delete). - “My records” pages can filter membership changes by subscribing to
RecordTransferredwherefromOwner==meortoOwner==meand parsingrecordIdfrom event data.
Token Host MUST avoid unbounded loops in state-changing methods.
- Create/update/delete/transfer MUST NOT iterate over unbounded arrays.
- Append-only index lists and swap-and-pop set maintenance are permitted to keep writes O(1).
- Read/list methods may iterate up to
count(bounded) and MUST cap output size.
Gas economics note:
- On-chain storage (especially
string) and on-chain index maintenance can be expensive. Token Host SHOULD be treated as L2-first for practical CRUD workloads. - Studio MUST surface cost/scaling warnings when schemas imply heavy storage or indexing, and when users target chains with high calldata/storage costs.
- Token Host Chain (where offered) SHOULD be the default target for full-mode apps because it is operated and priced for “EVM as database” workloads.
For high-scale apps (or Lite mode), Token Host SHOULD recommend enabling the indexer and using events as the canonical query surface.
A generated Token Host app is intended to expose a small, stable on-chain surface for UIs and indexers. In single-contract mode that surface is one App contract; in modular mode it is an App deployment composed of one or more contracts described by the release manifest. To keep ABIs stable and UIs simple, Token Host generates typed functions per collection rather than an untyped “bytes payload” generic interface.
For each collection Candidate, the generator MUST emit an external API that, at minimum, enables:
- create a record,
- fetch a record by id,
- list record IDs (and optionally summaries),
- (if configured) update a record,
- (if configured) delete a record,
- (if configured) transfer a record to a new owner,
- (if configured) lookup by unique field(s),
- (if configured and
onChainIndexing=true) list by reference field(s), - (if configured and
onChainIndexing=true) list record IDs by owner (record.owner).
The exact Solidity signatures are generator-defined but MUST follow these rules:
- Create/update functions MUST accept parameters for required/mutable user fields (plus any required reference IDs).
- Create functions MUST return the new record ID (
uint256). getC(id)MUST return a view struct that includes system fields and user fields (in schema order).listIdsC(...)MUST return IDs only (not full record structs).- If
listSummariesC(...)is emitted, it MUST return only system fields plus configured summary fields. - Any function that returns a dynamic array MUST be paginated (offset + limit or cursor + limit) and MUST cap
limit. The generator MUST NOT emit unbounded “return all IDs/records” accessors. - All write functions MUST revert (not silently fail) on access violations and constraint violations.
In addition to per-collection CRUD, the generated App contract MUST include a batching primitive:
multicall(bytes[] calldata calls) external returns (bytes[] memory results)- MUST execute each call against the same contract (no arbitrary external calls),
- MUST preserve
_msgSender()for access checks (delegatecall-based pattern), - MUST revert the entire batch if any call fails (atomicity),
- MUST cap the number of calls per batch to a safe maximum (recommended source:
limits.multicall.maxCallsin chain config, or a conservative generator default).
For v1, multicall SHOULD be non-payable. Paid create operations (those requiring a native fee) SHOULD be executed as single direct calls (not inside multicall) unless a future multicall-with-value design is introduced.
Token Host MAY emit both:
- generic events (
RecordCreated,RecordUpdated,RecordDeleted) for uniform indexing, and - per-collection typed events (
CandidateCreated, …) for convenience.
Inside events and manifests, Token Host needs a stable way to refer to collections.
Token Host MUST define a stable collectionId for each collection. In v1, collectionId SHOULD be:
bytes32 collectionId = keccak256(bytes(collectionName))
This allows indexers and external tools to identify collections without relying on ordering. The generator MUST record collectionName -> collectionId in the build manifest.
When a collection uses allowlist or role access modes, the generated contract(s) MUST include administrative functions to manage membership.
Deployment-time admin and treasury
- Deployments MUST specify an
adminAddressthat receivesDEFAULT_ADMIN_ROLE(or equivalent) for any AccessControl/allowlist management. - If paid creates are enabled for any collection, deployments MUST also specify a
treasuryAddressthat is the default recipient of withdrawn native fees. - In managed deployments, Token Host infrastructure MAY submit the deployment transaction, but Token Host MUST set
adminAddress/treasuryAddressto customer-selected addresses and MUST NOT retain on-chain admin privileges by default.
If role access is used, Token Host SHOULD generate per-collection per-operation roles such as:
CANDIDATE_CREATE_ROLECANDIDATE_UPDATE_ROLECANDIDATE_DELETE_ROLECANDIDATE_TRANSFER_ROLE(only for collections with transfers enabled)
If allowlist access is used, Token Host SHOULD generate per-collection per-operation allowlists such as:
candidateCreateAllowlist[address] => boolcandidateUpdateAllowlist[address] => boolcandidateTransferAllowlist[address] => bool(only for collections with transfers enabled)
To avoid ambiguity, “owner” access MUST always be defined as “record.owner == _msgSender()”.
dataHash / recordHash values exist to provide an integrity primitive for indexers and auditors. Hashes MUST be computed deterministically from the record’s canonical ABI encoding.
In v1, Token Host SHOULD compute:
recordHash = keccak256(abi.encode(collectionId, recordId, systemFields..., userFieldsInSchemaOrder...))
This design ensures:
- identical records across environments produce identical hashes,
- indexers can recompute the hash from decoded record values to detect RPC inconsistencies.
For update events, the generator MAY place this post-update recordHash into the changedFieldsHash event slot in v1. That preserves a stable event surface while still giving indexers an integrity primitive tied to the resulting stored record state.
Generated contracts MUST use explicit, distinguishable reverts. For gas efficiency, Token Host SHOULD prefer Solidity custom errors over string revert reasons.
At minimum, generated contracts SHOULD expose the following error classes:
- unauthorized access (create/read/update/delete/transfer)
- record not found
- record deleted (soft-deleted record accessed without includeDeleted)
- unique constraint violation
- invalid reference (referenced record missing or deleted when enforce=true)
- invalid payment (incorrect
msg.valuefor paid creates) - transfer disabled (transfer attempted when not enabled)
- invalid transfer recipient (
to == address(0)) - version mismatch (optimistic concurrency)
- invalid pagination (limit too large, offset out of range)
The build manifest SHOULD include an “error catalog” mapping logical error names to selectors for external tooling.
The generated App contract MUST expose enough metadata for UIs and indexers to self-configure:
- schema hash (bytes32)
- schema version string
- app slug (string)
- list of collections (names and ids) or an equivalent manifest link in off-chain metadata
Token Host MAY also publish the schema and manifest at a stable URL and include that URL in contract storage for discoverability.
The hosted UI is a static site (Next.js export) that reads a public, immutable manifest and then connects to the chain via wallet provider or RPC.
The manifest MAY include multiple deployments (primary + legacy) to support data continuity across schema versions. The generated UI MUST be able to render legacy deployments as read-only data sources as described in Section 6.9.2.
The UI MUST:
- support wallet connection (EIP-1193 providers),
- detect chain mismatch and guide network switching,
- perform CRUD operations with clear error handling,
- display build/version metadata (schema version, deployed address).
For each collection, Token Host MUST generate:
- list page with pagination,
- detail page,
- create page/form,
- (if enabled) edit page/form,
- (if enabled) delete UI with confirmation,
- (if enabled) transfer UI (set new owner) with confirmation.
List pages MUST avoid oversized on-chain reads. Without an indexer, the generated UI SHOULD render list pages by calling listIdsC and then fetching details via multicall(getC) or listSummariesC when available. Pagination SHOULD be cursor-based using cursorIdExclusive (e.g., pass the smallest ID from the prior page as the next cursor).
For unique fields, the UI SHOULD offer:
- “lookup by unique field” page (e.g.,
/candidate/by-handle/<handle>), backed by on-chain unique mapping or indexer query.
For reference fields, the UI SHOULD offer:
- “view related records” pages (reverse lookup).
Uploads are off-chain but referenced on-chain as strings (URLs or CIDs).
In managed mode, Token Host MUST provide:
- signed upload URLs for assets (S3/GCS or equivalent),
- content-type restrictions,
- size limits,
- malware scanning or basic file checks,
- stable public URLs for consumed assets.
Token Host MAY offer optional IPFS pinning and store returned CID URLs.
When an indexer is enabled, the UI SHOULD:
- use indexer queries for list/search,
- use on-chain reads for detail verification or fallback.
If the indexer is unavailable, the UI MUST remain functional via on-chain reads (with reduced features).
Token Host-generated UIs must feel “product-grade” even when the underlying data source is a blockchain.
At minimum, generated apps MUST include:
- clear loading states for list/detail queries,
- explicit empty states (no records yet),
- actionable error messages for common failures (user rejected signature, wrong network, RPC unavailable, unique constraint violation),
- transaction status UI (submitted, pending, confirmed, failed),
- optimistic UI patterns only when correctness is preserved (e.g., display “pending” record until confirmed).
If a collection has paid creates, the UI MUST:
- display the required create fee alongside gas estimates before submission,
- submit the transaction with the correct native value,
- clearly distinguish “fee” from “gas”.
If a collection has transfers enabled, the UI MUST:
- render a “Transfer” action only when the user is authorized,
- validate destination addresses client-side (basic checks) and require confirmation,
- surface transfer history from events when an indexer is available (optional but recommended).
Generated apps SHOULD include:
- pagination controls that preserve URL state (shareable links),
- deep links for record detail pages,
- safe rendering of user-provided content (escape HTML, prevent injection),
- accessibility considerations (keyboard navigation, aria labels, color contrast).
Token Host provides a template system to balance “no-code defaults” with customization:
- In managed mode, users MAY select from vetted templates and themes.
- In export/self-host mode, developers MAY override templates and add custom components.
Template customization MUST NOT allow arbitrary server-side code execution in Token Host infrastructure. Managed templates must be declarative (e.g., theme tokens, layout choices, component selection) rather than arbitrary code.
If an app has deployments on multiple chains, the manifest MUST enumerate them and MUST identify a primary deployment for writes (plus optional legacy deployments for continuity). The generated UI MUST:
- use the primary deployment as the default write target,
- detect the user’s current wallet network and guide network switching when the user attempts a write,
- make it clear which chain is “primary” vs “legacy” when legacy deployments exist (migration-aware UX),
- allow read-only browsing via public RPC even without a wallet (optional but recommended).
When a release includes multiple deployments (primary + legacy), the generated UI MUST use consistent, explicit terminology so users understand where reads and writes occur.
The generated UI MUST:
- label the active write target as “Primary chain”,
- label any historical read sources as “Legacy chain” (or “Legacy chains”),
- treat legacy deployments as read-only by default and communicate this clearly.
Required action terminology:
- Any UI action that creates a new record on the primary chain based on a legacy record MUST be labeled “Copy record”.
- The UI MUST NOT label this action as “move”, “migrate”, or “transfer” unless it actually preserves identity and mutates the source (which v1 does not).
Required provenance display:
- If a record on the primary chain is known to have been copied from a legacy deployment, the UI MUST display provenance in a human-readable way using the term “Copied from”.
- Provenance SHOULD include at minimum: source chain name + chainId, source deployment entrypoint address, source collection name, and source recordId.
Recommended default English copy (illustrative):
- “Primary chain: ()”
- “Legacy chain (read-only): ()”
- “Copy record: Create a new record on the primary chain based on this legacy record. The new record will have a new ID.”
- “Copied from () / / #”
Token Host-generated apps SHOULD feel real-time by responding to on-chain events.
- Generated apps MUST treat chain state as authoritative and MUST respect finality guidance from the selected chain config (e.g.,
finality.recommendedConfirmations). - Generated apps MUST distinguish:
- an observed head (latest block the client has seen), and
- a safe head (observed head minus confirmations, or a chain-provided
safe/finalizedtag when available).
- By default, generated apps SHOULD use events as refresh triggers and SHOULD refetch list/detail state at the safe head rather than applying unfinalized event payloads directly. The UI MAY render optimistic/pending states for user-submitted transactions, but MUST reconcile them to chain state at the safe head.
- If the UI renders unconfirmed changes (optimistic or observed-head updates), it MUST label them as Pending and MUST treat them as non-final until they reach the safe head.
- Generated apps MUST assume that websocket subscriptions can disconnect and MUST implement reconnection + resync logic.
- Generated apps MUST handle reorgs correctly when the underlying client library surfaces removed logs (or when receipts disappear). Where “removed logs” are not available, the UI MUST tolerate inconsistencies by verifying record state on-chain at the safe head.
When the selected chain config includes an RPC endpoint that supports websocket log subscriptions (eth_subscribe) (recommended signal: rpc.endpoints[].capabilities.subscriptions=true on a wss:// endpoint), the generated app SHOULD subscribe to events as narrowly as possible to avoid event storms.
Normative requirements:
- Subscriptions MUST be scoped to the active deployment entrypoint contract address(es) (primary + any legacy deployments being actively read in the current UI context).
- Subscriptions MUST use topic filters over
indexedevent parameters where feasible (see Section 7.9.1), so that only the currently viewed collection(s) and/or record(s) are subscribed. - In v1, the UI MUST NOT subscribe to
RecordUpdatedfor an entire collection. Preferred patterns:- Collection list pages: subscribe to
RecordCreatedandRecordDeletedfor that collection. (Row-level updates on list pages are refreshed on user demand or via periodic refresh; they are not pushed by default.) - Record detail pages: subscribe to
RecordUpdatedandRecordDeletedfor that(collectionId, recordId). - “My records” pages: subscribe to
RecordCreatedfiltered byactor==meandRecordTransferredfiltered byfromOwner==meortoOwner==me, and refresh membership lists on demand.
- Collection list pages: subscribe to
- Subscriptions MUST be attached only while a view is active and MUST be cleaned up on navigation (unsubscribe) to avoid accumulating subscriptions.
- The UI MUST debounce refresh/invalidation work under high event rates (recommended default: 250ms, or
limits.realtime.recommendedDebounceMsif present in chain config). - The UI MUST cap concurrent websocket subscriptions. If
limits.realtime.maxActiveSubscriptionsis present in chain config, the UI MUST NOT exceed it; otherwise, the UI SHOULD keep active subscriptions under 8. If subscription caps are exceeded or the provider errors, the UI MUST degrade by unsubscribing from the least-critical subscriptions (e.g., broad list subscriptions) and relying on polling.
If websocket log subscriptions are not available, the generated app MUST fall back to polling (e.g., periodic block number checks + targeted refetches). Polling SHOULD use a conservative default interval (e.g., 10s) or limits.realtime.recommendedPollingIntervalSeconds when present in chain config.
Real-time updates change what “page 2” means. Token Host does not provide snapshot isolation for list pages by default.
- The generated UI MUST treat list views as live: results MAY change over time as new events arrive.
- To avoid disruptive UI jumps, when a user is not on the “top” of a list (e.g., page > 1 or scrolled down in an infinite list), the UI SHOULD avoid inserting/removing rows automatically and SHOULD instead display an “Updates available” affordance that the user can click to refresh.
- Record detail pages SHOULD refresh in place when the record is updated or transferred (subject to finality display).
- The UI SHOULD show a “Live” indicator and SHOULD expose a user-facing refresh control.
If an indexer is enabled, the UI SHOULD still use chain events (or polling) as the realtime trigger, then re-query the indexer for list/search views and use on-chain reads for detail verification and fallback.
To keep UX honest when the indexer lags:
- Token Host-hosted indexer/gateway responses MUST include an
indexedToBlockwatermark (and MUST include the correspondingchainId). - In v1, Token Host-hosted indexer/gateway APIs SHOULD expose this metadata using a consistent JSON envelope shape:
{
"data": { },
"meta": { "chainId": 1, "indexedToBlock": 12345678 }
}- When using the indexer as a list source, the UI MUST surface indexer freshness (e.g., “Indexer synced to block N”) and SHOULD surface lag relative to the observed head.
- When a chain event arrives for block
Band the indexer watermark is< B, the UI SHOULD NOT thrash by repeatedly refetching the same list query; it SHOULD instead show “Updates pending indexer sync” and MAY poll until the indexer watermark reachesBbefore refetching.
Token Host accounts are used to access Studio and manage apps. Wallet addresses are used to sign on-chain transactions in generated apps.
Token Host MUST support linking wallet addresses to Token Host accounts to provide richer profile displays, but the generated app MUST NOT require Token Host login to function.
However, generated apps MAY optionally offer Token Host login (OAuth) as a convenience layer (e.g., for profiles, notifications, or optional gas sponsorship/session UX), as long as the app remains usable with direct wallet connections.
Studio MUST support:
- email/password,
- OAuth (Google, Facebook),
- wallet signature login (nonce-based).
Wallet signature login MUST:
- issue a one-time nonce,
- verify signature server-side,
- rotate nonce on success,
- enforce rate limits.
Token Host SHOULD provide a public, privacy-respecting profile endpoint:
GET /v1/profiles/<address>returns a minimal public profile only if the user opted in.
Profile scraping and correlation are real risks. Therefore:
- The public profile response MUST NOT include email addresses, OAuth identifiers, or other sensitive account metadata.
- Public profile fields MUST be explicitly opt-in and SHOULD default to empty/hidden.
- Token Host SHOULD return only a minimal safe set by default (e.g.,
displayName,avatarUrl) and require explicit per-field toggles for anything beyond that. - Token Host SHOULD rate limit this endpoint and monitor abuse.
Editing profile data MUST require authentication and explicit consent for public display. Extended profile data (if any) MUST require authenticated access with explicit scopes and MUST NOT be world-readable by default.
EIP-7702 is an emerging account abstraction primitive that enables an EOA to delegate execution to contract logic via explicit authorization. The practical effect is that an EOA can temporarily behave more like a smart account: executing richer logic, batching calls, and enforcing additional authorization layers.
Token Host treats EIP-7702 as an optional enhancement that can reduce UX friction, but it is not a dependency. Token Host apps MUST remain fully usable with normal EOA transactions (direct wallet calls to the generated App contract).
Because ecosystem support varies and the EIP may evolve, Token Host MUST gate EIP-7702 features behind:
- explicit chain capability detection, and
- explicit user opt-in per app.
Token Host clients (Studio and generated apps) MUST:
- detect whether the connected chain supports delegation,
- explain what enabling delegation does at a high level,
- require explicit user consent before enabling delegation behaviors,
- provide a clear “disable/revoke” path (at minimum, by letting delegation expire and/or by changing delegation state).
When available, Token Host MAY use EIP-7702 to implement:
-
Safe batching
Token Host-generated UIs commonly perform multi-step workflows (e.g., create a record, then update a secondary field, then link a reference). Without batching, these steps require multiple transactions and can leave partial state if users abandon the flow. Token Host MUST provide a contract-level batching primitive (multicall) so standard wallets can batch calls today. Where EIP-7702 is available, delegation MAY further improve batching UX and policy enforcement. -
Policy enforcement at the account layer
Delegation logic can enforce additional constraints before calling the App contract (e.g., method allowlists, spend limits, session expiry). This is particularly valuable when Token Host adds optional “session” features.
Token Host SHOULD design these features so that the App contract remains the canonical storage and rule engine. Delegation logic must be an authorization/convenience layer, not the sole guardian of correctness.
EIP-7702 alone does not guarantee “gasless” transactions on all chains. Gas sponsorship generally requires either:
- chain-native sponsorship mechanisms, or
- an account abstraction/paymaster model (e.g., EIP-4337 or L2-specific equivalents).
On Token Host Chain (if operated by Token Host), Token Host SHOULD treat sponsorship support as part of the “cloud-like” offering:
- provide a standard paymaster/relayer under strict quotas and abuse controls,
- standardize on supported AA primitives for that network (e.g., EIP-4337 and/or EIP-7702 where available),
- enable sponsorship flows that can work for both wallet-connected and Token Host login (OAuth) onboarding, without requiring Token Host to custody user private keys.
Token Host MAY offer gas sponsorship only where the underlying chain infrastructure supports it. If offered, it MUST be:
- rate-limited,
- explicitly disclosed to the user,
- protected against abuse (per-user quotas, per-app quotas, risk scoring),
- implemented without user private-key custody.
Token Host MAY support “session keys” as an opt-in feature where:
- a user creates a short-lived session authorization with least privilege,
- subsequent app actions can be authorized without repeating full wallet flows.
Implementation is chain-dependent. Token Host SHOULD implement session keys using established account abstraction patterns (e.g., smart-account validation, scoped permissions, nonces, expiry) and MAY leverage EIP-7702 where it meaningfully reduces per-user deployment friction.
Session keys MUST:
- be time-bounded (TTL),
- be scope-bounded (contract + method selectors + value limits),
- be revocable by expiry and by explicit user action when possible,
- never require Token Host to store the user’s private key.
If Token Host ships delegation-enabled UX, it SHOULD use audited, reusable “kernel” logic rather than generating bespoke delegation code per app. Any kernel/manager contracts MUST:
- be versioned,
- be independently auditable,
- be included in the manifest and verification workflow,
- have a clear upgrade story (ideally deploy-new-version, not mutable upgrades).
Delegation expands the account attack surface. Token Host MUST treat delegation features as security-sensitive and MUST:
- include replay protection (nonces),
- prevent confused-deputy flows (strict allowlists),
- require explicit consent and clear UI,
- include monitoring and incident response for relayer/paymaster abuse if applicable.
If delegation is unavailable, disabled, or fails:
- the UI MUST fall back to direct contract calls,
- batch/session UX MUST degrade gracefully (more prompts, more transactions),
- the user MUST not be blocked from using the app.
A build takes an immutable schema version and produces:
- generated Solidity source,
- compiled artifacts (ABI, bytecode),
- generated UI bundle,
- signed manifest.
Build steps:
- validate schema (structural + semantic lint),
- generate contracts and UI,
- compile contracts with pinned toolchain,
- run unit tests and snapshot/golden tests,
- package UI (static export),
- write artifacts and manifest to the artifact store.
Deployment steps:
- select chain config from registry,
- select deployment-time parameters (at minimum:
adminAddress; andtreasuryAddressif paid creates are enabled), - deploy contract(s),
- verify contract source on explorer and/or Sourcify,
- update manifest with deployed addresses, deployment parameters, and chain metadata.
In managed deployments, Token Host infrastructure MAY submit deployment transactions, but MUST set adminAddress/treasuryAddress to customer-selected addresses and MUST NOT retain on-chain admin privileges by default.
Publishing steps:
- select a build + deployment set,
- publish UI bundle to hosting,
- map
https://<slug>.apps.tokenhost.com(and optional custom domain) to the release, - expose release metadata publicly (manifest URL).
The manifest MUST include:
thsVersion, schema hash, and schemaVersion,- generator version,
- Solidity compiler version and settings,
- canonical artifact digests for generated sources, compiled artifacts (ABI/bytecode), and UI bundle,
- per-chain deployments including a canonical
deploymentEntrypointAddressand the full set of deployed contract addresses (one or many, depending on deployment mode), - deployment-time parameters (
adminAddress,treasuryAddress) per deployment, - per-deployment chain config references/digests (recommended, especially for managed chains),
- release lineage (primary deployment, legacy deployments, and superseded releases where applicable),
- feature flags (indexer enabled, delegation enabled, uploads enabled, on-chain indexing enabled),
- signatures/checksums.
Canonical JSON Schema:
schemas/tokenhost-release-manifest.schema.json
In managed mode, builds and deployments run as asynchronous jobs. Every job MUST have:
- a stable
jobId, - a type (
build,deploy,verify,publish,indexer), - a state machine with explicit states,
- timestamps (createdAt, startedAt, finishedAt),
- an immutable link to inputs (schemaHash, buildId, chainId),
- structured logs and a summarized failure reason on error.
Token Host MUST implement the following job states:
queued: accepted but not startedrunning: worker assigned and executingsucceeded: completed successfullyfailed: permanently failed (with error summary)canceled: canceled by user/admin (best effort)
Studio MUST surface job state changes in real time (polling or streaming) and MUST provide sufficient context for users to remediate failures (e.g., “verification failed: missing explorer API key”, “deploy failed: insufficient funds”).
To keep builds reproducible and auditable, Token Host MUST:
- pin Node.js version, Solidity compiler version, and build tooling versions per generator release,
- run builds in hermetic environments (container images) where inputs are controlled,
- record toolchain digests (container image digest, package lock hashes) in the manifest,
- compute and record canonical artifact digests for all emitted artifacts (Section 11.6.1).
Token Host SHOULD support build caching keyed by (schemaHash, generatorVersion, toolchainDigest) and MUST treat caches as an optimization only (never a source of truth).
All digests recorded in Token Host manifests and chain config artifacts MUST be expressed as sha256:<lowercase-hex>.
Token Host MUST compute digests as follows:
- File digest:
sha256(fileBytes). - Directory digest (e.g., UI bundle directory):
- recursively list all regular files,
- compute
sha256:<hex>for each file, - build a JSON object
{ "version": 1, "files": [{"path": "...", "digest": "sha256:..."}, ...] }wherepathuses/separators, - sort
filesbypathascending (bytewise UTF-8), - canonicalize the JSON using RFC 8785 and hash it with SHA-256.
The manifest MUST record artifact digests for at least:
- generated Solidity sources (directory digest),
- compiled ABI/bytecode bundle (directory digest),
- generated UI bundle (directory digest).
Publishing creates a release pointer for a domain. Token Host MUST support:
- publishing a new release to
https://<slug>.apps.tokenhost.com, - keeping a release history per app,
- rolling back the domain pointer to a previous release.
Rollbacks MUST be domain-pointer changes only (immutable assets remain versioned by hash), and must not mutate historical artifacts.
Token Host’s primary lifecycle includes starting apps on Token Host Chain (full on-chain mode) and later migrating the app to either:
- a Token Host-provisioned dedicated appchain (growth step), and/or
- an external EVM chain for ecosystem alignment or other requirements.
A chain migration is modeled as publishing a new release that changes the primary deployment used for writes, while retaining prior deployments as legacy read sources for continuity.
In v1:
- Chain migration MUST be supported as an EVM-to-EVM workflow (Token Host Chain -> an EVM appchain / external EVM chain such as an OP Stack chain or other L2/appchain).
- Non-EVM migrations that do not preserve EVM semantics (e.g., to CosmWasm) are out of scope and require a different contract backend. EVM-based chains in other ecosystems are in-scope if they can run the generated contracts without semantic changes.
Migration steps (conceptual):
- deploy the target deployment on the destination chain (same schema version or a newer schema version),
- (optional) provision/update indexers to cover the destination chain and any legacy chains required for read continuity,
- publish a new release where the destination deployment is
role=primaryand previous deployments arerole=legacy, - (optional) run an explicit state copy job that replays/copies selected legacy records into the new deployment.
State copy MUST be explicit and MUST NOT be implied by simply publishing a new release.
A state copy job MUST be modeled and audited as a job type (e.g., stateCopy) with:
- immutable inputs (source deployments, destination deployment, selected collections/records),
- a defined snapshot boundary for reads (source chain block number + required confirmations),
- batching parameters (max records per batch, max gas per batch),
- an operator identity (who initiated and who paid gas, if applicable),
- resumability (job can restart without duplicating destination records).
State copy output MUST include an exportable, signed report artifact containing at minimum:
- source and destination deployment identifiers (chainId + deployment entrypoint address),
- snapshot block numbers used for each source chain,
- counts of records attempted/copied/skipped/failed per collection,
- a provenance map from
(sourceChainId, sourceDeploymentEntrypointAddress, collectionId, sourceRecordId)->(destChainId, destDeploymentEntrypointAddress, collectionId, destRecordId), - failure reasons for any failed copies.
- The destination release manifest SHOULD reference the report artifact by URL and digest (e.g.,
migrations.stateCopyReports[]).
If state copy is performed:
- copied records MUST be treated as new records with new IDs on the destination chain,
- the UI MUST preserve provenance using the “Copied from” language (Section 8.8),
- access control and ownership MUST be validated on the destination chain at the time of copying (no blind imports).
Cost note: migrating full historical state from a cheap chain to an expensive chain may be cost-prohibitive. Studio MUST surface this risk and SHOULD encourage migration strategies that preserve legacy data via UI aggregation where feasible.
Token Host MAY offer “appchains” as a managed service: dedicated EVM rollup/appchains provisioned per app or org, intended as a growth step from the shared Token Host Chain.
Appchain provisioning MUST be modeled as an auditable job with explicit inputs and outputs (similar to build/deploy jobs). At minimum, provisioning MUST produce:
- chain identity:
chainId, human-readable name, network type, - RPC endpoints (public + optionally private),
- explorer endpoint(s) (if provided),
- canonical bridge/entry points (if applicable),
- AA/sponsorship capability flags (if applicable),
- signed chain configuration artifact that can be exported/self-hosted.
Token Host MUST treat “appchain” as an infrastructure choice, not a schema/contract change:
- the same schema version MUST be deployable onto Token Host Chain or an appchain,
- the UI/manifest model (primary + legacy deployments) MUST remain the mechanism for migrating the primary write target.
Progressive decentralization expectations:
- Token Host SHOULD provide a clear staged posture for appchains (e.g., “managed”, “co-managed”, “decentralized”) and MUST communicate what each stage means in operational and trust terms.
- Moving between stages MUST NOT require changing application schemas; it is a chain operation and governance operation.
- Token Host MUST preserve verifiable audit logs for chain lifecycle actions (provision, config changes, governance changes, upgrades) and MUST provide exportable evidence (signed artifacts and/or on-chain references).
Safety constraints:
- Appchains MUST have an explicit upgrade/maintenance policy for the chain stack itself (separate from app contract immutability), and Studio MUST distinguish “chain upgrade” from “app schema upgrade”.
- Token Host MUST provide an exit story for users and developers where feasible (e.g., the ability to migrate the app to another EVM chain and keep legacy reads available).
The CLI is the supported way to run the same pipeline locally or in CI.
Required commands:
th init: create a new app workspaceth validate: validate schemath generate: generate contracts and UIth build: compile + package, output manifestth deploy: deploy to a chain (BYO key) and update manifestth migrate-chain: migrate primary deployment to a new chain (deploy + release manifest with legacy deployments)th verify: verify on explorersth indexer: generate/deploy indexer configuration (optional)th migrate: apply schema migrations locallyth doctor: environment checks
Optional commands (chain management):
th chains: list configured chains / show chain detailsth chains add: add an external chain config locallyth chains provision: request provisioning of a Token Host-managed appchain (managed mode)
The CLI MUST be able to export a complete bundle suitable for self-hosting.
Token Host MUST support generated app test scaffolding that can run in downstream app repositories without manual setup.
Minimum emitted scaffold (when enabled):
- contract tests (
tests/contract/*), - UI smoke tests (
tests/ui/*), - generated app CI workflow (
.github/workflows/generated-app-ci.yml).
Rollout phases MUST be:
- scaffold emission,
- contract tests emission,
- UI tests emission,
- generated CI template emission,
- default-on behavior.
Until the default-on gate is met, test scaffold emission MAY remain opt-in (for example th generate --with-tests).
After default-on, CLI MUST provide an explicit opt-out (for example --no-tests).
Default-on gate requirements:
- builder CI includes generated-app verification in the required integration suite,
- canonical generated app coverage is green in CI,
- no open P0/P1 regressions attributable to generated scaffold emission.
Compatibility/deprecation requirements:
- existing generated app repositories MUST NOT be silently mutated by builder releases,
- migration notes for regeneration MUST be published in release notes and docs,
- compatibility aliases for prior behavior (for example
--with-tests) SHOULD be kept for at least two minor releases after default-on.
The Token Host API is the control plane for the managed platform. It is responsible for multi-tenant identity, schema storage, build/deploy orchestration, domain mapping, and artifact discovery.
The API MUST be versioned (e.g., /v1) and MUST be designed so that:
- managed Studio and CLI can call the same endpoints,
- long-running operations are modeled as jobs,
- write operations are idempotent where possible.
- Studio authentication MUST use secure sessions (httpOnly cookies preferred).
- Session cookies SHOULD be scoped to
app.tokenhost.comandapi.tokenhost.com(not.tokenhost.com) to avoid leaking privileged cookies to hosted apps under*.apps.tokenhost.com(or any hosted-app domain). - Mutating endpoints MUST be CSRF-protected (SameSite + CSRF tokens or equivalent).
Wallet signature login MUST be nonce-based and MUST include:
- nonce issuance endpoint,
- signature verification endpoint,
- nonce rotation on success,
- rate limiting and abuse detection.
Token Host is multi-tenant. Every resource belongs to an organization (org).
The API MUST support:
- org creation and management,
- inviting members,
- membership roles with least privilege (e.g., Owner, Admin, Developer, Viewer),
- audit logging of privileged actions.
Authorization rules MUST be applied server-side on every request. UI visibility is not sufficient.
Apps are durable objects with a slug reservation.
The API MUST support:
- creating an app and reserving
app.slug, - listing apps in an org,
- storing schemas as immutable versions,
- marking one schema version as the “default draft” in Studio (optional).
Schema endpoints SHOULD include:
GET /v1/apps/:appId/schemasPOST /v1/apps/:appId/schemas(creates new immutable schema version)GET /v1/apps/:appId/schemas/:schemaId
Builds and deployments are jobs. The API MUST provide:
- job creation endpoints (e.g., request a build),
- job status endpoints (polling),
- job log access endpoints (authorized),
- job cancellation (best-effort).
Example conceptual endpoints:
POST /v1/apps/:appId/builds(body includes schemaId)GET /v1/apps/:appId/builds/:buildIdPOST /v1/apps/:appId/deployments(body includes buildId + chainId)GET /v1/apps/:appId/deployments/:deploymentIdPOST /v1/apps/:appId/releases(body includes primary deployment set + UI bundle + optional legacy deployment references + optional supersedesReleaseId)GET /v1/apps/:appId/releases
The API SHOULD support idempotency keys for build/deploy requests to prevent duplicate work when clients retry.
Domain management MUST include:
- default subdomain mapping for
app.slug, - custom domain creation,
- DNS verification tokens,
- certificate provisioning status.
Example conceptual endpoints:
POST /v1/apps/:appId/domainsGET /v1/apps/:appId/domainsPOST /v1/apps/:appId/domains/:domainId/verify
If uploads are enabled, Token Host MUST provide an upload signing service so browsers can upload directly to object storage without exposing platform credentials.
Example:
POST /v1/uploads/signreturns a short-lived signed URL and required headers/policy.
The signing service MUST enforce content-type and size constraints and SHOULD enforce per-org quotas.
Token Host SHOULD support webhooks for:
- build completion,
- deployment completion,
- publish completion,
- failures (with sanitized error data).
Webhook delivery MUST be signed (HMAC or equivalent) and retried with exponential backoff.
Hosted apps require public access to manifests and (optionally) schemas.
Token Host MUST make release manifests publicly readable at stable URLs (hash-addressed), for example:
GET https://<slug>.apps.tokenhost.com/.well-known/tokenhost/manifest.json
If a schema is intended to be public, Token Host MAY also publish it under:
GET https://<slug>.apps.tokenhost.com/.well-known/tokenhost/schema.json
Publishing these files MUST NOT expose private org data (only the schema and release metadata).
Token Host MUST implement a moderation/takedown control that can unpublish hosted UI for apps that violate policy or present unacceptable risk, without attempting to alter immutable on-chain state.
At minimum, the API MUST support:
- setting an app or release hosting state (
published|unpublished|suspended), - a reason code and audit trail for state changes,
- admin-only enforcement actions.
Example conceptual endpoints:
POST /v1/admin/apps/:appId/takedown(suspend hosting; requires admin)POST /v1/admin/apps/:appId/restore(restore hosting; requires admin)
Token Host’s chain registry and (optional) appchain provisioning capabilities MUST be represented explicitly in the API.
The API MUST support:
- listing supported/public chain configs (including Token Host Chain if available),
- storing org-scoped external chain configs (BYO RPC/explorer keys),
- requesting provisioning of Token Host-managed appchains (where offered),
- tracking chain lifecycle as jobs with audit logs.
Example conceptual endpoints:
GET /v1/chains(public chain registry)POST /v1/orgs/:orgId/chains(add/update org-scoped external chain config)GET /v1/apps/:appId/chains(chains relevant to an app: Token Host Chain, provisioned appchains, user-configured chains)POST /v1/apps/:appId/chains/provision(request a managed appchain; returns a job)GET /v1/apps/:appId/chains/:chainId(chain details and status)
Token Host MUST provide:
- build logs (streaming and stored),
- deployment logs,
- structured audit logs for schema changes, publish actions, domain changes, and chain/appchain lifecycle actions,
- metrics (SLOs for build time, deploy success, hosting availability),
- error reporting for Studio and (optionally) hosted apps.
Token Host’s build pipeline is a high-risk surface because it transforms untrusted schema input into executable artifacts. Token Host MUST treat builds as untrusted workloads and isolate them accordingly.
Requirements:
- Build workers MUST run in isolated sandboxes (container isolation at minimum; stronger isolation preferred for untrusted workloads).
- Dependencies MUST be pinned (lockfiles) and the manifest MUST record dependency versions.
- Token Host SHOULD generate SBOMs (Software Bill of Materials) for releases and store them with artifacts.
- Build workers MUST not have access to long-lived production secrets beyond what is required for the specific job (principle of least privilege).
Studio and hosted apps are web-facing and MUST follow modern web security practices:
- Studio and hosted apps MUST set strict security headers (CSP, HSTS, X-Content-Type-Options, Referrer-Policy, etc.).
- Mutating API endpoints MUST be protected against CSRF.
- User-generated content displayed in hosted apps MUST be safely escaped to prevent XSS.
- Cross-tenant data access MUST be prevented by authorization checks and tenant scoping in the data model.
- CORS policies MUST be explicit and minimal; wildcard credentials MUST NOT be used.
Token Host MUST operate under “no secrets in code”:
- Secrets MUST NOT be stored in source control, templates, or generated output.
- Secrets MUST be stored in a dedicated secret manager.
- Secrets MUST be rotated and auditable.
- Production secrets MUST be scoped per environment (dev/staging/prod) and per service.
Token Host provides public endpoints (auth, builds, uploads) that can be abused. Token Host MUST:
- rate limit auth endpoints (especially nonce issuance and login),
- rate limit build requests per org (quota + burst limits),
- detect and reject abusive schemas (excessive size/complexity),
- rate limit upload signing and enforce storage quotas.
Generated contracts are part of the platform’s security boundary. Token Host MUST:
- avoid insecure attribution patterns (MUST NOT use
tx.origin), - avoid unbounded loops in state-changing functions,
- use explicit access control patterns (OpenZeppelin libraries preferred),
- produce contracts that can pass baseline static analysis (Slither/Semgrep rulesets),
- include invariant and fuzz testing templates for generated contracts.
If Token Host introduces delegation/kernel contracts, those MUST be audited independently from app-specific generated contracts.
If Token Host operates relayers or paymasters:
- relayer keys MUST be stored in HSM/KMS-backed systems,
- relayer operations MUST be rate-limited and policy-controlled,
- relayer actions MUST be attributable and logged,
- compromise of relayer keys MUST not grant control of user-owned assets (only the ability to spend Token Host gas budgets).
If Token Host operates Token Host Chain and/or Token Host-managed appchains, chain operator keys and admin controls are security-critical. Token Host MUST:
- store chain operator keys (sequencer/batcher/bridge/governance keys, as applicable) in HSM/KMS-backed systems,
- use least-privilege separation between operational roles (sequencing vs governance vs deployment tooling),
- require multi-party approval for high-impact actions (chain upgrades, bridge parameter changes),
- maintain immutable, queryable audit logs for chain lifecycle actions (provisioning, upgrades, key rotation),
- publish clear incident-response and rollback procedures for chain incidents.
The system is considered Phase 1 complete when:
- A user can sign into Studio and create an app schema.
- Schema validation rejects invalid schemas with actionable errors.
- A build produces a deterministic manifest and artifacts.
- A deployment to one public testnet succeeds and verifies contracts.
- The app is published at
https://<slug>.apps.tokenhost.com(or a custom domain) and supports basic CRUD. - When the chain supports websocket log subscriptions, the published app reflects on-chain changes in near-real-time (event-driven refresh with safe fallbacks).
- A schema can require a native fee for create on a collection, and the generated UI/contract correctly enforces it.
- A transfer-enabled collection can transfer record ownership via an on-chain transfer method and generated UI.
- Admins can place a published app into a takedown/suspended state that stops serving the user-generated UI under Token Host-controlled domains.
- Export bundle download works and can be self-hosted.
- Optional: indexer integration can be enabled for the example app.
The exact manifest schema is versioned, but a v1 example shape is:
{
"manifestVersion": "1.0.0",
"thsVersion": "2025-12",
"schemaVersion": "1.0.0",
"schemaHash": "sha256:…",
"generatorVersion": "0.1.0",
"toolchain": {
"node": "20.x",
"solc": "0.8.24",
"containerDigest": "sha256:…"
},
"release": {
"releaseId": "rel_…",
"supersedesReleaseId": null,
"publishedAt": "2025-12-27T00:00:00Z"
},
"app": { "name": "Job Board", "slug": "job-board" },
"collections": [{ "name": "Candidate", "collectionId": "0x…" }],
"artifacts": {
"soliditySources": { "digest": "sha256:…", "url": "https://…/sources.tgz" },
"compiledContracts": { "digest": "sha256:…", "url": "https://…/compiled.tgz" }
},
"deployments": [
{
"role": "primary",
"chainId": 11155111,
"chainName": "sepolia",
"deploymentMode": "single",
"deploymentEntrypointAddress": "0x…",
"adminAddress": "0x…",
"treasuryAddress": "0x…",
"contracts": [
{
"role": "app",
"address": "0x…",
"verified": true,
"bytecodeDigest": "sha256:…",
"abiDigest": "sha256:…"
}
],
"verified": true,
"blockNumber": 0,
"chainConfig": {
"url": "https://…/chain-config.json",
"digest": "sha256:…",
"chainConfigVersion": "1.0.0"
}
},
{
"role": "legacy",
"schemaVersion": "0.9.0",
"schemaHash": "sha256:…",
"chainId": 11155111,
"chainName": "sepolia",
"deploymentMode": "single",
"deploymentEntrypointAddress": "0x…",
"adminAddress": "0x…",
"treasuryAddress": null,
"contracts": [{ "role": "app", "address": "0x…", "verified": true }],
"verified": true,
"blockNumber": 0
}
],
"ui": {
"bundleHash": "sha256:…",
"baseUrl": "https://job-board.apps.tokenhost.com/",
"wellKnown": "/.well-known/tokenhost/manifest.json"
},
"features": {
"indexer": false,
"delegation": false,
"uploads": true,
"onChainIndexing": true
},
"migrations": {
"stateCopyReports": [{ "url": "https://…/state-copy-report.json", "digest": "sha256:…" }]
},
"signatures": [{ "alg": "ed25519", "sig": "…" }]
}Token Host-generated apps SHOULD use predictable routes so users can share links:
/home (collection dashboard)/<collection>list/<collection>/newcreate/<collection>/<id>detail/<collection>/<id>/editedit (if enabled)/<collection>/<id>/deletedelete (if enabled)/<collection>/by-<field>/<value>unique lookup (if enabled)/<collection>/by-<ref>/<refId>reverse-reference lookup (if enabled)
Route naming MUST be stable across builds for a given schema version.
This appendix captures optional, future-facing modules that align with Token Host’s “EVM as the backend” philosophy while expanding beyond pure CRUD data into assets and economies. These modules are intentionally opt-in because they increase security, UX, and regulatory complexity.
Token Host collections can represent either:
- data records (identity/profile, logs, posts), or
- assets (tickets, coupons, items) where transferability and wallet-native UX are desirable.
The core spec supports asset-like records via transferRules and the owner system field. A natural extension is to make a collection’s records be NFTs (ERC-721 or ERC-1155) so they integrate with existing wallets, tooling, and marketplaces.
Conceptual schema shape (non-normative):
tokenization(optional):standard:"erc721" | "erc1155"soulbound: boolean (if true, transfers are disabled)metadata:"offchain" | "onchain" | "hybrid"
Normative requirements if tokenization is enabled:
recordIdMUST equaltokenId.- Create MUST mint the token to the initial
owner. - Ownership checks MUST be based on token ownership (
ownerOf(tokenId)/ ERC-1155 balance model) and MUST remain compatible with_msgSender(). - Transfers SHOULD be implemented using the relevant token standard’s semantics (
transferFrom/safeTransferFrom) and MUST update the app’sownerview consistently (either stored as a cached field kept in sync, or derived at read time). - Soft delete MUST have defined behavior:
- either disallow deleting tokenized records,
- or implement “freeze” semantics (record is marked inactive while token still exists),
- or treat hard delete as burning the token (only if explicitly enabled).
Design note:
- Tokenizing everything is not the goal; the goal is to let an app mix data collections and asset collections within the same schema while retaining deterministic generation and migration capabilities.
Some apps want first-class economic primitives (e.g., “pay to post”, “reward contributors”, “stake for access”). Token Host can support this via an optional “app economy” module layered on top of deterministic schema generation.
Scope goals:
- Let app actions be paid for in native currency (already supported for creates) and/or in an app token (ERC-20).
- Optionally reward actions (“farming”) under strict safety constraints.
- Optionally support a developer-funded liquidity bootstrap, recognizing that DEX integrations are chain-specific and must be adapter-based.
Conceptual schema shape (non-normative):
app.economy(optional):token:mode:"external" | "generated"addressByChain: map ofchainId -> tokenAddress(if external)symbol,decimals(display only)
payments:- enable ERC-20 payments in
createRules.payment/updateRules.payment/etc (future) - allow
asset: "native" | "erc20"
- enable ERC-20 payments in
rewards:enabled: booleanfunding:"prefunded"(recommended default)rules: action-based reward rules (create/update/verify/etc) with strict rate limits
liquidity:enabled: booleanbootstrap: explicit operator/dev-driven steps (no implicit swaps)
Safety requirements (if implemented):
- The platform MUST NOT imply that token modules are safe by default; Studio MUST require explicit opt-in and display risk warnings (security, sybil abuse, market risk).
- Rewards MUST NOT be implemented as “mint on every CRUD action” without strict anti-abuse controls; prefunded + claim-based distribution SHOULD be preferred.
- Dynamic pricing and oracle-based conversions SHOULD NOT be part of the initial design; “pay in ETH or pay in token” SHOULD be modeled as explicit fixed-price options per action.
- Token Host MUST NOT require custody of user private keys for economy features. Any relayers/paymasters used for token payments MUST be abuse-controlled.
Relationship to Token Host Chain / OP chains:
- A first-party Token Host Chain can standardize paymaster/AA primitives and token-payment UX, making Web2-like onboarding (OAuth + sponsored actions) more achievable without compromising key custody defaults.