# Adapter Implementation
Source: https://docs.elata.bio/adapter-implementation
Build a device adapter that plugs into BleTransport and emits stable headband frames.
Once your protocol inventory is complete, build the adapter.
The goal is to isolate device-specific BLE and packet logic while preserving the
shared Elata transport contract for apps.
## Recommended Shape
For browser BLE integrations, the adapter should satisfy the `BleDeviceLike`
shape expected by `@elata-biosciences/eeg-web-ble`.
That means the device layer handles discovery, session setup, subscription, and
packet decoding, while `BleTransport` handles the app-facing transport surface.
## Implementation Order
Define the `requestDevice` filters and any optional services required to find
the device cleanly in the browser picker.
Connect to GATT, resolve characteristics, and do any startup writes that are
required before streaming can begin.
Turn vendor packets into `number[][]` where each row is one time step and
the row width matches `numEegChannels`.
Expose `samplingRate`, `eegNames`, `numEegChannels`, and related device
metadata accurately.
Unsubscribe, stop the stream safely, and release the session without leaking
resources.
## Minimal Adapter Skeleton
```ts theme={null}
import type { BleDeviceLike } from "@elata-biosciences/eeg-web-ble";
export class VendorBleDevice implements BleDeviceLike {
isAthena = false;
samplingRate = 250;
eegNames = ["CH1", "CH2"];
numEegChannels = 2;
opticsChannelCount = 0;
getBoardInfo() {
return { device_name: "VENDOR_DEVICE" };
}
getCharacteristicInfo() {
return { characteristics: [] };
}
async prepareSession() {
// Connect to GATT, resolve characteristics, and initialize streaming state
}
async releaseSession() {
// Tear down subscriptions and release the browser session
}
async startStream(eegCb, _ppgCb) {
// Subscribe to notifications and emit decoded EEG rows
eegCb([[0, 0]]);
}
async stopStream() {
// Stop device streaming and unsubscribe
}
}
```
## What the Adapter Must Own
| Responsibility | Adapter owns it? |
| ---------------------------------------- | ------------------------------------- |
| Device discovery | yes |
| GATT connection and characteristic setup | yes |
| Packet decoding | yes |
| Accurate metadata | yes |
| App-facing transport lifecycle | usually through `BleTransport` |
| Normalized frame delivery to apps | through the shared transport contract |
## Packet Decoding Rules
Keep the decoder boring and deterministic.
| Rule | Why it matters |
| ------------------------------------ | ----------------------------------------------- |
| Reject malformed packets early | Prevents bad rows from reaching apps |
| Preserve channel order | Keeps downstream features and models consistent |
| Batch rows only when needed | Avoids hidden latency and timestamp confusion |
| Track packet counters when available | Helps detect drops or corruption |
| Separate EEG from aux logic cleanly | Makes debugging and testing easier |
## Session Lifecycle Rules
Your adapter should make these transitions unsurprising:
| Stage | Good behavior |
| -------- | ----------------------------------------------------- |
| Discover | only matching devices are shown |
| Connect | required services and characteristics resolve clearly |
| Start | stream begins once, without duplicate subscriptions |
| Stop | notifications stop cleanly |
| Release | the session can be started again later |
## Reliability Notes
`startStreaming()` is the safest default for app consumers because it combines
connect and start. Your adapter should still make the underlying `connect()`
and `start()` steps reliable on their own.
If the device exposes auxiliary signals such as PPG, optics, IMU, or battery,
map them only when the semantics are clear. Do not force Muse-specific
assumptions onto non-Muse hardware.
## Related Docs
* [EEG BLE Getting Started](/sdk/eeg-web-ble/getting-started)
* [Transport Contract](./transport-contract)
* [Testing and Validation](./testing-and-validation)
* [Muse Device](/sdk/eeg-web-ble/muse-device)
## Next
Turn the adapter into a reliable transport under real failure conditions.
Package the integration with the docs and caveats other teams need.
# List activity
Source: https://docs.elata.bio/api-reference/apps/activity
/api-reference/openapi.yaml GET /apps/{tokenAddress}/activity
Buys, sells, and lifecycle events for an app, newest first.
**Status: proposed.** This endpoint is designed here but not yet implemented on `app.elata.bio/api`.
# Get app
Source: https://docs.elata.bio/api-reference/apps/get
/api-reference/openapi.yaml GET /apps/{tokenAddress}
Returns detailed information for a single app, including social links, team, and artifacts.
# List holders
Source: https://docs.elata.bio/api-reference/apps/holders
/api-reference/openapi.yaml GET /apps/{tokenAddress}/holders
Paginated list of app token holders, ranked by balance.
**Status: proposed.** This endpoint is designed here but not yet implemented on `app.elata.bio/api`.
# List apps
Source: https://docs.elata.bio/api-reference/apps/list
/api-reference/openapi.yaml GET /apps
Returns all apps with metadata. Supports filtering and cursor pagination.
# Search apps
Source: https://docs.elata.bio/api-reference/apps/search
/api-reference/openapi.yaml GET /apps/search
Full-text search across name, symbol, and description.
**Status: proposed.** This endpoint is designed here but not yet implemented on `app.elata.bio/api`.
# App snapshot
Source: https://docs.elata.bio/api-reference/market/app
/api-reference/openapi.yaml GET /market/apps/{tokenAddress}
Token price, market cap, FDV, 24h volume, holders, and curve progress for a single app.
**Status: proposed.** This endpoint is designed here but not yet implemented on `app.elata.bio/api`.
# OHLCV candles
Source: https://docs.elata.bio/api-reference/market/candles
/api-reference/openapi.yaml GET /market/apps/{tokenAddress}/candles
OHLCV candle series for an app token.
**Status: proposed.** This endpoint is designed here but not yet implemented on `app.elata.bio/api`.
# Bonding curve state
Source: https://docs.elata.bio/api-reference/market/curve
/api-reference/openapi.yaml GET /market/apps/{tokenAddress}/curve
Reserves, current price, slope, and graduation threshold for an app's bonding curve.
**Status: proposed.** This endpoint is designed here but not yet implemented on `app.elata.bio/api`.
# ELTA snapshot
Source: https://docs.elata.bio/api-reference/market/elta
/api-reference/openapi.yaml GET /market/elta
Extended ELTA price snapshot with 24h change and source attribution.
**Status: proposed.** This endpoint is designed here but not yet implemented on `app.elata.bio/api`.
# Leaderboard
Source: https://docs.elata.bio/api-reference/market/leaderboard
/api-reference/openapi.yaml GET /market/leaderboard
Top apps ranked by 24h volume, market cap, or holder growth.
**Status: proposed.** This endpoint is designed here but not yet implemented on `app.elata.bio/api`.
# Get prices (legacy)
Source: https://docs.elata.bio/api-reference/market/prices
/api-reference/openapi.yaml GET /prices
Current ELTA price data used by the App Store UI.
# API Reference
Source: https://docs.elata.bio/api-reference/overview
REST API for the Elata App Store at app.elata.bio.
The Elata App Store exposes a REST API at `https://app.elata.bio/api`. This tab is the source of truth for that surface — every page is generated from a single OpenAPI 3.1 spec at [`api-reference/openapi.yaml`](https://github.com/elata-biosciences/docs/blob/main/api-reference/openapi.yaml).
It is the API surface third-party builders need to discover apps, run launch preflight, render market widgets, show user portfolios, surface claimable rewards, and bootstrap SDKs against the right contracts.
For the higher-level integration narrative (iframe embedding, on-chain reads, transaction patterns), see [Builders → API Overview](/builders/api-overview).
***
## Status legend
Every operation in this reference carries an `x-elata-status`:
| Status | Meaning |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| **stable** | Implemented today on `app.elata.bio/api`. Safe to depend on. |
| **proposed** | Designed in the spec but **not yet implemented**. The spec is the contract the backend will build against. Subject to change. |
| **planned** | Acknowledged but not yet designed. Reserved namespace. |
The currently `stable` operations are:
* `GET /apps`
* `GET /apps/{tokenAddress}`
* `GET /prices`
Everything else in this tab is `proposed`.
***
## Base URL
```
https://app.elata.bio/api
```
***
## Authentication
Most reads are public. Personalized endpoints under **Portfolio** and **Rewards** require a SIWE-issued JWT bearer token.
```http theme={null}
Authorization: Bearer
```
The token is obtained via [Sign-In With Ethereum](https://eips.ethereum.org/EIPS/eip-4361) against `app.elata.bio`. The JWT's `sub` claim is the wallet address; requests whose path `{address}` does not match the `sub` are rejected.
***
## Rate limits
| Endpoint class | Limit |
| -------------- | ----------- |
| Read endpoints | 100 req/min |
| Heavy queries | 10 req/min |
For heavy on-chain reads, bring your own RPC provider in production (Alchemy, Infura, QuickNode).
***
## Sections
Discovery, detail, search, holders, activity.
Validate a launch before sending a transaction.
Prices, candles, bonding-curve state, leaderboards.
Per-wallet holdings, history, P\&L.
Claimable rewards, programs, claim payloads.
Runtime config for SDKs and AI agents.
***
## On-chain interfaces
For Solidity contract interfaces, ABIs, and deployed addresses:
* [elata-protocol](https://github.com/elata-biosciences/elata-protocol) — Solidity source and deployment scripts
* [elata-appstore](https://github.com/elata-biosciences/elata-appstore) — Frontend reference, including ABIs in `src/abi/`
* [Resources → Smart Contracts](/resources/smart-contracts) — current deployed addresses
# Get portfolio
Source: https://docs.elata.bio/api-reference/portfolio/get
/api-reference/openapi.yaml GET /portfolio/{address}
Aggregate portfolio for a wallet: ELTA, veELTA, app token holdings, USD value, and unrealized P&L.
**Status: proposed.** Requires a SIWE-issued bearer token whose `sub` matches `{address}`.
# Portfolio history
Source: https://docs.elata.bio/api-reference/portfolio/history
/api-reference/openapi.yaml GET /portfolio/{address}/history
Trade and transfer history for a wallet, cursor-paginated.
**Status: proposed.** Requires a SIWE-issued bearer token whose `sub` matches `{address}`.
# List positions
Source: https://docs.elata.bio/api-reference/portfolio/positions
/api-reference/openapi.yaml GET /portfolio/{address}/positions
Per-app token positions for a wallet.
**Status: proposed.** Requires a SIWE-issued bearer token whose `sub` matches `{address}`.
# Validate launch
Source: https://docs.elata.bio/api-reference/preflight/launch
/api-reference/openapi.yaml POST /preflight/launch
Validates a candidate app payload against current launch requirements before any transaction is sent.
**Status: proposed.** This endpoint is designed here but not yet implemented on `app.elata.bio/api`.
# Check name
Source: https://docs.elata.bio/api-reference/preflight/name-available
/api-reference/openapi.yaml GET /preflight/name-available
Returns whether a candidate app name is available.
**Status: proposed.** This endpoint is designed here but not yet implemented on `app.elata.bio/api`.
# Get requirements
Source: https://docs.elata.bio/api-reference/preflight/requirements
/api-reference/openapi.yaml GET /preflight/requirements
Current launch requirements (minimum ELTA, fee schedule, image dimensions, metadata schema).
**Status: proposed.** Designed to mirror `/apps/build/launch-requirements` so the live UI and the docs share a single source of truth.
# Check symbol
Source: https://docs.elata.bio/api-reference/preflight/symbol-available
/api-reference/openapi.yaml GET /preflight/symbol-available
Returns whether a candidate app token symbol is available.
**Status: proposed.** This endpoint is designed here but not yet implemented on `app.elata.bio/api`.
# Claude Code
Source: https://docs.elata.bio/apps/ai-tools/claude-code
Use Claude Code to build tokenized apps with the Elata protocol and Biometric SDK.
## Overview
Claude Code can help you build Elata apps by generating frontend code, integrating SDK packages, and interacting with protocol contracts. This guide covers setup and key workflows.
***
## Setup
1. Install Claude Code globally:
```bash theme={null}
npm install -g @anthropic-ai/claude-code
```
2. Navigate to your project directory and run:
```bash theme={null}
claude
```
3. Create a `CLAUDE.md` file in your project root with the context below.
***
## CLAUDE.md Template
Create this file at the root of your project to give Claude Code context about Elata development:
```markdown theme={null}
# Project Context
This is an Elata app built on the Elata protocol.
## Key References
- Protocol docs: https://docs.elata.bio/apps/what-are-elata-apps
- SDK docs: https://docs.elata.bio/sdk/overview
- App store: https://app.elata.bio
## SDK Packages
- `@elata-biosciences/rppg-web` - Camera-based heart rate (primary integration)
- `@elata-biosciences/eeg-web` - EEG signal processing
- `@elata-biosciences/eeg-web-ble` - Web Bluetooth for Muse headsets
- `@elata-biosciences/create-elata-demo` - App scaffolder
## Protocol Contracts
- AppFactory: deploys app token stacks
- AppRegistry: on-chain app metadata
- AppBondingCurve: constant-product price discovery
- FeeRouterV2: fee routing to treasury and contributors
- ContributorSplit: pull-based contributor payouts
## Stack
- Frontend: React + Vite
- Biosignals: Elata SDK (WASM)
- Contracts: Solidity (Foundry)
- Chain: Ethereum / Base
## Writing Standards
- No em-dashes
- Practical, concise code
- Use TypeScript
```
***
## Key Workflows
### Scaffold a New App
Ask Claude Code to scaffold using the Elata CLI:
```text theme={null}
> Scaffold a new rPPG app using create-elata-demo
```
### Integrate the Biometric SDK
```text theme={null}
> Add camera-based heart rate to my app using @elata-biosciences/rppg-web.
> Use createRppgSession with auto backend and diagnostics.
```
### Connect to Protocol Contracts
```text theme={null}
> Add a component that reads the current bonding curve price for my app token.
> Use ethers.js to call AppBondingCurve.getPrice().
```
### Build Tournament UI
```text theme={null}
> Create a tournament listing page that shows active tournaments from the
> TournamentFactory contract and lets users join with their app tokens.
```
***
## Tips
Point Claude Code at the SDK docs (`docs.elata.bio/sdk/overview`) and protocol docs (`docs.elata.bio/apps/build/overview`) when asking it to generate integration code. It produces better results with explicit documentation references.
* Start with `create-elata-demo` to get a working scaffold, then iterate with Claude Code
* Ask Claude Code to explain SDK code before modifying it
* Use the `--template rppg` flag for camera apps, `--template eeg` for EEG apps
***
## Next
What the protocol gives builders
Biometric SDK packages
# Cursor
Source: https://docs.elata.bio/apps/ai-tools/cursor
Use Cursor to build tokenized apps with the Elata protocol and Biometric SDK.
## Overview
Cursor's AI features can accelerate Elata app development. This guide covers project rules setup and key workflows for building with the protocol and SDK.
***
## Setup
1. Install Cursor from [cursor.sh](https://cursor.sh)
2. Open your Elata project
3. Create `.cursor/rules.md` with the template below
***
## Cursor Rules Template
Create `.cursor/rules.md` in your project root:
```markdown theme={null}
# Elata Development Rules
## Project Context
This is an Elata app built on the Elata protocol with biometric SDK integration.
## Key References
- Protocol docs: https://docs.elata.bio/apps/what-are-elata-apps
- SDK docs: https://docs.elata.bio/sdk/overview
- App store: https://app.elata.bio
## SDK Packages
- `@elata-biosciences/rppg-web`: Camera-based heart rate (createRppgSession)
- `@elata-biosciences/eeg-web`: EEG signal processing (initEegWasm, band_powers)
- `@elata-biosciences/eeg-web-ble`: Web Bluetooth headband transport (BleTransport)
- `@elata-biosciences/create-elata-demo`: App scaffolder
## Protocol Architecture
- AppFactory: deploys app token stacks (10M supply, 50/25/25 split)
- AppBondingCurve: constant-product price discovery, graduates at 42k ELTA
- FeeRouterV2: routes fees to treasury (20%) and contributors (80%)
- ContributorSplit: pull-based payout to named contributors
## Code Standards
- TypeScript, React, Vite
- No em-dashes in docs or comments
- Use createRppgSession for browser rPPG (not raw RppgProcessor)
- Use BleTransport.startStreaming() for BLE (not separate connect/start)
- Initialize WASM before using SDK analysis functions
```
***
## Key Workflows
### Scaffold and Extend
Start with a scaffold, then use Cursor to extend:
```bash theme={null}
pnpm create @elata-biosciences/elata-demo my-app -- --template rppg
cd my-app
pnpm install
```
Open in Cursor and ask it to add features on top of the scaffold.
### SDK Integration Patterns
When asking Cursor to integrate the SDK, reference the specific entry points:
* **rPPG:** `createRppgSession({ video, backend: "auto", faceMesh: "off" })`
* **EEG:** `initEegWasm()` then `band_powers(samples, sampleRate)`
* **BLE:** `new BleTransport({ deviceOptions: { athenaDecoderFactory } })`
### Contract Interaction
Ask Cursor to generate contract interaction code by providing the contract name and method:
* Read bonding curve state: `AppBondingCurve.getReserves()`
* Check app status: `AppRegistry.getApp(appId)`
* Join tournament: `Tournament.join(entryFee)`
***
## Tips
Add the Elata SDK docs and protocol docs as documentation sources in Cursor's settings. This improves code generation quality for SDK-specific patterns.
***
## Next
What the protocol gives builders
Biometric SDK packages
# Windsurf
Source: https://docs.elata.bio/apps/ai-tools/windsurf
Use Windsurf to build tokenized apps with the Elata protocol and Biometric SDK.
## Overview
Windsurf's AI coding features work well for building Elata apps. This guide covers workspace rules setup and key workflows.
***
## Setup
1. Install Windsurf from [windsurf.ai](https://windsurf.ai)
2. Open your Elata project
3. Create `.windsurf/rules.md` with the template below
***
## Windsurf Rules Template
Create `.windsurf/rules.md` in your project root:
```markdown theme={null}
# Elata Development Rules
## Project Context
This is an Elata app built on the Elata protocol with biometric SDK integration.
## Key References
- Protocol docs: https://docs.elata.bio/apps/what-are-elata-apps
- SDK docs: https://docs.elata.bio/sdk/overview
## SDK Packages
- `@elata-biosciences/rppg-web`: Camera-based rPPG (createRppgSession)
- `@elata-biosciences/eeg-web`: EEG processing (initEegWasm, band_powers)
- `@elata-biosciences/eeg-web-ble`: Web Bluetooth (BleTransport)
## Protocol Contracts
- AppFactory: deploys token stacks
- AppBondingCurve: constant-product price discovery
- FeeRouterV2: fee routing (80% contributors, 20% treasury)
- ContributorSplit: pull-based contributor payouts
## Standards
- TypeScript, React, Vite
- Use createRppgSession for rPPG (not raw processor)
- Use BleTransport.startStreaming() for BLE
- No em-dashes
```
***
## Key Workflows
### Getting Started
Scaffold an app first, then open in Windsurf:
```bash theme={null}
pnpm create @elata-biosciences/elata-demo my-app -- --template rppg
cd my-app && pnpm install
```
### Common Prompts
**Add biometric features:**
* "Add camera heart rate monitoring using createRppgSession from rppg-web"
* "Add EEG band power visualization using eeg-web"
* "Connect to a Muse headset using BleTransport from eeg-web-ble"
**Add protocol features:**
* "Show the current bonding curve price from AppBondingCurve"
* "Create a tournament join button that calls Tournament.join()"
* "Display the user's staking position from the StakingVault"
***
## Tips
Reference the SDK docs at `docs.elata.bio/sdk/overview` in your prompts when asking Windsurf to generate integration code.
***
## Next
What the protocol gives builders
Biometric SDK packages
# App Lifecycle
Source: https://docs.elata.bio/apps/app-lifecycle
From registration through bonding curve to graduation and live DEX trading.
## Overview
Every Elata app follows the same lifecycle: register, launch a token, trade on a bonding curve, graduate to a DEX pair, and operate with full community tools.
```mermaid theme={null}
flowchart LR
REG["Register App"] --> LAUNCH["Launch Token"]
LAUNCH --> CURVE["Bonding Curve"]
CURVE --> GRAD["Graduation"]
GRAD --> DEX["DEX Trading"]
DEX --> OPS["Operate"]
```
***
## Phase A: Registration
Register your app by calling `AppFactory` with metadata and a Safe wallet address.
* **Cost:** 10 ELTA registration fee
* **What happens:** App appears in the store immediately (without a token)
* **Requirements:** A Safe multisig wallet to control the app
Your app exists on-chain and is visible in the app store, but has no token yet.
***
## Phase B: Token Launch
Deploy the full token stack by sending 100 ELTA as the bonding curve seed.
* **Cost:** 100 ELTA seed (total with registration: 110 ELTA)
* **What gets deployed:** App Token (10M supply), Bonding Curve, Staking Vault, Vesting Wallet, Ecosystem Vault
**Token distribution:**
| Destination | Share | Amount |
| --------------- | ----- | ---------------- |
| Bonding curve | 50% | 5,000,000 tokens |
| Team vesting | 25% | 2,500,000 tokens |
| Ecosystem vault | 25% | 2,500,000 tokens |
After deployment, the bonding curve enters `PENDING` state.
***
## Bonding Curve
Once activated, the curve enters `ACTIVE` state and trading begins.
**How it works:**
* Constant-product formula: `reserveELTA * reserveToken = k`
* Price rises as more ELTA is deposited; falls as tokens are sold back
* 1% trading fee on each trade (configurable by governance)
**Early access:** For the first 6 hours, buyers need 100 XP to participate. This prevents sniping.
**Lifecycle states:**
| State | Description |
| ----------- | ------------------------------------- |
| `PENDING` | Deployed, not yet activated |
| `ACTIVE` | Trading is live |
| `GRADUATED` | Reserves hit the graduation threshold |
| `CANCELLED` | App cancelled before graduation |
***
## Graduation
Graduation triggers when ELTA reserves in the bonding curve reach **42,000 ELTA**.
When graduation happens:
Remaining tokens and ELTA reserves are paired on a DEX.
The liquidity position is locked for **730 days** (2 years).
All trading moves from the bonding curve to the DEX pair.
***
## Post-Graduation
After graduation, your app token trades freely on the DEX. The protocol continues to handle:
* **Fee routing** through the FeeRouter
* **Transfer tax** (up to 2%, LP-keyed)
* **Contributor payouts** via the ContributorSplit contract
* **Community tools** (tournaments, items, staking)
***
## Next
What you need to launch
Price discovery mechanics
Token design and distribution
# App Metadata and Listing
Source: https://docs.elata.bio/apps/build/app-metadata
How your app appears in the store, metadata standards, and image requirements.
## Store Listing Fields
When you register an app, you provide metadata that appears in the app store:
| Field | Required | Description |
| ------------ | -------- | ------------------------------------- |
| Name | Yes | Display name for your app |
| Description | Yes | Short summary shown on the store card |
| Logo | Yes | Square image for the app icon |
| Category | Yes | Focus, Gaming, Wellness, or Research |
| Website | No | External link to your app |
| Social links | No | Twitter, Discord, GitHub, etc. |
| Team members | No | Public team display |
***
## NFT Metadata Standard
App tokens and in-app items follow ERC-721 with ERC-4906 metadata update signaling. This means metadata changes emit on-chain events that marketplaces can listen to for auto-refresh.
Example metadata JSON:
```json theme={null}
{
"name": "My Elata App Token",
"description": "Access token for the My App ecosystem",
"image": "ipfs://QmExample.../logo.png",
"external_url": "https://myapp.example.com",
"attributes": [
{ "trait_type": "Category", "value": "Gaming" },
{ "trait_type": "Version", "value": "1.0" }
]
}
```
***
## Image Requirements
| Asset | Recommended Size | Format |
| ------------ | ---------------- | ----------- |
| App logo | 500x500px | PNG or SVG |
| Token image | 1000x1000px | PNG |
| Store banner | 1200x630px | PNG or WebP |
Use square images for logos and token images. Non-square images will be cropped in the store.
***
## Storage Options
| Option | Best For | Notes |
| -------------------------- | ----------- | --------------------------------------------------- |
| IPFS (Pinata, NFT.Storage) | Production | Recommended. Decentralized and permanent. |
| Arweave | Production | Alternative permanent storage. |
| Centralized HTTP | Development | Fine for testing. Do not use for production tokens. |
Once metadata is referenced on-chain, changing the underlying content at the same URI does not trigger an ERC-4906 update. Use `setTokenURI` to point to new content and emit the update event.
***
## Updating Metadata
After launch, your Safe can update app metadata by calling the appropriate registry function. Token metadata updates follow the ERC-4906 pattern: set a new URI and emit the `MetadataUpdate` event so marketplaces and indexers refresh.
***
## Next
Deploy your app on-chain
What to do after launch
Create in-app items
# Launch Requirements
Source: https://docs.elata.bio/apps/build/launch-requirements
Everything you need before deploying an app on the Elata protocol.
## What You Need
Before launching, make sure you have:
Every app is controlled by a Safe. Create one at [safe.global](https://safe.global) with your team members as signers.
10 ELTA for registration + 100 ELTA for the bonding curve seed. Both are paid to the protocol during launch.
Name, description, logo, social links. See [App Metadata and Listing](/apps/build/app-metadata) for the full spec.
Phase A registers your app (10 ELTA). Phase B deploys the token stack (100 ELTA). See [App Lifecycle](/apps/app-lifecycle) for details.
***
## Cost Breakdown
| Item | Cost | What It Does |
| ------------------ | ------------ | -------------------------------------------------- |
| Registration fee | 10 ELTA | Registers app metadata and Safe in AppRegistry |
| Bonding curve seed | 100 ELTA | Seeds the initial ELTA reserve for price discovery |
| **Total** | **110 ELTA** | Full launch |
The registration fee is non-refundable. The bonding curve seed becomes part of the trading reserve and is not directly withdrawable.
***
## Safe Wallet Setup
The Safe multisig is the owner of your app on-chain. It controls:
* Contributor split configuration
* Team vesting wallet
* Ecosystem vault
* App metadata updates
Set up your Safe before starting the launch process. Add all core team members as signers with an appropriate threshold (e.g., 2-of-3 for a small team).
***
## Network Options
| Network | Gas Cost | Use Case |
| ---------------------- | ------------------ | ----------------------- |
| Ethereum Mainnet | \~\$260 at 20 gwei | Production |
| Base | \~\$2-5 | Production (lower cost) |
| Sepolia / Base Sepolia | Free | Testing |
Start on a testnet to validate your launch flow before spending real ELTA. Use the faucet to get test tokens.
***
## Next
Step-by-step launch flow
Metadata fields and listing
Full lifecycle overview
# Launch Your App
Source: https://docs.elata.bio/apps/build/launch-your-app
Step-by-step launch flow using current protocol contracts.
## Overview
Launching an Elata app is a two-phase process. Phase A registers your app. Phase B deploys the full token stack.
**Total cost:** 110 ELTA (10 ELTA registration + 100 ELTA bonding curve seed).
***
## Phase A: Register (10 ELTA)
Register your app metadata and Safe wallet address through the AppFactory.
1. Prepare your Safe multisig wallet
2. Approve 10 ELTA for the AppFactory contract
3. Call `registerApp()` with metadata and your Safe address
4. Your app appears in the store immediately (no token yet)
***
## Phase B: Deploy Token Stack (100 ELTA)
Deploy the full token infrastructure by seeding the bonding curve.
1. Approve 100 ELTA for the AppFactory contract
2. Call `deployToken()` from your Safe
3. The protocol deploys: App Token, Bonding Curve, Staking Vault, Vesting Wallet, Ecosystem Vault
**What gets deployed:**
| Contract | Purpose |
| ----------------- | ----------------------------------------- |
| App Token | ERC-20 with 10M supply |
| Bonding Curve | Constant-product price discovery |
| Staking Vault | Token holder staking |
| Vesting Wallet | 25% team allocation with vesting schedule |
| Ecosystem Vault | 25% ecosystem reserves |
| Contributor Split | Revenue distribution to contributors |
***
## Post-Launch State
After Phase B, your bonding curve is in `PENDING` state. Activate it to begin trading.
The curve moves through: `PENDING` to `ACTIVE` to `GRADUATED` (or `CANCELLED`).
Creator allocation (team vesting) is subject to vesting schedules. Tokens vest linearly over the configured period.
***
## Important
* Transfer tax (up to 2%) applies to token transfers that touch allowlisted LPs
* Fee routing begins immediately when trading starts
* The 100 ELTA seed becomes part of the bonding curve reserve and is not directly withdrawable
***
## Next
Store listing and metadata
What to do after launch
How price discovery works
# Builder Overview
Source: https://docs.elata.bio/apps/build/overview
What the protocol gives you and what you avoid building yourself.
## What You Get
The Elata protocol provides a ready-made launch and fee-routing stack for tokenized applications. As a builder, you deploy your app through the protocol and receive:
* An ERC-20 app token with 10M supply
* A bonding curve for price discovery
* Fee routing through FeeRouter and ContributorSplit
* A staking vault for token holders
* Team vesting and ecosystem vaults
* In-app item (NFT) infrastructure
* Tournament and competition tooling
***
## What You Avoid Building Yourself
| Component | Protocol Handles It |
| ------------------------------- | ---------------------------------------------- |
| Token creation and distribution | AppFactory deploys with 50/25/25 split |
| Price discovery | Constant-product bonding curve with graduation |
| Liquidity provisioning | Automated DEX pair creation and LP lock |
| Fee collection and routing | FeeRouter splits to treasury and contributors |
| Revenue distribution | ContributorSplit with pull-based claims |
***
## Protocol Defaults
| Parameter | Value |
| ----------------- | ---------------------------- |
| Launch cost | 110 ELTA (10 fee + 100 seed) |
| Token supply | 10,000,000 per app |
| Curve allocation | 50% (5M tokens) |
| Team vesting | 25% (2.5M tokens) |
| Ecosystem vault | 25% (2.5M tokens) |
| Graduation target | 42,000 ELTA in reserves |
| LP lock duration | 730 days |
***
## Core Contracts
| Contract | Purpose |
| ------------------ | ---------------------------------------------------- |
| `AppFactory` | Deploys app token stack |
| `AppRegistry` | On-chain mapping of appId to ownerSafe, token, curve |
| `AppToken` | ERC-20 token per app |
| `AppBondingCurve` | Constant-product price discovery |
| `FeeRouterV2` | Single routing surface for all fee kinds |
| `ContributorSplit` | Pull-based payout for app contributors |
***
## Next
What you need to launch
Step-by-step launch flow
How fees are routed
# Post-Launch Checklist
Source: https://docs.elata.bio/apps/build/post-launch-checklist
What to do after deploying your app token on the Elata protocol.
## Immediate Actions
After Phase B deployment, the curve starts in `PENDING` state. Activate it to begin trading. Early access gating (100 XP required) applies for the first 6 hours.
Check the app store at [app.elata.bio](https://app.elata.bio) to confirm metadata, logo, and description appear correctly.
Configure the ContributorSplit contract through your Safe. Add team members and set their share percentages. This determines how app revenue is distributed.
Add Twitter, Discord, GitHub, and team member info to your app listing so users can find your community.
***
## Launch Week
The first week sets the tone for your app's community. Focus on visibility and first purchases.
* **Announce** on Twitter and Discord with your app store link
* **Distribute XP** to early supporters so they can participate during the early access window
* **Run an inaugural tournament** to drive engagement and token activity
* **Create an access pass item** if your app has gated features
* **Monitor holder distribution** to ensure healthy early spread
***
## Ongoing Operations
After the initial launch, shift focus to sustained growth:
* **Track graduation progress** toward the 42,000 ELTA threshold
* **Run regular tournaments** to maintain engagement
* **Adjust contributor splits** as your team grows
* **Review analytics** for holder concentration and trading volume
***
## Next
Community growth playbook
Run competitive events
Track app health metrics
# Choose Your Path
Source: https://docs.elata.bio/apps/choose-your-path
Whether you want to build an app or participate as a user, start here.
## For Builders
You want to launch and operate a tokenized app on Elata. The protocol handles token economics, fee routing, and community infrastructure.
What builders get and what you avoid building
Prerequisites: Safe wallet, ELTA, metadata
Step-by-step launch flow
What to do after launch
***
## For Users
You want to discover apps, buy tokens, stake, and participate in governance.
Find and evaluate apps
How buying and trading works
Staking and rewards
Lock ELTA for voting power
***
## Not Sure?
Start with [What Are Elata Apps?](/apps/what-are-elata-apps) for the big picture, or jump to [What You Can Build](/apps/what-you-can-build) to see real examples.
# App Tokens
Source: https://docs.elata.bio/apps/design/app-tokens
Token creation, distribution, transfer tax, and lifecycle for Elata apps.
## How App Tokens Work
Each Elata app receives its own ERC-20 token with a fixed supply of **10,000,000 tokens**. The token is created during Phase B of the [launch process](/apps/build/launch-your-app).
***
## Distribution
| Destination | Share | Amount | Purpose |
| ------------------- | ----- | --------- | ---------------------------------------- |
| Bonding curve | 50% | 5,000,000 | Liquidity for price discovery |
| Team vesting wallet | 25% | 2,500,000 | Team incentives, vests linearly |
| Ecosystem vault | 25% | 2,500,000 | Community incentives, controlled by Safe |
***
## Design Purposes
App tokens serve three main functions:
1. **Bootstrap distribution:** the bonding curve makes tokens available to early supporters at a price that rises with demand
2. **App incentives:** builders use tokens for tournaments, items, staking, and community rewards
3. **Fee routing:** trading fees and transfer taxes flow through FeeRouter to the treasury and contributor split
***
## Transfer Tax
App tokens support an optional transfer tax of up to 2% (protocol-wide cap). The tax applies only when:
* Neither the sender nor the receiver is tax-exempt
* The transfer touches an allowlisted liquidity pool (LP-keyed)
Transfers between regular wallets that do not involve an LP are not taxed.
Transfer tax is not retroactive. It applies from the moment it is configured. Builders configure it through their Safe.
***
## Token Lifecycle
1. **Phase B deployment:** 10M tokens minted, distributed 50/25/25
2. **Bonding curve active:** tokens tradeable on the curve
3. **Graduation:** remaining curve tokens and ELTA pair on a DEX, LP locked for 730 days
4. **Post-graduation:** tokens trade freely on the DEX with fee routing active
5. **Burns:** item purchases burn tokens, reducing supply over time
***
## Next
Price discovery mechanics
How fees are routed
Token burn through items
# Bonding Curve Basics
Source: https://docs.elata.bio/apps/design/bonding-curve-basics
How price discovery works on Elata launch curves, from seeding to graduation.
## Core Model
Elata launch curves use constant-product reserves:
```text theme={null}
k = reserveElta * reserveToken
```
When someone buys:
```text theme={null}
newReserveElta = reserveElta + eltaIn
newReserveToken = k / newReserveElta
tokensOut = reserveToken - newReserveToken
```
Price rises as more ELTA enters the reserve. Selling tokens reverses the direction.
***
## Default Constants
| Parameter | Value |
| ----------------- | ----------------------- |
| Total launch cost | 110 ELTA |
| Seed ELTA | 100 ELTA |
| Creation fee | 10 ELTA |
| App token supply | 10,000,000 |
| Curve allocation | 5,000,000 (50%) |
| Graduation target | 42,000 ELTA in reserves |
| LP lock | 730 days |
***
## Curve Lifecycle
The `AppBondingCurve` contract follows a state machine:
| State | Description |
| ----------- | -------------------------------------- |
| `PENDING` | Deployed, not yet activated |
| `ACTIVE` | Trading is live |
| `GRADUATED` | Reserves reached the graduation target |
| `CANCELLED` | App cancelled before graduation |
Additional behavior:
* Permissionless activation after a configurable delay
* Force graduation after deadline
* Creator-only cancel while in `PENDING` state
***
## Fees On Curve
* Default trading fee: **1%** (from `AppFeeRouter.feeBps`)
* Fee is paid on top of the buy amount
* Fees accumulate and are swept into FeeCollector as `TRADING_FEE`
The trading fee is configurable by governance. Check current protocol parameters for the active rate.
***
## Graduation
When the ELTA reserve reaches **42,000 ELTA** (or the deadline triggers force graduation):
A Uniswap V2 pair is created or loaded for the app token and ELTA.
Remaining token reserves and ELTA reserves are added as liquidity.
LP tokens are locked in LpLocker for **730 days**.
Bonding curve trading is disabled. All trading moves to the DEX.
***
## Early Access Gate
For the first **6 hours** after launch, buyers need **100 XP** to participate on the curve. This prevents sniping by requiring demonstrated protocol participation.
The gate parameters are configurable by governance.
***
## Next
Token distribution and lifecycle
How fees are routed
Full lifecycle overview
# Community Systems
Source: https://docs.elata.bio/apps/design/community-systems
Contributor splits, veELTA governance, and protocol-level community mechanics.
## Contributor Splits
Each app has a ContributorSplit contract controlled by the app's Safe. This is the mechanism for distributing app revenue to team members and contributors.
```mermaid theme={null}
flowchart LR
FEES["App Fees"] --> ROUTER["FeeRouter"]
ROUTER --> TREASURY["Treasury (20%)"]
ROUTER --> SPLIT["ContributorSplit (80%)"]
SPLIT --> C1["Contributor 1"]
SPLIT --> C2["Contributor 2"]
SPLIT --> CN["Contributor N"]
```
**Key properties:**
* Pull-based: contributors claim their share, the protocol does not push payments
* Managed exclusively by the app's Safe (add, remove, or adjust share percentages)
* Supports up to 150-200 contributors per app
* Default split: 80% to contributors, 20% to protocol treasury (adjustable via governance)
Contributors must actively claim their payouts. Unclaimed funds remain in the contract until withdrawn.
***
## veELTA and Governance
veELTA is the vote-escrow mechanism for protocol governance. Users lock ELTA tokens for a chosen duration and receive voting power in return.
| Parameter | Value |
| ------------------ | ------------------------------------ |
| Lock duration | 7 to 730 days |
| Voting power boost | 1x (7 days) to 2x (730 days), linear |
| Fee yield | None (V2 design) |
**Boost formula:**
```text theme={null}
boost = 1 + (lockDuration / maxDuration)
veELTA = lockedAmount * boost
```
veELTA is used exclusively for governance voting. It does not entitle holders to fee yields. This design choice mitigates securities risk under the Howey test.
**Position management:** create a lock, increase the locked amount, extend the duration, or unlock the principal after expiry.
***
## Protocol-Level Decisions
Governance proposals are submitted through the ElataGovernor contract and executed via the ElataTimelock. Changes to protocol parameters (fee splits, graduation thresholds, trading fees) require governance approval.
Key parameters controlled by governance:
| Parameter | Default | Range |
| -------------------- | ----------- | --------------------- |
| Trading fee | 1% | Governance-set |
| Transfer tax cap | 2% | Protocol-wide maximum |
| Treasury take | 20% | Adjustable |
| Graduation threshold | 42,000 ELTA | Governance-set |
| LP lock duration | 730 days | Governance-set |
***
## Next
How fees are routed
User guide for locking ELTA
# Fee Flow for Apps
Source: https://docs.elata.bio/apps/design/fee-flow
How fees flow through FeeCollector, FeeSwapper, and ContributorSplit.
## Overview
All protocol and app fees flow through the same contracts but are routed differently based on their `FeeKind`.
```mermaid theme={null}
flowchart LR
subgraph sources ["Fee Sources"]
BC["Bonding Curve
(TRADING_FEE)"]
AT["App Token
(TRANSFER_TAX)"]
AF["App Factory
(LAUNCH_FEE)"]
MOD["App Modules
(CONTENT_SALE, TOURNAMENT_FEE, OTHER)"]
end
subgraph pipeline ["Pipeline"]
FC["FeeCollector"]
FS["FeeSwapper"]
end
subgraph destinations ["Destinations"]
TR["Treasury"]
CS["ContributorSplit"]
end
BC --> FC
AT --> FC
AF --> FC
MOD --> FC
FC --> FS
FS --> TR
FS --> CS
```
***
## FeeKind Routing
Every fee is tagged with a `FeeKind` that determines where it goes:
| FeeKind | Routing |
| ---------------- | ------------------------------- |
| `LAUNCH_FEE` | 100% treasury |
| `TRADING_FEE` | 80% contributors / 20% treasury |
| `TRANSFER_TAX` | 80% contributors / 20% treasury |
| `CONTENT_SALE` | 80% contributors / 20% treasury |
| `TOURNAMENT_FEE` | 80% contributors / 20% treasury |
| `OTHER` | 80% contributors / 20% treasury |
Default treasury take: **20%** (2000 bps), configurable per-app by governance.
***
## FeeCollector
The accounting layer. It tracks pending fee balances indexed by `(appId, FeeKind, asset)`.
* Receives ELTA deposits via `depositElta(appId, kind, amount)`
* Receives app token deposits for transfer tax
* Sweeps accumulated balances to FeeSwapper via `sweep(appId, kind, asset)`
* Sweeping is permissionless (anyone can trigger it)
***
## FeeSwapper
The routing layer (implements `IFeeRouterV2`):
1. If the app is **paused** in AppRegistry, 100% goes to treasury
2. If the fee kind is `LAUNCH_FEE`, 100% goes to treasury
3. Otherwise: treasury gets its take, the rest goes to ContributorSplit
Contributors receive their share through pull-based claims, not automatic distribution. They must actively claim their payouts from the ContributorSplit contract.
***
## ContributorSplit
Each app has a ContributorSplit contract deployed at registration:
```mermaid theme={null}
flowchart TB
FS["FeeSwapper"] -->|"deposit"| CS["ContributorSplit"]
SAFE["App Owner Safe"] -->|"setContributors()"| CS
CS -->|"release()"| C1["Contributor A"]
CS -->|"release()"| C2["Contributor B"]
CS -->|"release()"| CN["Contributor N"]
```
* **Shares-based:** each contributor has a share weight; payouts are proportional
* **Max 200 contributors** (factory default, governance-configurable)
* **Pull claims:** contributors call `release(asset, account)` to withdraw
* **Owner Safe controlled:** only the app's Safe can modify contributors
***
## Next
Price discovery mechanics
Governance and contributor splits
Token design and distribution
# Incentives and Access
Source: https://docs.elata.bio/apps/design/incentives-and-access
Points system, early access gating, and how protocol incentives work.
## Elata Points System
Elata points are non-transferable ERC20Votes token used for governance weight and protocol access. It cannot be bought or sold.
Points are distributed through Merkle-tree-based epochs. Each epoch adds points to qualifying addresses. Reductions only happen through operator revocation, not natural decay.
Points are additive only. Once earned, it stays unless explicitly revoked by an operator.
***
## How Points are Earned
| Activity | Description |
| ------------------------ | ------------------------------------------------------------------ |
| Launching an app | Builders who deploy through the protocol earn Points |
| Staking ELTA | Long-term ELTA stakers receive Points based on duration |
| Governance participation | Voting on proposals earn Points |
| Protocol activity | Active trading, tournament participation, and community engagement |
Point distribution is managed by addresses with the `XP_OPERATOR_ROLE`. Operators publish epochs and manage distributions. Each distribution ID is tracked to prevent double-issuance.
***
## Early Access Gate
New app launches include a **6-hour early access window**. During this period, only users with at least \*\*100 Points \*\*can buy tokens on the bonding curve.
If you do not have 100 Points, you cannot participate in the first 6 hours of a new app's bonding curve. After the window closes, anyone can trade.
This mechanism prevents sniping by requiring demonstrated protocol participation before early access.
***
## What Points Unlock
* **Early access** to new app token launches (100 point minimum)
* **Governance weight** in protocol-level decisions via ERC20Votes
* **Future incentive eligibility** as the protocol expands reward mechanisms
Points are not a financial instrument. It represents participation and is not designed to have monetary value.
***
## Next
Governance and contributor splits
Token design and distribution
# Analytics and Health
Source: https://docs.elata.bio/apps/operate/analytics-and-health
Metrics to track, holder distribution signals, and what healthy patterns look like.
## Key Metrics
Track these metrics to understand your app's health:
| Metric | What It Tells You | Healthy Signal |
| ------------------------ | --------------------------------- | ----------------------------------------- |
| Unique holders | Community size | Steady growth over time |
| Trading volume | Market activity | Consistent volume, not just launch spikes |
| Graduation progress | Distance to 42,000 ELTA threshold | Steady accumulation |
| Staking ratio | % of supply locked | 30-50% of circulating supply |
| Tournament participation | Community engagement | Growing or stable participation rates |
***
## Holder Distribution
Holder concentration is one of the strongest health indicators for an app token.
| Pattern | Signal | Action |
| ------------------------------- | ----------------------- | --------------------------------------- |
| Top 5 wallets hold \< 30% | Healthy distribution | Continue current strategy |
| Top 5 wallets hold 30-50% | Moderate concentration | Encourage broader distribution |
| Top 5 wallets hold > 50% | High concentration risk | Run events and incentives to distribute |
| Many wallets with tiny holdings | Wide but shallow | Focus on engagement depth |
High concentration in a few wallets creates sell pressure risk. If a single holder exits, it can significantly impact the price on the bonding curve.
***
## Health Signals
**Positive signals:**
* Growing unique holder count week over week
* Staking ratio above 30%
* Regular tournament participation
* Item purchases creating token burns
* Organic trading volume (not just bot activity)
**Warning signals:**
* Declining holder count
* Staking ratio below 10%
* No tournament or item activity
* Volume concentrated in a few wallets
* Large unstaking events
***
## What To Do With This Data
Use analytics to guide your operating decisions:
* **Low engagement?** Run a tournament or create a new access-pass item
* **Holder concentration too high?** Distribute ecosystem vault tokens to new users
* **Staking dropping?** Review your staking incentives and lock durations
* **Close to graduation?** Announce the milestone and create urgency
***
## Next
Community growth playbook
Run competitive events
Create in-app items
# Grow Your Community
Source: https://docs.elata.bio/apps/operate/grow-your-community
Community growth playbook for app builders on the Elata protocol.
## Overview
Once your app is live, community growth comes from three levers: competitions, collectibles, and commitment tools. This page covers the strategic playbook. For the specific mechanics, see the dedicated pages below.
***
## Launch Week Playbook
The first week sets the tone. Focus on visibility, first purchases, and establishing a community heartbeat.
1. **Announce everywhere.** Post your app store link on Twitter, Discord, and any relevant communities.
2. **Run an inaugural tournament.** Even a small prize pool generates activity and creates early trading volume.
3. **Create a soulbound access pass.** Gate a feature behind an item purchase to give early supporters something exclusive.
4. **Distribute XP** to your community so they can participate during the 6-hour early access window.
5. **Set up social links and team display** on your app listing.
***
## After Launch
Shift from launch buzz to sustained engagement:
* **Run tournaments regularly** (weekly or bi-weekly) to maintain trading volume and participation
* **Release new items** to create ongoing token burn pressure
* **Monitor holder distribution** and address concentration risks early
* **Adjust contributor splits** as your team and contributor base grows
* **Engage stakers** with exclusive access or governance participation
***
## Social Presence
Set up and maintain these channels:
| Platform | Purpose |
| --------- | ------------------------------------------------------ |
| Twitter/X | Announcements, launch events, milestone updates |
| Discord | Community discussion, support, tournament coordination |
| GitHub | Open source code, contribution tracking |
| Telegram | Quick updates, regional communities |
| Website | Product landing page, documentation |
***
## Team Display
Add team members to your app listing. Visible teams build trust and help users evaluate new apps. Update this as your team grows.
***
## What Works
* **Consistency beats intensity.** Regular small events outperform rare large ones.
* **Burns create interest.** Item purchases that burn tokens give holders a reason to watch supply dynamics.
* **Staking shows conviction.** A healthy staking ratio signals long-term community health.
* **Transparency builds trust.** Public contributor splits, visible team, and open governance.
***
## Next
Run competitive events
Create in-app items
Track app metrics
# Items and Unlocks
Source: https://docs.elata.bio/apps/operate/items-and-unlocks
Create ERC-721 items for gating, collectibles, and in-app utility.
## Creating Items
Items adhere to the ERC-721 standard, and are created through the InAppContent721 contract. Each item has:
| Property | Description |
| ---------- | ------------------------------------------------------- |
| Name | Display name for the item |
| Price | Cost in app tokens (burned on purchase) |
| Soulbound | Whether the item is transferable or locked to the buyer |
| Max supply | Optional cap on total mints |
***
## Item Types
| Type | Purpose | Example |
| ----------- | ------------------------------------------ | -------------------------------------------- |
| Access Pass | Gate features or content | "Pro Mode" unlock, exclusive dashboard |
| Cosmetic | Visual customization | Profile borders, badges, themes |
| Power-up | Temporary or persistent gameplay advantage | Double XP for 24 hours |
| Collectible | Limited-edition tokens | Launch day commemorative, achievement badges |
***
## Burn Mechanics
All item purchases burn 100% of the app tokens spent. This creates direct deflationary pressure on the app token supply.
When a user buys an item:
1. App tokens are transferred from the buyer
2. Tokens are burned (sent to the zero address)
3. The item is minted to the buyer
There is no treasury cut or fee on item purchases. The entire price is removed from circulation.
***
## Feature Gating
Use items to gate app features. Check ownership in your frontend:
```javascript theme={null}
const hasAccess = await contract.balanceOf(userAddress) > 0;
if (hasAccess) {
// Show premium feature
} else {
// Show purchase prompt
}
```
For soulbound items, `balanceOf` is the only check needed since the item cannot be transferred away.
***
## Soulbound vs Transferable
| Property | Soulbound | Transferable |
| ---------------------------- | --------------------------- | ----------------------- |
| Can be sold or traded | No | Yes |
| Appears on secondary markets | No | Yes |
| Best for | Access passes, achievements | Collectibles, cosmetics |
| Ownership check | Wallet-locked | Can change hands |
Use soulbound items for access gates and feature unlocks. Use transferable items for collectibles and cosmetics that users may want to trade.
***
## Next
Run competitive events
Community growth playbook
Token design and distribution
# Staking for Apps
Source: https://docs.elata.bio/apps/operate/staking-for-apps
App-level staking as a builder tool for engagement and commitment signals.
## Why App Staking
Staking lets token holders lock their app tokens to signal commitment. For builders, it provides:
* A measure of community conviction
* Reduced circulating supply (less sell pressure)
* A tool for tiered access or rewards
***
## Setting Up Staking
Each app receives a staking vault as part of the token launch. To enable staking:
The staking vault is deployed automatically during Phase B (token launch). No additional deployment is needed.
Use your Safe to set staking parameters: minimum stake duration, reward rates (if applicable), and any access tiers.
Let users know staking is live and what they get for participating.
***
## What Users See
From the user perspective, staking is straightforward:
| Action | Description |
| ------------- | --------------------------------------------------------------- |
| Stake | Lock app tokens in the vault for a chosen duration |
| Unstake | Withdraw tokens after the lock period ends |
| View position | Check locked amount, duration remaining, and any earned rewards |
Stakers appear in holder analytics as committed participants, distinct from short-term traders.
***
## Staking as a Builder Tool
Use staking data to:
* **Gate features** by requiring a minimum staked balance
* **Weight governance** by giving stakers more influence in app-level decisions
* **Reward loyalty** with tournament entry, item discounts, or priority access
* **Track health** by monitoring the ratio of staked to circulating tokens
A healthy staking ratio (30-50% of circulating supply) indicates strong community conviction. Track this in your [analytics dashboard](/apps/operate/analytics-and-health).
***
## Next
Community growth playbook
Track app health metrics
# Tournaments
Source: https://docs.elata.bio/apps/operate/tournaments
Time-boxed competitions with smart contract payouts for Elata apps.
## What Is A Tournament?
A tournament is a time-boxed competition that runs inside an Elata app. Entry fees build a prize pool, winners are determined by app-defined rules, and payouts happen automatically through smart contracts.
Each tournament is:
* A single-use event with a clear start and end time
* Funded by entry fees in the app's token
* Governed by transparent, on-chain fee splits
***
## How Tournaments Work
The app builder creates a tournament through the TournamentFactory contract. This deploys a fresh Tournament contract, registers it under the app, and applies fee settings.
During the entry window, players pay an entry fee in the app's token. The contract collects fees and builds the prize pool.
Players compete under the app's rules. Scoring and results are determined by the app's logic.
The tournament creator (or an authorized address) finalizes results. The contract calculates prize allocations based on the final standings.
Winners claim their share of the prize pool. Unclaimed rewards remain in the contract until withdrawn.
***
## Tournament Economics
| Recipient | Share |
| ---------- | ----- |
| Winners | 96.5% |
| Protocol | 2.5% |
| Token burn | 1% |
These defaults are configurable per tournament.
***
## Example Use Cases
Weekly leaderboard competitions with escalating prize pools. Players compete for high scores, and the top 10% split the pot.
30-day meditation challenges tracked through EEG or rPPG. Participants who meet daily targets share the reward pool.
Focus sprints where participants compete on sustained attention metrics. Best performers earn from the prize pool.
Data collection campaigns where participants contribute quality biosignal data. Compensation is proportional to data quality and quantity.
***
## Why Tournaments Matter
| For | Benefit |
| ------------- | -------------------------------------------------------------------------------- |
| Users | Earn rewards, competitive engagement, community connection |
| Builders | Drive trading volume, create engagement loops, attract new users |
| Token holders | Tournament fees route through the fee pipeline, activity increases token utility |
As a builder, you are responsible for tournament integrity. Set clear rules, use fair scoring, and finalize results promptly.
***
## Next
Community growth playbook
Create in-app items
How fees are routed
# Buy and Trade
Source: https://docs.elata.bio/apps/users/buy-and-trade
How buying works during the bonding curve phase and after graduation.
## Two Trading Phases
Every app token goes through two phases:
| Phase | Where You Trade | How Price Works |
| ----------- | ---------------------- | --------------------------------------------------------- |
| **Raising** | Bonding curve (in-app) | Constant-product formula: price rises as more ELTA enters |
| **Live** | DEX (e.g., Uniswap) | Free market with locked liquidity |
***
## Bonding Curve Phase
During the raising phase, you buy tokens directly from the bonding curve using ELTA.
**What to know:**
* Price is determined by the curve formula (`reserveELTA * reserveToken = k`)
* Each purchase pushes the price up; each sale pushes it down
* A 1% trading fee applies to each trade
* The first 6 hours have an early access gate requiring 100 XP
**How to buy:**
1. Go to the app's detail page on [app.elata.bio](https://app.elata.bio)
2. Connect your wallet
3. Enter the amount of ELTA you want to spend
4. Confirm the transaction
***
## Graduation
The bonding curve graduates when ELTA reserves reach **42,000 ELTA**. When this happens:
1. Remaining tokens and ELTA reserves are paired on a DEX
2. LP tokens are locked for **730 days** (2 years)
3. All trading moves from the bonding curve to the DEX
After graduation, the token trades like any other DEX-listed token with an established liquidity pool.
***
## Transfer Tax
App tokens may have a transfer tax of up to **2%**. The tax applies only on transfers that touch an allowlisted liquidity pool (LP-keyed). Regular wallet-to-wallet transfers are not taxed.
***
## Fee Awareness
When you trade on the bonding curve:
* **1% trading fee** is collected and routed through the fee pipeline
* Fees are split between contributors (80%) and protocol treasury (20%)
When you trade on a DEX after graduation:
* Standard DEX swap fees apply
* Transfer tax (if configured) applies on LP-involving transfers
***
## Next
Lock tokens for rewards
How price discovery works
# Explore Apps
Source: https://docs.elata.bio/apps/users/explore-apps
Discover and evaluate apps deployed on the Elata protocol.
## The App Store
Browse neurotech apps at [app.elata.bio](https://app.elata.bio). Each app has its own token, bonding curve, and community tools.
Go to the App Store
***
## Finding Apps
The main page shows all launched apps with their icon, name, token symbol, status, and creator address.
### App Statuses
| Status | Meaning |
| ----------- | ------------------------------------------- |
| **Raising** | Bonding curve active, price discovery phase |
| **Live** | Graduated to DEX, free market trading |
***
## App Detail Pages
Click any app to see:
* **Overview:** icon, name, token price, creator, description, social links
* **Trading:** buy form (if raising), price chart, trading history, DEX link (if live)
* **Community:** holder distribution, staking stats, team members, tournaments
* **Play:** embedded game/app with full-screen option
***
## Playing Apps
Most Elata apps run directly in your browser.
1. Go to the app's detail page
2. Click the "Play" button
3. Use full-screen for the best experience
Some apps require wallet connection for saving progress, accessing premium features, participating in tournaments, or verifying item ownership.
Apps may support EEG devices or camera-based rPPG. Check the app description for compatibility requirements.
***
## App Categories
| Category | Examples |
| ------------ | ------------------------------------------------------------ |
| **Focus** | Attention trainers, flow detectors, brain-feedback pomodoros |
| **Gaming** | Mind-controlled games, neurofeedback challenges |
| **Wellness** | Meditation, stress tracking, breathwork |
| **Research** | Data collection, cognitive tests, experiments |
***
## Early Access
New app launches have a **6-hour early access window**. During this period, you need at least **100 XP** to buy tokens on the bonding curve.
XP is earned through protocol participation, data contributions, and community activities. It is non-transferable.
XP is the only way to access early launch windows. There is no way to buy or transfer it. See [Incentives and Access](/apps/design/incentives-and-access) for details.
***
## Evaluating Apps
Before participating, check:
* **Creator address:** look it up on the block explorer
* **Team members:** verify identities if shown
* **Social links:** confirm they are official channels
* **Trading volume:** higher volume means more liquidity
* **Holder distribution:** check for concentration risk
Start small and learn how the system works before committing significant funds. Tokens can go to zero.
***
## Your Dashboard
Access your personal dashboard at [app.elata.bio/dashboard](https://app.elata.bio/dashboard) to see holdings, staked positions, claimable rewards, and transaction history.
***
## Help
* Discord: [#support](https://discord.gg/GqS9CstffK)
* Twitter: [@elata\_bio](https://x.com/elata_bio)
* GitHub: [Issues](https://github.com/elata-biosciences)
Click "Connect Wallet" in the header. Choose your wallet provider (MetaMask, Coinbase Wallet, etc.) and approve the connection.
Elata is currently on Ethereum Sepolia (testnet). Your wallet should prompt you to switch automatically.
Follow [@elata\_bio](https://x.com/elata_bio) on X/Twitter and DM your wallet address to request free testnet ELTA.
***
## Next
How buying and trading works
Staking and rewards
# Stake and Earn
Source: https://docs.elata.bio/apps/users/stake-and-earn
How staking works for app tokens and what you can earn.
## What Staking Offers
Each Elata app has a staking vault where you can lock your app tokens. Staking provides:
* A way to signal commitment to the app community
* Potential access to gated features (if the builder configures it)
* Reduced circulating supply, which can support token price
* Governance weight in app-level decisions (where applicable)
***
## How To Stake
1. Go to the app's detail page on [app.elata.bio](https://app.elata.bio)
2. Navigate to the staking section
3. Choose the amount of app tokens to stake
4. Confirm the transaction
Your staked tokens are locked for the duration you choose. After the lock period ends, you can unstake and withdraw.
***
## XP and Protocol Rewards
Beyond app-level staking, you can earn XP through protocol-wide participation:
| Activity | What You Earn |
| ------------------------ | ---------------------------------- |
| App token staking | Community standing, feature access |
| ELTA staking (veELTA) | Governance voting power |
| Protocol participation | XP (non-transferable) |
| Tournament participation | Prize pool rewards (in app tokens) |
XP is earned through active participation, not passive holding. See [Incentives and Access](/apps/design/incentives-and-access) for details.
***
## Before You Stake
Check these before locking tokens:
* **Fee routing:** understand how the app's fees are split (see [Fee Flow](/apps/design/fee-flow))
* **Lock duration:** tokens are locked for the chosen period and cannot be withdrawn early
* **App health:** review holder distribution and trading volume in the app's analytics
* **Staking ratio:** a healthy range is 30-50% of circulating supply
***
## ELTA Staking (veELTA)
For protocol-level governance, you can lock ELTA tokens to receive veELTA (voting power). This is separate from app token staking.
See [veELTA and Governance](/apps/users/veelta-governance) for details on lock durations and voting mechanics.
***
## Next
Lock ELTA for voting power
Find apps to participate in
# veELTA and Governance
Source: https://docs.elata.bio/apps/users/veelta-governance
How ELTA locking translates to voting power and governance participation.
## What veELTA Is
veELTA is the vote-escrow token for Elata protocol governance. You receive veELTA by locking ELTA tokens for a chosen duration.
* Lock ELTA for 7 to 730 days
* Receive voting power proportional to your lock amount and duration
* Unlock your principal after the lock period expires
veELTA is used exclusively for governance. It does not entitle holders to fee yields.
***
## Lock Parameters
| Parameter | Range |
| ------------- | ---------------- |
| Minimum lock | 7 days |
| Maximum lock | 730 days |
| Minimum boost | 1x (at 7 days) |
| Maximum boost | 2x (at 730 days) |
***
## Boost Formula
Voting power scales linearly with lock duration:
```
boost = 1 + (lockDuration / maxDuration)
veELTA = lockedAmount * boost
```
**Example:** Locking 1,000 ELTA for 365 days gives you:
* boost = 1 + (365 / 730) = 1.5x
* veELTA = 1,000 \* 1.5 = 1,500 veELTA voting power
***
## Position Management
| Action | What It Does |
| -------- | ------------------------------------- |
| Create | Lock ELTA for a chosen duration |
| Increase | Add more ELTA to an existing lock |
| Extend | Increase the lock duration |
| Unlock | Withdraw principal after lock expires |
***
## When To Lock
Consider locking ELTA when:
* You want to participate in governance proposals
* You plan to hold ELTA long-term anyway
* You want to influence protocol direction (fee parameters, graduation thresholds, etc.)
Longer locks give more voting power per ELTA. If you are unsure about duration, start with a shorter lock and extend later.
***
## Governance Participation
veELTA voting power is used for protocol-level governance proposals submitted through the ElataGovernor contract:
* **Parameter changes:** trading fees, transfer tax caps, treasury take percentages
* **Protocol upgrades:** contract changes, new features
* **Community decisions:** incentive distributions, partnership approvals
Proposals go through a timelock (ElataTimelock) before execution, giving the community time to review changes.
***
## Next
App token staking
Governance mechanics in detail
# What Are Elata Apps?
Source: https://docs.elata.bio/apps/what-are-elata-apps
Tokenized applications on the Elata protocol with built-in economics, governance, and community tools.
## The Idea
Leading to our iOS/Android launch, we will implement an abstracted version of the user interface, running all economic features under the hood. Transfers, fees, and other economic variables will get routed under the hood and be out-of-sight from end users.
Under the hood, an Elata app is a tokenized application deployed on the Elata protocol. Each app gets its own ERC-20 token, a bonding curve for price discovery, a staking vault, contributor splits, and governance hooks.
You build the product. The protocol handles token launch, fee routing, liquidity, and community infrastructure.
***
## What You Get When You Launch
Every app deployed through the protocol receives:
| Component | What It Does |
| --------------------- | ------------------------------------------------------------------- |
| **App Token** | ERC-20 with 10M supply, distributed 50/25/25 (curve/team/ecosystem) |
| **Bonding Curve** | Constant-product price discovery until graduation at 42,000 ELTA |
| **Staking Vault** | Lets token holders stake and signal commitment |
| **Vesting Wallet** | 25% team allocation with configurable vesting |
| **Ecosystem Vault** | 25% reserved for ecosystem incentives |
| **Contributor Split** | Pull-based payout system for up to 150-200 contributors |
Total launch cost: **110 ELTA** (10 ELTA registration fee + 100 ELTA bonding curve seed). ELTA is the protocol's native token with a fixed 77M supply.
***
## How It Works
1. **Register** your app with metadata and a Safe wallet (10 ELTA).
2. **Launch** the token stack with a 100 ELTA seed.
3. **Trade** on the bonding curve while the app builds traction.
4. **Graduate** when reserves hit 42,000 ELTA, creating a locked DEX liquidity pair.
5. **Operate** with community tools: tournaments, items, staking, and governance.
The full sequence is covered in [App Lifecycle](/apps/app-lifecycle).
***
## Who This Is For
* **Builders** who want to launch a tokenized app without building their own economic infrastructure
* **Users** who want to discover, trade, and participate in app ecosystems
* **Communities** that benefit from transparent fee routing and governance
***
## Next
Builder or user?
Full launch-to-graduation arc
Start building
# What You Can Build
Source: https://docs.elata.bio/apps/what-you-can-build
App categories, real examples, and what the protocol makes possible.
## App Categories
Elata apps span four main categories. Each uses the same protocol infrastructure but targets different user behaviors.
| Category | What It Does | Example Use Case |
| ------------ | ------------------------------------------------ | ---------------------------------------------------------- |
| **Focus** | Attention training, productivity enhancement | Neurofeedback sessions that reward sustained attention |
| **Gaming** | Competitive or casual games with token mechanics | Chess with neural HUD overlays, reaction-time contests |
| **Wellness** | Health monitoring, breathing, meditation | Guided breathwork with live EEG or rPPG biometric feedback |
| **Research** | Data collection, citizen science, experiments | EEG data studies with participant token incentives |
***
## What Makes An Elata App Different
An Elata app is not just a web app with a token bolted on. The protocol gives you:
* **Price discovery** through bonding curves that graduate to DEX liquidity
* **Fee routing** that splits revenue between contributors and the protocol treasury
* **Community tools** like tournaments, NFT items, and staking
* **Governance hooks** so token holders can participate in app-level decisions
You focus on the product experience. The protocol handles the economics.
***
## Building With Biometrics
Many Elata apps use the [Biometric SDK](/sdk/overview) to add real-time biosignal processing:
* **Camera rPPG** for heart rate, stress, and arousal feedback (no hardware needed)
* **EEG via Web Bluetooth** for brain-signal analysis with Muse headsets
* **Signal processing** for band powers, calmness models, and alpha detection
These integrations are optional. You can build an Elata app with or without biometrics.
***
## Next
How apps launch and graduate
Start building
See what's live
# Development
Source: https://docs.elata.bio/development
Preview changes locally to update your docs
**Prerequisites**:
* Node.js version 19 or higher
* A docs repository with a `docs.json` file
Follow these steps to install and run Mintlify on your operating system.
```bash theme={null}
npm i -g mint
```
Navigate to your docs directory where your `docs.json` file is located, and run the following command:
```bash theme={null}
mint dev
```
A local preview of your documentation will be available at `http://localhost:3000`.
## Custom ports
By default, Mintlify uses port 3000. You can customize the port Mintlify runs on by using the `--port` flag. For example, to run Mintlify on port 3333, use this command:
```bash theme={null}
mint dev --port 3333
```
If you attempt to run Mintlify on a port that's already in use, it will use the next available port:
```md theme={null}
Port 3000 is already in use. Trying 3001 instead.
```
## Mintlify versions
Please note that each CLI release is associated with a specific version of Mintlify. If your local preview does not align with the production version, please update the CLI:
```bash theme={null}
npm mint update
```
## Validating links
The CLI can assist with validating links in your documentation. To identify any broken links, use the following command:
```bash theme={null}
mint broken-links
```
## Deployment
If the deployment is successful, you should see the following:
## Code formatting
We suggest using extensions on your IDE to recognize and format MDX. If you're a VSCode user, consider the [MDX VSCode extension](https://marketplace.visualstudio.com/items?itemName=unifiedjs.vscode-mdx) for syntax highlighting, and [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) for code formatting.
## Troubleshooting
This may be due to an outdated version of node. Try the following:
1. Remove the currently-installed version of the CLI: `npm remove -g mint`
2. Upgrade to Node v19 or higher.
3. Reinstall the CLI: `npm i -g mint`
Solution: Go to the root of your device and delete the `~/.mintlify` folder. Then run `mint dev` again.
Curious about what changed in the latest CLI version? Check out the [CLI changelog](https://www.npmjs.com/package/mintlify?activeTab=versions).
# Elata Points
Source: https://docs.elata.bio/elata-eco-points
Earned, non-transferable participation points that unlock early access to app launches and track real contribution across Elata.
This page reflects the current `dev` branch of `elata-protocol`.
Some internal files and parameters still use `XP` naming. On this page, **Points** and **XP** refer to the same participation primitive.
Elata Points are Elata's onchain participation primitive. They are earned, not bought, and they stay bound to the wallet that earned them.
At the contract level, `ElataPoints` is a non-transferable token with voting checkpoints. Transfers between wallets are disabled, while authorized operators can award, revoke, or publish claimable Merkle distributions.
## Why Points exist
New app curves can reserve the first part of a launch for wallets that hold enough points.
Points show real participation because they cannot be traded or transferred.
Points are checkpointed for governance-style voting and historical balance lookups.
The system favors real participation over simple buying power.
## Core rules
| Property | Current behavior |
| ------------- | ------------------------------------------- |
| Contract | `src/experience/ElataPoints.sol` |
| Name / symbol | `Elata Points` / `POINTS` |
| Transferable | No |
| Tradeable | No |
| Decimals | 18 |
| Minting paths | Operator award, signed update, Merkle claim |
| Revocable | Yes, by authorized operators |
| Checkpointed | Yes |
Points are soulbound, but they are not irreversible. They cannot be transferred, but they **can** be revoked by an authorized operator to correct bad data, fraudulent awards, or an incorrect distribution.
## How points are issued
### 1. Direct operator awards
Authorized operators can mint points directly to a wallet.
Use this when the protocol already knows a wallet earned points and wants to credit it immediately.
### 2. Signed awards
The contract also supports signed point updates.
This lets an authorized operator approve an award offchain, while the user or frontend submits the claim onchain later.
### 3. Merkle distributions
For larger batches, operators can publish a Merkle root for a distribution.
Users then claim their allocation by submitting the distribution ID, amount, and Merkle proof.
This is the cleanest path for epoch-based distributions because it keeps onchain storage small while still letting each user verify and claim their own allocation.
## Early access on app launches
Points matter most during the first phase of a new app token launch.
By default, each bonding curve uses:
* **100 points minimum**
* **6 hour early-access window**
During that window, the bonding curve checks the buyer's points balance. If the wallet is below the threshold, the purchase fails.
After the window ends, the curve is open to everyone.
The launch gate is configurable through governance, so the minimum points threshold and early-access duration can change over time.
## Claim flow
1. The protocol calculates a new points distribution offchain.
2. An authorized operator generates the canonical Merkle JSON and publishes a root onchain.
3. The frontend loads the matching distribution file and verifies it against the onchain distribution metadata.
4. The user submits a claim with their proof.
5. The contract marks the claim as used, mints the points, and auto-delegates to self if the wallet has not delegated before.
## What points are not
* Points are **not** ELTA
* Points are **not** veELTA
* Points are **not** transferable rewards
* Points are **not** a market asset
Think of them as earned access and participation weight, not money.
## FAQ
No. Points are earned or claimed through protocol-defined distribution paths. They are not designed to be bought on a market.
No. Transfers are disabled at the contract level.
Yes, in limited cases. Authorized operators can revoke points to correct bad awards or a faulty distribution.
Not for basic participation. Points mainly matter during gated launch windows and in any reputation-weighted flows built on top of the points contract.
Points stay attached to the wallet address that earned them. Losing access to that wallet means losing access to those points.
## Security model
* Transfers are disabled in the token contract itself
* Only authorized operator addresses can award, revoke, or publish Merkle roots
* Distribution claims are one-time per wallet per distribution
* Published distribution metadata can be matched against the onchain data hash
* Points auto-delegate on first mint or claim so checkpoint tracking works without extra user steps
## Related pages
ELTA supply, app launch costs, and fee routing.
The full protocol lifecycle from app launch to fee flow.
Local setup and contract-level development docs.
Get a local environment running and explore the protocol.
# Elata Points
Source: https://docs.elata.bio/elata-points
Non-transferable reputation points that gate early access, weight funding votes, and track meaningful participation across the Elata ecosystem.
Elata Points are the protocol's on-chain reputation primitive. Points are earned by participating in the ecosystem: playing apps, submitting data, and engaging in governance and therefore, cannot be bought, sold, or transferred. Your points balance signals genuine contribution and unlocks privileged access across the protocol.
Points live in the `ElataPoints` smart contract (`src/experience/ElataPoints.sol`) and is soulbound, meaning it is permanently bound to the address that earned it.
***
## Why Points Matters
Points holders meeting the minimum threshold get a 6-hour head start on every new app token launch, buying from the bonding curve before the general public
Points determines your voting weight in the weekly LotPool funding round, where the community allocates treasury ELTA to research proposals and development grants
Because points are non-transferable and earned exclusively through participation, they serve as an unfakeable signal of commitment to the ecosystem
***
## How Points Work
Unlike ELTA (which is a tradeable ERC-20), points are a non-transferable point balance tracked on-chain. It accumulates over time as you interact with the protocol and can never be sent to another address or listed on a market.
### Core Properties
| Property | Detail |
| :----------------- | :----------------------------------------------------------------------------------------------------------------- |
| **Contract** | [`ElataPoints.sol`](https://github.com/Elata-Biosciences/elata-protocol/blob/vNext/src/experience/ElataPoints.sol) |
| **Token standard** | Non-standard (non-transferable, soulbound) |
| **Transferable** | No |
| **Burnable** | No. Points are permanent once earned |
| **Mintable by** | Authorized protocol operator(s) via role-based access |
| **Chain** | Same chain as the core Elata Protocol deployment |
Points cannot be purchased with ELTA or any other token. Any claim to sell or transfer points are fraudulent. The contract enforces non-transferability at the smart contract level.
***
## Earning points
Points are awarded for meaningful, verifiable participation across three categories:
Engage with neurotech applications in the Elata ecosystem, such as neurofeedback sessions, brain-training games, meditation experiences, EEG-based interactions, and more. Each qualifying session or milestone earns points proportional to the depth of engagement.
Submit biosensor data (EEG, eye-tracking, or other biometric signals) to research experiments or app-level data pipelines. Data contributions that pass validation earn points, rewarding users who actively power the ecosystem's scientific infrastructure.
Vote on proposals, participate in funding rounds, and engage in protocol discussions. Active governance participants earn points that further amplifies their future influence.
Points are designed to reward consistent, genuine participation rather than one-time actions. Engage regularly across multiple categories for the fastest accumulation.
***
## Early Access Gating
One of the most impactful functions for points is gating access to new app token launches on the bonding curve.
When a new app is launched via `AppFactory`, its token begins selling on a constant-product bonding curve (`x*y=k`). The initial supply is 10,000,000 app tokens with 50% allocated to the curve.
For the first 6 hours after launch, only wallets holding at least 100 XP can buy from the curve. This gives active ecosystem participants first access at the earliest (and lowest) prices.
After the 6-hour window, the curve opens to all buyers regardless of XP balance. The 1% trading fee applies to all trades throughout the curve's lifecycle.
Once the curve collects 42,000 ELTA, liquidity auto-deploys to Uniswap V2 and LP tokens are locked for 2 years. Free-market trading begins.
The default gate parameters (100 points minimum, 6-hour window) are configurable through governance. These values represent the current protocol defaults on the `vNext` branch.
***
## Funding Allocation
Points play a central role in how the protocol supports capital expenditures:
Each week, the LotPool governance mechanism opens a funding round where the community votes to allocate ELTA from the treasury to submitted proposals — research studies, open-source tools, neurotech hardware, and more.
Your voting weight in LotPool is determined by your points balance, not your ELTA holdings or veELTA position. This separates financial stake from contribution-based influence, ensuring that the people who have participated most actively in the ecosystem have the greatest say over where funding goes
Every point counts equally. Your share of total points supply determines your proportional weight in each funding round
Funding rounds run on a weekly cycle. Proposals are submitted, the community votes, and winning proposals receive ELTA from the treasury
***
## Points vs veELTA vs ELTA
The protocol uses three distinct primitives for different functions. Understanding how they interact is key to navigating the ecosystem:
| | **ELTA** | **veELTA** | **Elata Points** |
| :------------------ | :----------------------- | :------------------------------- | :---------------------------- |
| **Type** | ERC-20 token | Vote-escrowed ELTA | Non-transferable points |
| **Transferable** | Yes | No | No |
| **How to get** | Buy, earn, or receive | Lock ELTA (7–730 days) | Participate in ecosystem |
| **Governance role** | None directly | On-chain proposal & voting power | Funding vote weight (LotPool) |
| **Revenue share** | None directly | 15% of protocol fees | None directly |
| **Launch access** | Required to buy on curve | No special access | Gates 6-hour early window |
| **Boost mechanic** | N/A | 1×–2× linear by lock duration | Accumulates over time |
ELTA is the economic primitive (money). veELTA is the governance primitive (voting power + revenue). XP is the reputation primitive (contribution signal + funding influence). All three work together to align incentives across the ecosystem.
***
## Points Distribution Architecture
Points are minted by authorized operators — protocol-level contracts and admin roles that can verify qualifying actions. The distribution system is designed with these principles:
The `ElataPoints` contract uses a role-based access pattern. Only addresses granted the operator role can mint new XP. This role is managed by the protocol's multisig admin, ensuring no single actor can inflate XP balances arbitrarily.
Wherever possible, point-earning actions are verified on-chain. For example, completing a bonding curve purchase, voting on a governance proposal, or staking ELTA. Off-chain actions (such as data submissions) flow through an authorized backend operator that validates participation before minting.
Unlike ELTA (which has a fixed 77M supply), XP has no hard cap. However, points can only be minted in response to verified actions. There is no passive emission or time-based inflation. Growth is organic and participation-driven.
Like all protocol contracts, `ElataPoints` is non-upgradeable. The minting logic and non-transferability guarantees are permanently enforced at the contract level with no proxy pattern or admin override.
***
## Frequently Asked Questions
No. Points cannot be purchased, traded, or transferred. It is exclusively earned through verified participation in the ecosystem. Any entity claiming to sell points are fraudulent.
Points are bound to your on-chain address. If you lose access to your wallet, your points are not recoverable. Secure your keys carefully.
No. Once earned, points are permanent and do not decrease. Your balance can only grow as you continue to participate.
No. You can buy ELTA, trade on bonding curves (after the early-access window), stake, and participate in governance without any points. Points provide additional benefits, such as early launch access and funding vote weight. However, points are not a hard requirement for basic protocol usage.
The current default threshold is 100 Points. This grants access to the 6-hour early buy window on new app token launches. Both the threshold and the window duration are configurable through governance.
No. All Elata protocol contracts are non-upgradeable and immutable after deployment. The rules governing points minting and non-transferability cannot be changed post-deployment.
***
## Security Considerations
The points system inherits the same security posture as the rest of the Elata Protocol:
* **Non-upgradeable**: No proxy patterns, no admin upgrade keys
* **Role-based minting**: Only authorized operator addresses can mint points, managed by multisig
* **Soulbound enforcement**: Transfer functions are disabled at the contract level, not just by convention
* **Open source**: Full contract source is available for public review and audit
The protocol is currently pending an external security audit before mainnet deployment. All contracts, including `ElataPoints`, are open-source on the `vNext` branch and available for community review.
Explore the ElataPoints contract on the `vNext` branch of the Elata Protocol repository.
***
## Related Pages
Lock ELTA for veELTA: governance voting power and protocol revenue share
Full protocol architecture, app lifecycle, and fee flow overview
ELTA supply, distribution, and economic model breakdown
# New file
Source: https://docs.elata.bio/home/ELTA-Economy
Description of your new file.
# Structure
Source: https://docs.elata.bio/home/governance/structure
## Governance Structure
Elata governance uses on-chain governor and timelock contracts with veELTA voting power.
## Core Parameters (Current Defaults)
| Parameter | Value |
| ------------------ | -------------- |
| Voting delay | 1 day |
| Voting period | 7 days |
| Proposal threshold | 0.1% of supply |
| Quorum | 4% of supply |
| Timelock delay | 48 hours |
## Notes
* veELTA voting power comes from ELTA lock amount and lock duration.
* Critical protocol parameters are timelock-gated.
# Operating Teams
Source: https://docs.elata.bio/home/governance/teams
***
Elata’s internal units are referred to as teams - which are essentially working groups - composed of Elata members. They are tasked by the member community to perform a set of thematic activities in line with the mission and vision of Elata.
**JOINING A TEAM**
If you are interested in joining an Elata team, please complete [this](https://tally.so/r/wkPZ71) form to declare your interest.
The following teams comprise Elata's organizational structure:
1. **Development team**
2. **Operations team**
3. **Engineering team**
4. **Engagement team**
Teams are supposed to encourage member involvement. Their purpose is to incentivize efficiency and results by rewarding members and contributors based on their performance.
***
## Development team
Responsible for screening, segmenting, and seeking projects for Elata to finance and develop, and is responsible for the following:
* Critically analyzes inbound and outbound investment opportunities; uses scientific and clinical analysis to gauge likelihood of regulatory approval and positive patient outcomes.
* Organizes, reviews, and segments all inbound and outbound opportunities for Elata’s research dealings.
* Works together with engagement and engineering teams to ensure opportunities pursued fall in line with community interest.
* Works together with operations team and licensing offices/project managers to assume IP ownership.
***
## Operations team
Operations ensures security practices, legal needs, promotes transparency, and fulfills ancillary needs that relieve logistical burden from other teams, and is responsible for:
* Asset management; oversees technical and product development of all digital, physical, and intangible assets owned or managed by Elata, ensuring they align with the DAO's strategic goals.
* Manage legal affairs related to Elata's operations, including compliance, contracts, and regulatory affairs.
* Design and refine the economic models, including token distribution, incentives, and financial sustainability strategies.
* Implement and maintain security measures to protect assets, data, and member information from threats, ensuring the integrity and safety of all operations.
***
## Engineering team
The Engineering Team builds and maintains the core technology stack powering Elata’s mission. From open-source neuroimaging hardware that captures brainwave or behavioral data to decentralized software that processes and protects these data streams, this team bridges cutting-edge innovations in neuroscience, ML/AI, cryptography, and web services. They are responsible for the following:
* Developing open-source hardware for capturing relevant biomarkers or treating mental health issues (e.g., EEG, TMS, light/actigraphy sensors).
* Designing secure, privacy-preserving data pipelines, including those leveraging zero-knowledge proofs.
* Creating machine learning models aimed at mental health applications, especially computational neuroscience and psychiatry.
* Maintaining and expanding Elata’s suite of web services, such as our open-source news aggregator, contributor portals, and governance tools.
* Working in tandem with other teams to translate open-source insights into practical and profitable solutions that sustain the DAO.
***
## Engagement team
The engagement team carries a broad responsibility which is to promote Elata initiatives across various channels, act as liaison between community members and projects, provides updates to community, and ensures collaboration with external partnerships. They are responsible for the following:
* Educate and raise awareness about the significance of Elata Biosciences through targeted outreach and educational programs.
* Cultivate and sustain a vibrant, supportive community, ensuring active member participation and retention.
* Promote the growth and visibility of Elata, enhancing its reputation and reach within relevant communities.
* Drive business development by forging partnerships and collaborations that align with Elata’s mission and can amplify its impact.
***
## Stewardship & Core
Each team is guided by a steward, which are elected by [ELTA](/o/WFdlYgzLkzXseCEviHTq/s/Jfp0pu5VZ5XDDYs5IrR4/~/changes/108/operating-model/the-elta-token/elta-overview) holders. Stewards act as leaders, ensuring the smooth operation and direction of their respective working groups.
ElataCore is an advisory group that represents various groups of stakeholders. They are not a “Board of Directors”, but rather the opposite: ElataCore supports the Stewards. ElataCore is accountable to tokenholders, who define the scope of ElataCore's executive power over operational decisions and may refine these at any time in a new governance proposal.
Each ElataCore member is chosen to support a specific strategic objective of the DAO. In that sense, holders of our tokens are represented by ElataCore, but the token holders are the overseeing and superior entity that governs ElataCore through proposals.
To facillitate impartial decision-making, a clear separation between ElataCore and stewards will be indicated in Governance initiatives. This allows ElataCore to prioritize the highest-impact initiatives while stewards provide unbiased oversight. However, resource and contributor limitations may necessitate some overlap. The ultimate goal remains a clear and practical division between the two groups.
# Integrate your device with the Elata SDK
Source: https://docs.elata.bio/integrate
Integrate a headset or biosignal device into the Elata EEG BLE stack with a simple, reliable path.
This guide is for device companies that want their hardware to work with the
Elata SDK.
The target is simple: your device should plug into the shared Elata transport
boundary and emit stable frames that apps can trust.
Review the shared frame and transport types before writing adapter code.
Confirm Web Bluetooth, browser, and platform constraints first.
## What You Are Building
A production-ready integration should do three things well:
1. connect reliably
2. stream frames in a stable, predictable format
3. recover cleanly when the device, browser, or radio link misbehaves
In Elata terms, that means converging on `HeadbandTransport` and
`HeadbandFrameV1` instead of exposing vendor-specific packet layouts directly.
Apps should depend on the shared Elata transport contract, not on raw vendor
packets. That keeps downstream integrations simple and makes device support
easier to maintain.
## The Fast Path
Decide whether your device belongs upstream in `eeg-web-ble`, in a separate
package, or in an app-local adapter. Use
[Integration Paths](./integration-paths) to make that decision early.
Gather your GATT services, characteristics, packet format, sample rate,
channel names, timestamp behavior, and known firmware quirks. See
[Protocol Requirements](./protocol-requirements).
Build the device layer that handles discovery, session setup, packet
decoding, and frame emission. Start from
[Adapter Implementation](./adapter-implementation).
Test connect, start, stop, disconnect, malformed packets, reconnects, and
frame correctness before tuning anything downstream. Use
[Testing and Validation](./testing-and-validation).
Package the integration with platform notes, tested firmware versions, and
support expectations. See [Submission and Support](./submission-and-support).
## What You Need Before You Start
| Requirement | Why it matters |
| ------------------------ | -------------------------------------------------------------------------------- |
| Device protocol details | You need a complete byte-level picture before implementation starts. |
| A supported browser path | Web Bluetooth requires Chrome or Edge in a secure context. |
| Stable channel metadata | Apps need consistent channel order, names, and sample rates. |
| A clear ownership model | Decide early whether this will live upstream, in a vendor package, or privately. |
If you have not reviewed the current BLE flow yet, read
[EEG BLE Getting Started](/sdk/eeg-web-ble/getting-started) first.
## Guide Map
Choose where the integration should live and how it should be owned.
The shared interfaces your device must satisfy.
The protocol and metadata inventory to collect before coding.
Build the device adapter and connect it to `BleTransport`.
Make the integration reliable under normal and failure conditions.
Hand off the integration with the docs and caveats partners need.
## Reliability First
A device integration is not done when it streams once on a happy path.
It is done when:
| Done means | Notes |
| -------------------------------- | -------------------------------------------------------------- |
| `connect()` is predictable | discovery and session setup succeed or fail clearly |
| `start()` produces stable frames | row width, channel order, and sample rate stay correct |
| `stop()` is clean | the session stops without leaving the device in a bad state |
| `disconnect()` is clean | the browser session releases resources correctly |
| failures are surfaced clearly | apps can react to disconnects, retries, and unsupported states |
## Related Docs
* [Headband Transport](/sdk/eeg-web/headband-transport)
* [EEG BLE Getting Started](/sdk/eeg-web-ble/getting-started)
* [Muse Device](/sdk/eeg-web-ble/muse-device)
* [Compatibility](/sdk/operations/compatibility)
* [Troubleshooting](/sdk/operations/troubleshooting)
## Next
Pick the right ownership and packaging model before coding.
Confirm the frame and lifecycle expectations your adapter must satisfy.
# Integration Paths
Source: https://docs.elata.bio/integration-paths
Choose the right packaging and ownership model for a device integration.
Use this page before you write adapter code.
The right path depends on whether the integration is broadly useful, whether it
needs vendor SDKs or native bridges, and whether it will be maintained as a
public SDK surface.
## Recommended Default
If the device is likely to be useful to the wider Elata SDK audience, put it
upstream in `@elata-biosciences/eeg-web-ble`.
That keeps transport behavior centralized and avoids parallel adapters across
multiple apps.
## Choose By Outcome
| Path | Best when | Why |
| --------------------------------------- | ------------------------------------------------------------------- | --------------------------------------------- |
| Upstream device module in `eeg-web-ble` | The device is broadly useful and works through browser BLE | Best default for public SDK support |
| Separate package under `packages/` | You need a vendor SDK, a license gate, or a localhost/native bridge | Keeps heavier integrations isolated |
| App-local adapter | You are proving a concept or shipping a private integration | Fastest path when no shared package is needed |
## Path 1: Upstream in `eeg-web-ble`
Choose this when:
* the device can work through Web Bluetooth in a browser
* the integration is likely to be reused by more than one app
* you want one shared implementation of transport behavior
This path usually means:
1. add a device module under the BLE package
2. reuse `BleTransport`
3. add mocked tests
4. update consumer docs and device notes
This is the best path for a public, durable integration that should feel like
a first-class part of the SDK.
## Path 2: Separate Package
Choose this when the device needs more than a clean browser BLE flow.
Typical reasons:
| Situation | Why a separate package helps |
| --------------------------------- | ----------------------------------------------------- |
| Licensed or heavy vendor SDK | Keeps extra dependencies out of the main BLE package |
| Localhost helper or native bridge | Lets you ship a bridge-specific transport cleanly |
| Independent release cadence | Lets the vendor or partner own versioning and support |
The package should still expose a `HeadbandTransport`, or a thin wrapper around
one, so app code stays consistent with the rest of the Elata stack.
## Path 3: App-Local Adapter
Choose this when speed matters more than reuse.
This is a good fit for:
* a private customer deployment
* a fast proof of concept
* evaluation work before a public package exists
Keep the contract the same even if the code stays private. That makes it easier
to move upstream later.
## Decision Order
If multiple apps would benefit, start by assuming an upstream path.
If Web Bluetooth will not work, or if a native bridge is required, move to a
separate package.
Shared support usually belongs upstream. Vendor-owned release lines often fit
better in a separate package.
No matter where the code lives, the app-facing contract should still be
`HeadbandTransport` and `HeadbandFrameV1`.
## Paths to Avoid
Do not choose an app-local adapter just because it is easy in the first hour.
That usually creates extra work later if the integration becomes public.
Do not push a heavy, bridge-based, or licensed integration into the shared BLE
package either. Keep the public package lean.
## Related Docs
* [EEG BLE Getting Started](/sdk/eeg-web-ble/getting-started)
* [Transport Contract](./transport-contract)
* [Adapter Implementation](./adapter-implementation)
* [Compatibility](/sdk/operations/compatibility)
## Next
Review the shared interfaces your chosen path must still satisfy.
Gather the protocol details you need before implementation begins.
# How Elata Works
Source: https://docs.elata.bio/learn/how-it-works
Current protocol architecture and value flow
## Architecture
Three practical layers:
1. **Launch layer**: `AppFactory`, `AppRegistry`, `AppBondingCurve`, `AppToken`
2. **Fee layer**: `FeeCollector`, `FeeSwapper`, `ContributorSplit`
3. **Governance layer**: `ELTA`, `VeELTA`, `ElataPoints`, governor/timelock/config
***
## App Lifecycle
1. Register app and contributor split.
2. Launch token stack (`10,000,000` app supply).
3. Run active curve distribution (`x*y=k`).
4. Graduate to LP at target/deadline.
5. Continue app-level fee routing post-graduation.
***
## Fee Flow
```mermaid theme={null}
graph LR
A[LaunchAndTradingAndTransferFees] --> B[FeeCollector]
B --> C[FeeSwapper]
C --> D[Treasury]
C --> E[ContributorSplit]
```
Routing policy:
* launch fee -> treasury
* app-revenue fee kinds -> contributors + treasury (default `80/20`)
* paused app -> treasury
For full details on fee kinds, routing, and configuration, see the [Fee Flow for Apps](/apps/design/fee-flow) reference.
***
## XP and veELTA
* XP gates early buys during launch windows (default `100 XP`, `6h`).
* veELTA provides time-weighted voting power via ELTA locks (`7-730` days, `1x-2x` boost).
***
## Security Posture
* fixed ELTA supply (`77,000,000`)
* explicit curve lifecycle states
* LP lock on graduation
* explicit fee-kind taxonomy
* per-app fee accounting (no unbounded global sweeps)
# What is Elata?
Source: https://docs.elata.bio/learn/protocol-overview
## Overview
Elata is an app-launch protocol designed specifically for biosignal and neurotech applications.
It combines three capabilities that don't currently exist together anywhere: browser-based biosignal acquisition SDKs, on-device processing that keeps all user data local, and an economic app-launch system that lets anyone in the world ship a neurotech product with built-in monetization.
Blockchain provides the best execution framework for this project because it ensures builders can access instant price discovery for their inventions, users are able to parttake in the economic upside they help generate when using apps, and most importantly to ensure anyone in the world can participate with ease.
Network: Live on Base Sepolia (testnet). Mainnet on Base coming soon.
npm install an SDK, launch an app with a native token, and ship in minutes.
Find apps, trade tokens, stake for rewards, participate in governance.
***
## Why This Exists
Consumer devices have quietly become biosignal acquisition tools. When modeled and executed well, smartphone camera can accurately infer heart rate measurements, respiratory rate, track pupil dialation and eye movements, and even get relatively accurate inferences on cognitive states. Furthermore, at-home consumer EEG headsets can capture brainwave data that required clinical-grade hardware a decade ago.
The sensors are already in people's pockets. What doesn't exist yet is a standardized application layer for building on top of these signals.
Today, every company that processes biosignal data — from meditation apps to driver-drowsiness systems — does so by extracting user data to centralized servers. Users never see the value their data generates, and Builders must recreate proprietary signal-processing pipelines from scratch. And anyone outside of well-funded markets is simply excluded.
Elata addresses this with three design decisions:
1. Published SDKs that standardize biosignal acquisition across devices, installable as npm packages.
2. **On-device processing** where all raw data stays on the user's device and never gets extracted — by anyone, including Elata. Model improvements aggregate across the network via parameter updates, not raw signals, so the network gets smarter without compromising privacy.
3. **Permissionless app launch** with deterministic on-chain mechanics — bonding curves, fee routing, and graduation — so any builder, anywhere, can ship a biosignal app with aligned economics and no gatekeepers.
***
## What Elata Does
### Biosignal Acquisition
Elata ships published npm packages for browser-based biosignal capture:
| Package | What It Does |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `@elata-biosciences/rppg-web` | Heart rate, respiratory rate, and stress measurement from any standard webcam via rPPG. No wearable hardware required. |
| `@elata-biosciences/eeg-web` | Browser-based EEG data acquisition for web applications. |
| `@elata-biosciences/eeg-web-ble` | Connect consumer EEG headsets via Web Bluetooth directly in the browser. |
These packages standardize data across devices into a shared format. Hardware makers gain an app ecosystem; builders get a shared data layer instead of rebuilding signal processing from scratch.
All processing runs in the browser, on the user's device.
### Data Architecture
Raw biodata never leaves the user's device. Machine learning runs at the edge using on-device inference.
Model parameter updates (meaning not raw data) aggregate across the network. Each user's local model improves from the network's collective learning without any individual's signals being transmitted, stored, or made accessible to anyone.
Not only does this mechanism ensure privacy and security of sensitive data, but it also uniquely addresses the nuances inherent to biodata (namely the unique and often unpredictible differences between individual users' biological profiles).
More users means better models for everyone, without data extraction.
### App Launch Protocol
Short version: **register app → launch app token → trade on curve → graduate to LP**.
* **Pay 110 ELTA total** (`10` creation fee + `100` seed). The protocol deploys the entire app stack automatically.
* **Launch a 10,000,000 supply app token**. Default allocation: `50%` curve, `25%` vesting, `25%` ecosystem.
* **1% trading fee** routed through the fee pipeline: `FeeCollector` → `FeeSwapper` → `RewardsDistributor`.
* **Revenue split**: `70%` to app stakers, `15%` to veELTA holders, `15%` to treasury.
* No smart contract development needed. Account abstraction can hide wallets and gas entirely from end users.
* **Browse apps** on the [App Store](https://app.elata.bio).
* **Buy app tokens** during the active bonding-curve phase.
* **Stake app tokens** to receive `70%` of the trading fees that app generates.
* **Lock ELTA for veELTA** (`7`–`730` days, `1x`–`2x` boost) for protocol-level fee share and governance voting power.
* **Track graduation** — at `42,000 ELTA`, LP is created and locked for `2 years`.
***
## Why Onchain?
Two properties of this design make it such that any design except for blockchain would be incredibly impractical:
**Value return instead of value extraction:** Users who stake into apps are elligible to recieve a portion of the economic value those apps generate. As is the case with any type of bio-data, the traditional model creates a perverse incentive for companies to harvest users data without returning value. Elata inverts this: data stays on the user's device, and economic participation is opt-in.
**Permissionless global access:** Anyone in the world can build on, use, or stake into biosignal applications without providing personal information or requiring a specific form of identification. Builders and users may participate pseudonymously if they please. To prevent value extraction by bad actors, all developers implement core protocol primitives via open APIs that call ecosystem-native contracts only, and the entire onboarding process is completely seamless (no ID, SSN, etc) for all participants.
***
## Key Components
| Component | What It Does |
| ------------------ | ------------------------------------------------------------------------------------------------------------ |
| **ELTA** | Base protocol token. Fixed `77,000,000` supply, minted once at deployment. |
| **veELTA** | Vote-escrow token from locking ELTA (`7`–`730` days). Linear boost `1x`–`2x`. Governance voting + fee share. |
| **App Tokens** | Per-app tokens. Fixed `10,000,000` supply. Launched on bonding curves. |
| **Bonding Curves** | Constant-product (`x·y=k`) launch trading until graduation at `42,000 ELTA`. |
| **Fee Pipeline** | `FeeCollector` → `FeeSwapper` → `RewardsDistributor`. `1%` trading fee. `70/15/15` split. |
| **Biometric SDKs** | `rppg-web`, `eeg-web`, `eeg-web-ble`. Browser-based biosignal acquisition. On-device processing. |
| **Elata Open EEG** | Open-source 8-channel EEG hardware (Raspberry Pi 5 + ADS1299). Rust signal processing. |
***
## Choose your path
Step-by-step walkthrough of the protocol lifecycle
ELTA supply, distribution, and ecosystem mechanics
Elata-native rPPG, EEG, and BLE libraries
Select your modality and deploy a neurosignal app in minutes
Fee routing, staking economics, and protocol information modules
Collection of discontinued and/or active Open Source Projects created by Elata
***
## Links
* [Apps Ecosystem](https://app.elata.bio)
* [GitHub](https://github.com/elata-biosciences)
* [Discord](https://discord.gg/GqS9CstffK)
* [Twitter/X](https://x.com/elata_bio)
# Revenue Model
Source: https://docs.elata.bio/learn/revenue-model
How fees are collected and routed in the current protocol
## Overview
The current fee system uses explicit fee kinds and a two-step pipeline:
1. `FeeCollector` records pending balances per app, fee kind, and asset.
2. `FeeSwapper` routes each fee kind according to policy.
No inflation is required for this flow; fees come from protocol usage.
***
## Fee Sources
| Source | Mechanic |
| ----------------- | ----------------------------------------------- |
| Launch fee | `10 ELTA`, tagged `LAUNCH_FEE` |
| Curve trading fee | Configured in `AppFeeRouter` (default `1%`) |
| Transfer tax | LP-keyed in `AppToken` (default `1%`, max `2%`) |
| Module fees | `CONTENT_SALE`, `TOURNAMENT_FEE`, `OTHER` |
***
## Routing Policy
```mermaid theme={null}
graph TD
A[FeesFromApps] --> B[FeeCollector]
B --> C[FeeSwapper]
C --> D[LaunchFeeToTreasury100]
C --> E[AppRevenueToContributorSplit]
C --> F[AppRevenueToTreasury]
```
### Rules
* `LAUNCH_FEE` routes `100%` to treasury.
* App-revenue fee kinds route to contributor split + treasury.
* Default app-revenue take is `80% contributors / 20% treasury`.
* If app is paused in `AppRegistry`, routing is `100%` treasury.
***
## Worked Example
Assume a curve buy where `actualEltaIn = 1,000` and fee bps is `100`:
| Component | Amount |
| ------------ | ------------- |
| Trade amount | 1,000 ELTA |
| Trading fee | 10 ELTA |
| Fee kind | `TRADING_FEE` |
With default app-revenue routing:
| Recipient | Amount |
| ---------------- | ------ |
| ContributorSplit | 8 ELTA |
| Treasury | 2 ELTA |
Contributors claim from `ContributorSplit` based on configured shares.
***
## Key Defaults
| Metric | Value |
| --------------------------- | --------------------------------- |
| Launch fee | `10 ELTA` |
| Seed ELTA | `100 ELTA` |
| Curve fee baseline | `1%` |
| Transfer tax baseline | `1%` |
| Launch fee routing | `100% treasury` |
| App-revenue routing default | `80% contributors / 20% treasury` |
***
## Next
Full lifecycle and equations
Builder launch flow
# Tokenomics
Source: https://docs.elata.bio/learn/tokenomics
ELTA token supply, distribution, and veELTA mechanics
## ELTA Token
ELTA is the protocol base token used for launches, curve trading, and governance/staking flows.
**Contract**: ELTA is currently deployed on Ethereum Sepolia (testnet). View on [Etherscan](https://sepolia.etherscan.io/address/0x2AEb03A678A1e1E99a2AeEb4CeFCD0263A2D3587). Mainnet deployment coming soon.
***
## Supply
| | |
| -------------- | ------------------------- |
| **Max supply** | 77,000,000 ELTA |
| **Decimals** | 18 |
| **Model** | Fixed supply, minted once |
Hard cap. No inflation path in current token contract.
## What ELTA Does
**Launch apps** — `110 ELTA` total (`100` seed + `10` creation fee)
**Trade** — All app tokens trade against ELTA
**Stake** — Lock ELTA as veELTA for governance-weighted voting power
**Govern** — vote through governor/timelock system
***
## veELTA
Lock ELTA → get veELTA. Longer lock = more veELTA.
| | |
| ------------ | ------------------ |
| **Min lock** | 7 days |
| **Max lock** | 730 days (2 years) |
| **Boost** | 1x to 2x |
### Math
$$
\text{veELTA} = \text{ELTA} \times \left(1 + \frac{\text{Days Locked}}{730}\right)
$$
| Lock | Boost | 1,000 ELTA → |
| ------- | ----- | ------------ |
| 7 days | 1.01x | 1,010 veELTA |
| 1 year | 1.5x | 1,500 veELTA |
| 2 years | 2x | 2,000 veELTA |
### Properties
* **Non-transferable** — can't sell or send veELTA
* **One lock per wallet** — can add to it or extend, but only one
* **No decay** — veELTA stays constant until unlock
* **Full return** — get all your ELTA back when lock expires
No early unlock. Choose duration carefully.
***
## App Tokens
Each app launches with its own ERC-20:
| | |
| ------------------- | ------------------ |
| **Supply** | 10,000,000 |
| **Bonding curve** | 50% |
| **Vesting wallet** | 25% |
| **Ecosystem vault** | 25% |
| **Graduation** | 42,000 ELTA raised |
### Transfer Fee
Default transfer tax is `1%` (max `2%`) and is LP-keyed: it only applies when transfers touch allowlisted liquidity-pool addresses.
***
## Fee Sources
| Source | Rate |
| ------------- | ---------------------- |
| **Trading** | 1% |
| **Launches** | 10 ELTA |
| **Transfers** | 1% (default) |
| **Modules** | Configurable by module |
Current routing policy in V2 pipeline:
* `LAUNCH_FEE` -> `100%` treasury
* App-revenue fee kinds -> default `80%` contributors / `20%` treasury (governance configurable)
***
## Summary
* **77M max** — fixed forever
* **10M app supply per launch** — fixed by current factory defaults
* **Lock longer = more voting power** — veELTA boost up to 2x
* **Explicit fee kinds** — launch fee and app-revenue kinds are routed differently
***
## Next
Fee mechanics and math
Start earning
# New file
Source: https://docs.elata.bio/onchain-build
Description of your new file.
# Protocol Requirements
Source: https://docs.elata.bio/protocol-requirements
Collect the protocol, BLE, and metadata details needed before coding a device adapter.
Do this work before implementation starts.
A clean protocol inventory prevents most integration delays and avoids the
common failure mode where the adapter works only for one test session or one
firmware version.
## Required Handoff
Collect this information for every device and firmware line you plan to support.
| Area | What to capture |
| --------------- | ------------------------------------------------------------ |
| Discovery | device name patterns, service UUIDs, filters |
| GATT layout | characteristics, direction, notification/read/write behavior |
| Packet format | framing, byte order, headers, counters, checksums |
| EEG payload | channel names, channel count, sample rate, units, scaling |
| Timing | device timestamps, local timestamps, sequence behavior |
| Session control | any commands required to start or stop streaming |
| Failure modes | disconnect behavior, low battery behavior, firmware quirks |
## BLE Inventory Template
Use this as the minimum intake format:
```md theme={null}
## Device
- Model:
- Firmware:
- Transport: Web Bluetooth / bridge / native helper
## Discovery
- Device name prefix:
- Primary service UUIDs:
- Required optional services:
## Characteristics
- UUID:
- purpose:
- read / write / notify:
- notes:
## EEG payload
- channel names:
- channel count:
- sample rate:
- units and scaling:
- timestamp source:
## Session control
- how streaming starts:
- how streaming stops:
- reconnect notes:
```
## Packet Questions You Should Answer Early
| Question | Why it matters |
| --------------------------------------------- | ------------------------------------------------------- |
| How does a packet start and end? | The decoder needs a deterministic framing rule. |
| Is there a packet counter or sequence field? | This helps detect drops, reordering, and corruption. |
| Is there a checksum or CRC? | This helps reject bad packets safely. |
| How are samples packed? | The decoder needs the exact byte layout and endianness. |
| Can multiple time steps arrive in one packet? | This affects row batching and timestamp handling. |
| What changes across firmware versions? | You need predictable version gating and test coverage. |
## Metadata Rules
Your adapter should never guess the basics.
| Metadata | Rule |
| ------------- | ----------------------------------------------------- |
| Channel names | Use a documented, stable order |
| Channel count | Match the emitted EEG rows exactly |
| Sample rate | Use the true output rate for the active mode |
| Units | Document whether values are raw, scaled, or converted |
| Clock source | Clearly state `device` or `local` |
Do not tune models or downstream app logic until this metadata is verified.
Most reliability issues start here.
## Browser and Platform Questions
This guide focuses on device integration, but platform constraints still matter.
Confirm these before planning a browser BLE rollout:
| Question | What to confirm |
| ---------------------------------------- | --------------------------------------------- |
| Does the device work over Web Bluetooth? | Chromium browsers only for this workflow |
| Does it require a secure context? | `https://` or `localhost` |
| Does it need a native helper? | If yes, use a separate package or bridge path |
| Does Safari or iOS matter? | Browser Web Bluetooth is not available there |
See [Compatibility](/sdk/operations/compatibility) for the current platform
expectations.
## Definition of Ready
You are ready to implement when you can answer all of the following without
guessing:
1. which transport path you are taking
2. how the browser discovers the device
3. how the session starts and stops
4. how packet bytes become EEG rows
5. how timestamps and channel metadata should be emitted
6. what caveats apply by firmware, browser, or platform
## Related Docs
* [Integration Paths](./integration-paths)
* [Transport Contract](./transport-contract)
* [Compatibility](/sdk/operations/compatibility)
* [Troubleshooting](/sdk/operations/troubleshooting)
## Next
Use the protocol inventory to implement a clean device layer.
Turn protocol assumptions into repeatable tests.
# Quickstart
Source: https://docs.elata.bio/quickstart
Start building awesome documentation in minutes
## Get started in three steps
Get your documentation site running locally and make your first customization.
### Step 1: Set up your local environment
During the onboarding process, you created a GitHub repository with your docs content if you didn't already have one. You can find a link to this repository in your [dashboard](https://dashboard.mintlify.com).
To clone the repository locally so that you can make and preview changes to your docs, follow the [Cloning a repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) guide in the GitHub docs.
1. Install the Mintlify CLI: `npm i -g mint`
2. Navigate to your docs directory and run: `mint dev`
3. Open `http://localhost:3000` to see your docs live!
Your preview updates automatically as you edit files.
### Step 2: Deploy your changes
Install the Mintlify GitHub app from your [dashboard](https://dashboard.mintlify.com/settings/organization/github-app).
Our GitHub app automatically deploys your changes to your docs site, so you don't need to manage deployments yourself.
For a first change, let's update the name and colors of your docs site.
1. Open `docs.json` in your editor.
2. Change the `"name"` field to your project name.
3. Update the `"colors"` to match your brand.
4. Save and see your changes instantly at `http://localhost:3000`.
Try changing the primary color to see an immediate difference!
### Step 3: Go live
1. Commit and push your changes.
2. Your docs will update and be live in moments!
## Next steps
Now that you have your docs running, explore these key features:
Learn MDX syntax and start writing your documentation.
Make your docs match your brand perfectly.
Include syntax-highlighted code blocks.
Auto-generate API docs from OpenAPI specs.
**Need help?** See our [full documentation](https://mintlify.com/docs) or join our [community](https://mintlify.com/community).
# Create Elata Demo
Source: https://docs.elata.bio/sdk/create-elata-demo
Scaffold Elata starter apps from published templates.
## What This Package Is
`@elata-biosciences/create-elata-demo` is a CLI scaffolder that generates ready-to-run Elata web apps.
Use it when you want a clean scaffolded app or a consumer-facing reference project.
***
## When To Use It
Use this package when you want:
* the fastest path to a working Elata app
* a reference project that matches the published package surface
* a known-good baseline before integrating packages into an existing app
Do not start with repo-internal demos when this package already covers your use case.
***
## Templates
The scaffolder exposes three user-facing app starters:
| Template | Description |
| ----------- | ------------------------------------------------------------------------------- |
| `rppg-demo` | React + Vite rPPG starter app |
| `eeg-demo` | React + Vite EEG starter app with synthetic data and browser EEG wiring |
| `eeg-ble` | Muse-compatible EEG starter with Chrome or Bluefy-on-iOS Web Bluetooth guidance |
Short aliases are also supported:
| Alias | Resolves to |
| ------ | ----------- |
| `rppg` | `rppg-demo` |
| `eeg` | `eeg-demo` |
| `ble` | `eeg-ble` |
| `ble` | `eeg-ble` |
***
## Install And Invocation
This package is usually invoked without installing it permanently:
```bash pnpm theme={null}
pnpm create @elata-biosciences/elata-demo my-app
pnpm dlx @elata-biosciences/create-elata-demo my-app
```
```bash npm theme={null}
npm create @elata-biosciences/elata-demo my-app
npx @elata-biosciences/create-elata-demo my-app
```
***
## List Available Templates
```bash pnpm theme={null}
pnpm dlx @elata-biosciences/create-elata-demo -- --list-templates
```
```bash npm theme={null}
npx @elata-biosciences/create-elata-demo -- --list-templates
```
***
## Recommended Usage
```bash pnpm theme={null}
# Interactive template chooser
pnpm create @elata-biosciences/elata-demo my-app
# rPPG starter app (alias)
pnpm create @elata-biosciences/elata-demo my-app -- --template rppg
# EEG starter app
pnpm create @elata-biosciences/elata-demo my-app -- --template eeg-demo
# EEG starter app (alias)
pnpm create @elata-biosciences/elata-demo my-app -- --template eeg
# EEG starter app with BLE alias
pnpm create @elata-biosciences/elata-demo my-app -- --template eeg-ble
# Short BLE alias
pnpm create @elata-biosciences/elata-demo my-app -- --template ble
```
```bash npm theme={null}
# Interactive template chooser
npm create @elata-biosciences/elata-demo my-app
# rPPG starter app (alias)
npm create @elata-biosciences/elata-demo my-app -- --template rppg
# EEG starter app
npm create @elata-biosciences/elata-demo my-app -- --template eeg-demo
# EEG starter app (alias)
npm create @elata-biosciences/elata-demo my-app -- --template eeg
# EEG starter app with BLE alias
npm create @elata-biosciences/elata-demo my-app -- --template eeg-ble
# Short BLE alias
npm create @elata-biosciences/elata-demo my-app -- --template ble
```
When the CLI is run interactively without `--template`, it prompts you to pick a template. In non-interactive runs, it falls back to `rppg-demo`.
If you omit the project directory, the CLI also prompts for the project name.
***
## What You Get
Each generated app includes:
* a minimal Vite + React app shell
* Elata packages pinned to a compatible set of versions
* a template-specific `README.md`
* a `build` script that type-checks and runs `vite build`
After scaffolding:
```bash pnpm theme={null}
cd my-app
pnpm install
pnpm run dev
```
```bash npm theme={null}
cd my-app
npm install
npm run dev
```
***
## What Happens Behind The Scenes
The scaffolder binary:
1. Prompts for the app type first when running interactively without `--template`
2. Prompts for `projectName` when missing
3. Validates the selected starter name
4. Copies the chosen template into the target directory
5. Renames `_gitignore` to `.gitignore`
6. Rewrites placeholders like `__APP_NAME__` and package-version placeholders
***
## Workspace Caveat
If you scaffold a new app inside another `pnpm` workspace and that app is not added to the workspace globs, run this from the parent directory:
```bash pnpm theme={null}
pnpm --dir my-app --ignore-workspace install
pnpm --dir my-app --ignore-workspace run dev
```
```bash npm theme={null}
cd my-app
npm install
npm run dev
```
***
## Repo Verification
The package is tested from the repo with:
```bash theme={null}
pnpm --dir packages/create-elata-demo test
./run.sh test create-elata-demo
```
The second command also smoke-tests each template by scaffolding, installing dependencies, and running a build.
***
## When To Use The Scaffolder vs. Examples
| If you want... | Use |
| -------------------------------------------------------------------- | ---------------------------------------------------------- |
| A clean, self-contained scaffolded app with pinned deps | `create-elata-demo` |
| A full product-shaped reference with routing, game loops, and charts | [Example Apps](/sdk/guides/example-apps) |
| To modify the SDK itself | In-repo dev demos (`eeg-demo/`, `packages/rppg-web/demo/`) |
***
## Next
Scaffold and run a starter app
Package decision guide
Full reference implementations
# Getting Started
Source: https://docs.elata.bio/sdk/eeg-web-ble/getting-started
Connect to EEG headband devices over Web Bluetooth
## When To Use This Package
Use `@elata-biosciences/eeg-web-ble` when your app needs:
* browser-side discovery of Muse-compatible devices
* live EEG streaming over Web Bluetooth
* normalized headband frames for app logic or downstream analysis
Install it alongside `@elata-biosciences/eeg-web`.
***
## Installation
```bash theme={null}
pnpm add @elata-biosciences/eeg-web-ble @elata-biosciences/eeg-web
```
`eeg-web-ble` depends on `eeg-web` for shared frame types and the WASM module.
***
## Requirements
* Browser with **Web Bluetooth** support (Chrome/Edge on desktop or Android)
* Served from a **secure context** (`https://` or `localhost`)
* Safari/iOS does **not** support Web Bluetooth. See [platform notes](/sdk/overview) for alternatives
***
## Basic Usage
`startStreaming()` is the recommended default. It wraps `connect()` and `start()` together and avoids the common mistake of connecting successfully but never beginning to stream.
```typescript theme={null}
import { initEegWasm, AthenaWasmDecoder } from "@elata-biosciences/eeg-web";
import { BleTransport } from "@elata-biosciences/eeg-web-ble";
// Initialize WASM first
await initEegWasm();
// Create transport
const transport = new BleTransport({
deviceOptions: {
athenaDecoderFactory: () => new AthenaWasmDecoder(),
},
});
// Handle incoming EEG frames
transport.onFrame = (frame) => {
console.log(`EEG samples: ${frame.eeg.samples.length} rows`);
console.log(`Channels: ${frame.eeg.channelNames.join(", ")}`);
};
// Handle connection status changes
transport.onStatus = (status) => {
console.log(`Transport: ${status.state}`, status.reason || "");
};
// Connect and start streaming (triggers Bluetooth device picker)
await transport.startStreaming();
// ... later
await transport.stop();
```
***
## BleTransport Lifecycle
| Method | What it does |
| ------------------ | ---------------------------------------------------------- |
| `startStreaming()` | Recommended: connects and starts the stream in one call |
| `connect()` | Opens Bluetooth device picker, pairs, and prepares session |
| `start()` | Begins EEG data stream; `onFrame` callbacks fire |
| `stop()` | Stops the data stream; connection stays open |
| `disconnect()` | Releases the Bluetooth session |
***
## BleTransportOptions
```typescript theme={null}
const transport = new BleTransport({
sourceName: "my-app-ble", // name tag in frame.source
deviceOptions: {
athenaDecoderFactory: () => new AthenaWasmDecoder(), // for Athena headbands
logger: (msg) => console.debug(msg),
onDisconnected: () => console.warn("Device disconnected"),
},
});
```
| Option | Type | Description |
| --------------- | ------------------- | ----------------------------------------------- |
| `sourceName` | `string` | Identifier included in `HeadbandFrameV1.source` |
| `deviceOptions` | `MuseDeviceOptions` | Passed to underlying `MuseBleDevice` |
| `device` | `BleDeviceLike` | Inject a custom device implementation |
***
## Athena Support
Muse S headbands with Athena firmware require an Athena decoder factory. Include it up front to keep both classic and Athena flows covered:
```typescript theme={null}
import { AthenaWasmDecoder } from "@elata-biosciences/eeg-web";
import { BleTransport } from "@elata-biosciences/eeg-web-ble";
const transport = new BleTransport({
deviceOptions: {
athenaDecoderFactory: () => new AthenaWasmDecoder(),
},
});
```
Athena headbands provide 8 EEG channels, optics, accelerometer/gyroscope, and battery data in each frame.
***
## Device Info
After connecting, you can query device metadata:
```typescript theme={null}
await transport.connect();
const isAthena = transport.getIsAthena();
const boardInfo = transport.getBoardInfo();
const channelNames = transport.getEegNames();
```
***
## Platform Caveats
* Safari and iOS do not provide usable Web Bluetooth support for this workflow.
* For Safari and iOS, use a native BLE shell, companion bridge, or hybrid WebView strategy with `@elata-biosciences/eeg-web` frame contracts as the boundary.
* Firmware variants may differ in command behavior.
***
## Next
Step-by-step BLE streaming guide
Protocol details, characteristics, and compatibility
End-to-end streaming and processing guide
# Muse Device
Source: https://docs.elata.bio/sdk/eeg-web-ble/muse-device
MuseBleDevice internals, protocol details, and compatibility
## MuseBleDevice
`MuseBleDevice` is the low-level Web Bluetooth class that handles device pairing, GATT characteristic subscriptions, and packet decoding. `BleTransport` wraps it to provide the normalized `HeadbandTransport` interface.
You typically do not use `MuseBleDevice` directly. Use `BleTransport` instead.
***
## Supported Protocols
| Protocol | Headbands | EEG Channels | Extra Data |
| ----------- | ------------------------ | ------------------------------- | ------------------------ |
| **Classic** | Muse 2, Muse S (classic) | 4 (TP9, AF7, AF8, TP10) | PPG (3 channels) |
| **Athena** | Muse S (Athena firmware) | 8 (TP9, AF7, AF8, TP10, AUX1-4) | Optics, accgyro, battery |
Protocol is auto-detected during connection based on device characteristics.
***
## Classic Protocol
Classic Muse headbands expose 4 EEG channels at 256 Hz. Each channel has its own GATT characteristic:
| Characteristic | Channel |
| -------------- | ------- |
| `273e0003-...` | TP9 |
| `273e0004-...` | AF7 |
| `273e0005-...` | AF8 |
| `273e0006-...` | TP10 |
PPG is available on 3 additional characteristics (`PPG1`, `PPG2`, `PPG3`).
***
## Athena Protocol
Athena headbands use a different packet format with two main characteristics:
| Characteristic | Data |
| -------------- | ------------------------------------ |
| `273e0013-...` | EEG (8 channels, multiplexed) |
| `273e0014-...` | Auxiliary (optics, accgyro, battery) |
Athena requires a decoder factory. Without one, connection will fail:
```typescript theme={null}
import { AthenaWasmDecoder } from "@elata-biosciences/eeg-web";
const transport = new BleTransport({
deviceOptions: {
athenaDecoderFactory: () => new AthenaWasmDecoder(),
},
});
```
***
## MuseDeviceOptions
```typescript theme={null}
interface MuseDeviceOptions {
athenaDecoderFactory?: AthenaDecoderFactory;
sleepMs?: (ms: number) => Promise;
logger?: (message: string) => void;
onDisconnected?: () => void;
}
```
***
## MuseBoardInfo
Returned by `getBoardInfo()` after connection:
```typescript theme={null}
interface MuseBoardInfo {
device_name: string;
sample_rate_hz: number; // 256
channel_count: number; // 4 or 8
eeg_channel_names: string[];
board_id: number;
protocol: "classic" | "athena";
optics_channel_count: number;
description: string;
}
```
***
## Browser Compatibility
| Browser | Platform | Status |
| ---------- | ------------------------------ | ------------- |
| Chrome 56+ | Windows, macOS, Linux, Android | Supported |
| Edge 79+ | Windows, macOS | Supported |
| Opera 43+ | Desktop | Supported |
| Safari | macOS, iOS | Not supported |
| Firefox | All | Not supported |
Web Bluetooth requires HTTPS. It will not work on `http://` (except `localhost` for development).
***
## Safari/iOS Workarounds
Three strategies for iOS support:
1. **Native app shell** (recommended): implement BLE in Swift with CoreBluetooth, bridge frames to web UI
2. **Companion bridge**: native app streams frames over WebSocket/WebRTC to the web app
3. **Hybrid WebView**: `WKWebView` with native message handlers for BLE
In all cases, use the `HeadbandFrameV1` schema as the interface boundary so browser and native transports emit the same frame shape.
***
## Next
Transport API and connection guide
Frame schema and transport interface
# Getting Started
Source: https://docs.elata.bio/sdk/eeg-web/getting-started
Install and initialize the @elata-biosciences/eeg-web WASM package
## When To Use This Package
Use `@elata-biosciences/eeg-web` when your app needs:
* EEG analysis APIs in the browser
* shared EEG web contracts used by higher-level integrations
* browser-side access to Elata WASM models and signal-processing helpers
This package does not implement Bluetooth device connection. Add `@elata-biosciences/eeg-web-ble` if you also need live Muse-compatible browser transport.
New to the SDK? Start by scaffolding a demo app with `create-elata-demo` before wiring packages manually. See the [First App tutorial](/sdk/tutorials/first-app).
***
## Installation
```bash pnpm theme={null}
pnpm add @elata-biosciences/eeg-web
```
```bash npm theme={null}
npm install @elata-biosciences/eeg-web
```
Requirements: Node.js 20+ for server-side usage and local repo tooling; modern browser with WebAssembly support for in-browser usage.
***
## WASM Initialization
Before using any signal processing or model APIs, initialize the WASM module:
```typescript theme={null}
import { initEegWasm } from "@elata-biosciences/eeg-web";
await initEegWasm();
```
`initEegWasm` is idempotent. Calling it multiple times returns the same promise. For synchronous initialization (e.g., in a Web Worker):
```typescript theme={null}
import { initEegWasmSync } from "@elata-biosciences/eeg-web";
initEegWasmSync(wasmModule);
```
***
## Basic Usage
Compute band powers from EEG data:
```typescript theme={null}
import { initEegWasm, band_powers } from "@elata-biosciences/eeg-web";
await initEegWasm();
const sampleRate = 256;
const eegSamples = new Float64Array(/* ... your EEG data ... */);
const powers = band_powers(eegSamples, sampleRate);
console.log("Alpha:", powers.alpha);
console.log("Beta:", powers.beta);
console.log("Theta:", powers.theta);
console.log("Delta:", powers.delta);
console.log("Gamma:", powers.gamma);
```
***
## Package Structure
`@elata-biosciences/eeg-web` is a thin TypeScript wrapper around WASM bindings generated by `wasm-bindgen`:
* `initEegWasm` / `initEegWasmSync`: WASM initialization helpers
* Headband frame types: normalized data schema for EEG transports
* All WASM APIs: re-exported from the generated bindings
### Key Exports
* `initEegWasm`
* `initEegWasmSync`
* `band_powers`
* `WasmAlphaBumpDetector`
* `WasmAlphaPeakModel`
* `WasmCalmnessModel`
* `AthenaWasmDecoder`
* `createRppgPipeline`
Generated `wasm-bindgen` exports are re-exported for compatibility and SDK debugging. Avoid instantiating generated wrappers directly unless you are intentionally debugging the SDK itself.
***
## Next
Not sure if this is the right package? See the decision guide.
Step-by-step integration tutorial
Band powers, FFT, spectrum analysis
Alpha bump detection, calmness scoring
Frame schema and transport interface
# Headband Transport
Source: https://docs.elata.bio/sdk/eeg-web/headband-transport
Normalized frame schema and transport interface for EEG headband devices
## HeadbandFrameV1
The canonical data frame emitted by all headband transports. Every implementation (BLE, native bridge, synthetic) produces this same shape:
```typescript theme={null}
interface HeadbandFrameV1 {
schemaVersion: "v1";
source: string; // e.g., "muse-ble"
sequenceId: number;
emittedAtMs: number;
eeg: HeadbandSignalBlock; // always present
ppgRaw?: HeadbandSignalBlock; // PPG (if available)
optics?: HeadbandSignalBlock; // Athena optics
accgyro?: HeadbandSignalBlock; // Athena accelerometer/gyroscope
battery?: HeadbandBatteryBlock; // battery level
}
```
***
## HeadbandSignalBlock
A block of time-series samples for one or more channels:
```typescript theme={null}
interface HeadbandSignalBlock {
sampleRateHz: number;
channelNames: string[]; // e.g., ["TP9", "AF7", "AF8", "TP10"]
channelCount: number;
samples: number[][]; // rows of [ch0, ch1, ch2, ch3]
timestampsMs?: number[];
clockSource?: "device" | "local";
}
```
***
## HeadbandTransportState
Transport lifecycle states:
| State | Description |
| -------------- | --------------------------------- |
| `Idle` | Transport created, not connected |
| `Connecting` | Connection in progress |
| `Connected` | Connected, not streaming |
| `Streaming` | Actively receiving frames |
| `Degraded` | Connected but experiencing issues |
| `Reconnecting` | Attempting to reconnect |
| `Disconnected` | Disconnected from device |
| `Error` | Unrecoverable error |
***
## HeadbandTransport Interface
All transports implement this interface:
```typescript theme={null}
interface HeadbandTransport {
onFrame?: (frame: HeadbandFrameV1) => void;
onStatus?: (status: HeadbandTransportStatus) => void;
connect(): Promise;
disconnect(): Promise;
start(): Promise;
stop(): Promise;
}
```
**Lifecycle:**
```mermaid theme={null}
stateDiagram-v2
[*] --> Idle
Idle --> Connecting: connect()
Connecting --> Connected: success
Connected --> Streaming: start()
Streaming --> Connected: stop()
Connected --> Disconnected: disconnect()
Streaming --> Disconnected: disconnect()
Connecting --> Error: failure
Streaming --> Degraded: signal issues
Degraded --> Streaming: recovery
Disconnected --> Connecting: connect()
```
***
## HeadbandTransportStatus
Status updates emitted via `onStatus`:
```typescript theme={null}
interface HeadbandTransportStatus {
state: HeadbandTransportState;
atMs: number;
reason?: string;
errorCode?: string;
recoverable?: boolean;
details?: Record;
}
```
***
## Usage Pattern
```typescript theme={null}
const transport: HeadbandTransport = /* BleTransport or other */;
transport.onFrame = (frame) => {
const eegSamples = frame.eeg.samples;
// Process each row: [tp9, af7, af8, tp10]
};
transport.onStatus = (status) => {
console.log(`State: ${status.state}`, status.reason);
};
await transport.connect();
await transport.start();
// ... later
await transport.stop();
await transport.disconnect();
```
***
## Next
Connect to headband devices
End-to-end streaming guide
# Analysis Models
Source: https://docs.elata.bio/sdk/eeg-web/models
Alpha bump detection, alpha peak tracking, and calmness scoring
## WasmAlphaBumpDetector
Detects alpha bumps, which are transient increases in alpha band power that indicate state transitions.
```typescript theme={null}
import { initEegWasm, WasmAlphaBumpDetector } from "@elata-biosciences/eeg-web";
await initEegWasm();
const detector = new WasmAlphaBumpDetector();
// Feed samples and check for bumps
const bumpDetected = detector.process(samples, sampleRateHz);
```
***
## WasmAlphaPeakModel
Tracks the individual alpha peak frequency (typically 8-13 Hz) which varies per person.
```typescript theme={null}
import { WasmAlphaPeakModel } from "@elata-biosciences/eeg-web";
const model = new WasmAlphaPeakModel();
// Feed EEG data to refine peak estimate
model.process(samples, sampleRateHz);
const peakHz = model.peak_frequency();
```
***
## WasmCalmnessModel
Computes a calmness score based on the ratio of alpha to beta power. Higher alpha relative to beta generally indicates a more relaxed state.
```typescript theme={null}
import { WasmCalmnessModel } from "@elata-biosciences/eeg-web";
const model = new WasmCalmnessModel();
model.process(samples, sampleRateHz);
const score = model.score(); // 0.0 to 1.0
```
***
## AthenaWasmDecoder
Decodes raw Athena protocol packets from Muse S (Athena firmware) headbands. Used as a factory parameter for `BleTransport`:
```typescript theme={null}
import { AthenaWasmDecoder } from "@elata-biosciences/eeg-web";
const decoder = new AthenaWasmDecoder();
decoder.reset();
decoder.set_use_device_timestamps(true);
decoder.set_clock_kind("monotonic");
decoder.set_reorder_window_ms(50);
const output = decoder.decode(rawBytes);
```
The Athena decoder is typically not used directly. It is passed as a factory to [`BleTransport`](/sdk/eeg-web-ble/getting-started):
```typescript theme={null}
import { BleTransport } from "@elata-biosciences/eeg-web-ble";
import { AthenaWasmDecoder } from "@elata-biosciences/eeg-web";
const transport = new BleTransport({
deviceOptions: {
athenaDecoderFactory: () => new AthenaWasmDecoder(),
},
});
```
***
## Next
Band powers, FFT, spectrum analysis
Frame schema and transport interface
# Signal Processing
Source: https://docs.elata.bio/sdk/eeg-web/signal-processing
Band power analysis, FFT, and spectrum computation
## Band Powers
Compute standard EEG frequency band powers from a sample array:
```typescript theme={null}
import { initEegWasm, band_powers } from "@elata-biosciences/eeg-web";
await initEegWasm();
const powers = band_powers(samples, sampleRateHz);
// powers.delta 0.5-4 Hz
// powers.theta 4-8 Hz
// powers.alpha 8-13 Hz
// powers.beta 13-30 Hz
// powers.gamma 30-100 Hz
```
`band_powers` returns a `WasmBandPowers` object with named fields for each standard band.
***
## Individual Band Functions
For targeted analysis, use individual band functions:
```typescript theme={null}
import {
alpha_power,
beta_power,
theta_power,
delta_power,
gamma_power,
custom_band_power,
} from "@elata-biosciences/eeg-web";
const alpha = alpha_power(samples, sampleRateHz);
const beta = beta_power(samples, sampleRateHz);
const theta = theta_power(samples, sampleRateHz);
const delta = delta_power(samples, sampleRateHz);
const gamma = gamma_power(samples, sampleRateHz);
// Custom frequency range
const mu = custom_band_power(samples, sampleRateHz, 8.0, 12.0);
```
***
## Power Spectrum
Compute the full power spectrum via FFT:
```typescript theme={null}
import { compute_power_spectrum, get_fft_frequencies } from "@elata-biosciences/eeg-web";
const spectrum = compute_power_spectrum(samples, sampleRateHz);
const frequencies = get_fft_frequencies(samples.length, sampleRateHz);
// spectrum[i] is the power at frequencies[i] Hz
```
***
## Input Format
All signal processing functions expect:
| Parameter | Type | Description |
| -------------- | ---------------------------- | ---------------------------------------- |
| `samples` | `Float64Array` or `number[]` | Single-channel EEG time series |
| `sampleRateHz` | `number` | Sampling rate in Hz (e.g., 256 for Muse) |
For multi-channel data (e.g., from `HeadbandFrameV1.eeg.samples`), extract individual channels and process them separately:
```typescript theme={null}
const frame: HeadbandFrameV1 = /* ... from transport ... */;
const channelIndex = 0; // TP9
const channelSamples = frame.eeg.samples.map(row => row[channelIndex]);
const powers = band_powers(new Float64Array(channelSamples), frame.eeg.sampleRateHz);
```
***
## Next
Alpha bump detection, calmness scoring
Install and initialize eeg-web
# rPPG Architecture
Source: https://docs.elata.bio/sdk/guides/architecture-rppg
Pipeline design, component breakdown, and distribution strategy for the rPPG + Ocular Proxy system.
This is an architecture reference, not a tutorial. For integration guidance, see [rPPG In A Browser App](/sdk/guides/rppg-browser) and [rppg-web Getting Started](/sdk/rppg-web/getting-started).
## Summary
The rPPG system extracts heart rate and optional ocular features from camera video, then produces calibrated, probabilistic bandpower proxies with confidence scores. A hybrid model keeps capture and vision in JS or native code, while DSP and metrics live in shared Rust/WASM/FFI modules.
***
## Goals
* Single processing core for web, iOS, Android, and desktop
* On-device processing (no raw video leaves the device)
* Stable metrics with explicit confidence and quality gating
* Clear separation between capture/vision and DSP
## Non-Goals
* Replacing clinical EEG or producing diagnostic-grade signals
* Reconstructing full multi-channel EEG from webcam
* Shipping training pipelines in production builds
***
## High-Level Pipeline
```mermaid theme={null}
flowchart LR
CAM["Camera Capture"] --> ROI["Face/ROI Tracking"]
ROI --> PIX["ROI Pixel Stats"]
PIX --> DSP["rPPG DSP Core"]
DSP --> FUS["Optional Fusion"]
FUS --> MET["Metrics + Confidence"]
```
1. **Camera capture** via `getUserMedia` (web) or native camera APIs
2. **Face/ROI tracking** using MediaPipe tasks
3. **ROI pixel stats** extracting average RGB and green channel values
4. **rPPG DSP core** with bandpass filtering, temporal normalization, and HR/HRV estimation
5. **Optional fusion** combining ocular features with rPPG signals
6. **Metrics + confidence** for app consumption
***
## Components
### Capture and ROI (Platform-Specific)
| Platform | Capture | Face Detection |
| ------------- | ------------------ | -------------------------------- |
| Web | `getUserMedia` | MediaPipe tasks (face/landmarks) |
| iOS / Android | Native camera APIs | Native MediaPipe |
**Output:** timestamped ROI statistics (average R/G/B, quality flags).
### rPPG DSP Core (Shared Rust Module)
The core Rust module consumes `{timestamp, intensity}` samples and runs:
* Temporal normalization
* Bandpass filtering (0.7-4.0 Hz for heart rate)
* Periodogram HR estimation with ACF fallback and harmonic checks
**Output:** `bpm`, `confidence`, `signal_quality`, and optional debug data.
### Ocular Features (Optional)
Optional capture-side features used for fatigue and arousal fusion:
* Blink rate and PERCLOS (percentage of eye closure)
* Gaze stability
* Pupil dynamics
These support fatigue/arousal estimation, not "EEG replacement."
### Fusion and Sentiment (Optional, Modular)
A separate module consuming rPPG + ocular features to produce coarse indices:
* `fatigue_index`
* `arousal_score`
* `focus_proxy`
Can be JS/ONNX or native ML. Kept independent of the rPPG core.
***
## Distribution Strategy
| Target | Approach | Package |
| ------------- | ------------------------------------------------------------------------------------------------ | ----------------------------- |
| Web | Rust to WASM via the internal `elata-rppg-wasm` crate, wrapped by `rppg-web` for a stable TS API | `@elata-biosciences/rppg-web` |
| Native | Rust to FFI via the internal `elata-rppg-ffi` crate | Internal binding crate |
| Compatibility | Constructors also available through the internal `elata-eeg-wasm` / `elata-eeg-ffi` crates | `@elata-biosciences/eeg-web` |
Camera and MediaPipe stay outside WASM, handled by browser or native APIs.
***
## Quality, Calibration, and Uncertainty
* Always publish a signal quality index (SQI)
* Require per-user baseline (60-120 seconds) for proxy metrics
* Emit confidence or prediction intervals for proxy metrics
* Gate app behavior on SQI and confidence
For the calibration API that fuses Muse PPG with camera rPPG, see [Calibration and Fusion](/sdk/rppg-web/calibration).
***
## Benchmarking
* Use pyVHR offline for algorithm validation and regression tests
* Compare HR error under motion, lighting, and elevated HR conditions
* Use strict splits (leave-one-subject/context out)
***
## Next
Install and use the rPPG package
Muse PPG calibration models
End-to-end webcam pipeline
MediaPipe face detection
# Choosing the right package
Source: https://docs.elata.bio/sdk/guides/choose-the-right-package
Pick the right Elata SDK package for your use case and avoid common setup mistakes.
## Recommended Decision Order
1. If you are evaluating the SDK or starting a new app, use [`create-elata-demo`](/sdk/create-elata-demo) first.
2. If you already have an app and know which capability you need, add the published package for that capability.
3. Only drop to repo-internal workflows if you are actively modifying the SDK inside the monorepo.
***
## New Project Or Evaluation
The best starting point when you want a working app quickly.
***
## EEG In A Browser App
Use `@elata-biosciences/eeg-web` when you need:
* browser-side EEG WASM APIs
* signal processing and model functions
* shared types used by browser integrations
Signal processing, band powers, and analysis models
***
## Browser BLE For Muse-Compatible Devices
Use `@elata-biosciences/eeg-web-ble` when you need:
* browser BLE discovery and streaming
* normalized headband frames
* a transport layer for Muse-compatible EEG devices
You should generally install `@elata-biosciences/eeg-web` alongside it.
Connect to headband devices over Web Bluetooth
***
## Camera-Based rPPG
Use `@elata-biosciences/rppg-web` when you need:
* browser-side rPPG processing
* packaged WASM backend loading
* demo helpers for camera-driven prototypes
Heart rate from camera via face detection
***
## Local Repo Development
Use `./run.sh sync-to` only if you are modifying `packages/eeg-web` inside the monorepo and want to link the local package into another app.
Do not use `sync-to` as the normal onboarding path for new SDK consumers.
***
## Paths To Avoid
These paths create unnecessary friction for most consumers. Only use them if you have a specific reason.
* **Avoid starting from `./run.sh sync-to` for new apps.** It is a local `eeg-web` development helper, not a general setup flow.
* **Avoid treating in-repo dev demos as the normal install path.** They are useful references, but `create-elata-demo` is the cleaner consumer starting point.
* **Avoid assuming a scaffolded app is broken if `pnpm install` behaves strangely inside another workspace.** Check the `pnpm --ignore-workspace` workflow first.
***
## Next
Scaffold your first app
Full reference implementations
Browser, device, and tooling support
# EEG + BLE integration
Source: https://docs.elata.bio/sdk/guides/eeg-ble-integration
Connect a Muse-compatible headband in the browser, stream EEG frames, and compute band powers with the Elata SDK.
Use this guide when you want the full browser EEG flow in one place. You will initialize the EEG WASM layer, connect over Web Bluetooth, stream frames, and turn those frames into app-ready metrics.
## What this guide covers
This guide shows how to combine `@elata-biosciences/eeg-web` and `@elata-biosciences/eeg-web-ble` into a single browser flow.
By the end, you will be able to:
* initialize the EEG WASM runtime
* connect to a Muse-compatible headband from a browser
* receive normalized `HeadbandFrameV1` frames
* compute real-time band powers from incoming EEG samples
* stop cleanly and recover from disconnects
## Before you start
You need:
* `@elata-biosciences/eeg-web`
* `@elata-biosciences/eeg-web-ble`
* Chrome or Edge with Web Bluetooth support
* `https://` or `localhost`
* a Muse 2 or Muse S headband
Safari and iOS do not support this browser BLE workflow. For those platforms, use a native BLE shell, a bridge app, or another transport layer and keep `@elata-biosciences/eeg-web` as your processing boundary.
## Choose the right starting point
Scaffold an EEG BLE demo if you want the fastest path to a reference app.
Learn the browser EEG processing package before adding device transport.
Review the transport package, lifecycle methods, and platform constraints.
Follow the step-by-step version if you want a tutorial before this end-to-end guide.
## Install the packages
```bash theme={null}
pnpm add @elata-biosciences/eeg-web @elata-biosciences/eeg-web-ble
```
```bash theme={null}
npm install @elata-biosciences/eeg-web @elata-biosciences/eeg-web-ble
```
`@elata-biosciences/eeg-web-ble` depends on `@elata-biosciences/eeg-web` for shared frame types and the EEG WASM layer.
## Integration flow
Initialize the EEG WASM module before you create a transport or call analysis helpers.
```ts theme={null}
import { initEegWasm } from "@elata-biosciences/eeg-web";
await initEegWasm();
```
Include an Athena decoder up front so both classic and Athena-compatible headbands work through the same setup.
```ts theme={null}
import { AthenaWasmDecoder } from "@elata-biosciences/eeg-web";
import { BleTransport } from "@elata-biosciences/eeg-web-ble";
const transport = new BleTransport({
sourceName: "my-app",
deviceOptions: {
athenaDecoderFactory: () => new AthenaWasmDecoder(),
logger: (msg) => console.debug("[BLE]", msg),
},
});
```
Use `onStatus` for UI state and `onFrame` for EEG data.
```ts theme={null}
import type { HeadbandFrameV1 } from "@elata-biosciences/eeg-web";
transport.onStatus = (status) => {
const el = document.getElementById("status");
if (el) el.textContent = status.state;
};
transport.onFrame = (frame: HeadbandFrameV1) => {
console.log("channels", frame.eeg.channelNames);
console.log("rows", frame.eeg.samples.length);
};
```
Browser BLE flows usually need a click or another user gesture to open the device picker.
```ts theme={null}
async function connectHeadband() {
try {
await transport.connect();
await transport.start();
} catch (err) {
console.error("Connection failed:", err);
}
}
```
In simpler app flows, `startStreaming()` is often the safest default because it combines `connect()` and `start()` in one call.
Compute band powers per channel as frames arrive.
```ts theme={null}
import { band_powers } from "@elata-biosciences/eeg-web";
transport.onFrame = (frame: HeadbandFrameV1) => {
const { eeg } = frame;
for (let ch = 0; ch < eeg.channelCount; ch++) {
const channelSamples = eeg.samples.map((row) => row[ch]);
const powers = band_powers(
new Float64Array(channelSamples),
eeg.sampleRateHz,
);
console.log(
`${eeg.channelNames[ch]}: alpha=${powers.alpha.toFixed(2)} beta=${powers.beta.toFixed(2)}`,
);
}
};
```
## Full example
```ts theme={null}
import {
initEegWasm,
band_powers,
AthenaWasmDecoder,
} from "@elata-biosciences/eeg-web";
import type { HeadbandFrameV1 } from "@elata-biosciences/eeg-web";
import { BleTransport } from "@elata-biosciences/eeg-web-ble";
await initEegWasm();
const transport = new BleTransport({
sourceName: "my-app",
deviceOptions: {
athenaDecoderFactory: () => new AthenaWasmDecoder(),
logger: (msg) => console.debug("[BLE]", msg),
},
});
transport.onStatus = (status) => {
const el = document.getElementById("status");
if (el) el.textContent = status.state;
};
transport.onFrame = (frame: HeadbandFrameV1) => {
const { eeg } = frame;
for (let ch = 0; ch < eeg.channelCount; ch++) {
const channelSamples = eeg.samples.map((row) => row[ch]);
const powers = band_powers(
new Float64Array(channelSamples),
eeg.sampleRateHz,
);
console.log(
`${eeg.channelNames[ch]}: alpha=${powers.alpha.toFixed(2)} beta=${powers.beta.toFixed(2)}`,
);
}
};
try {
await transport.connect();
await transport.start();
} catch (err) {
console.error("Connection failed:", err);
}
```
## Clean shutdown
Stop the stream and release the Bluetooth session when the user leaves the view or closes the page.
```ts theme={null}
async function shutdown() {
await transport.stop();
await transport.disconnect();
}
window.addEventListener("beforeunload", shutdown);
```
## Handle reconnection
If a disconnect is recoverable, try to reconnect and restart the stream.
```ts theme={null}
transport.onStatus = (status) => {
if (status.state === "disconnected" && status.recoverable) {
console.log("Attempting reconnect...");
setTimeout(async () => {
try {
await transport.connect();
await transport.start();
} catch (error) {
console.error("Reconnect failed:", error);
}
}, 2000);
}
};
```
## Understand the transport lifecycle
| Method | What it does |
| ------------------ | ----------------------------------------------------- |
| `startStreaming()` | Connects and starts the stream in one call |
| `connect()` | Opens the Bluetooth picker and prepares the session |
| `start()` | Begins the EEG stream and triggers `onFrame` |
| `stop()` | Stops the stream but keeps the Bluetooth session open |
| `disconnect()` | Releases the Bluetooth session |
## Device notes
Works with the standard browser BLE flow.
Use `athenaDecoderFactory` during transport creation. Athena devices expose richer frame content, including 8 EEG channels plus additional sensor data.
You can also inspect metadata after connecting:
```ts theme={null}
await transport.connect();
const isAthena = transport.getIsAthena();
const boardInfo = transport.getBoardInfo();
const channelNames = transport.getEegNames();
```
If you are testing across Muse variants, keep the Athena decoder factory enabled from day one so you do not need a separate transport setup later.
## Architecture at a glance
The browser EEG BLE flow usually looks like this:
1. initialize `@elata-biosciences/eeg-web`
2. create a `BleTransport`
3. connect from a user gesture
4. receive `HeadbandFrameV1` frames
5. compute app metrics such as band powers, scores, or state transitions
6. render charts, scores, or adaptive UI in your app
## Integration tips
* Buffer frames before analysis for more stable results
* Use `Float64Array` when calling `band_powers`
* Keep BLE connection logic behind explicit user actions
* Move frame handling into app state instead of leaving it in `console.log`
* Start with a demo app if you need a working reference project first
* Test with synthetic data first during development if no headband is available
`band_powers` is most useful with roughly 1 to 2 seconds of data. At 256 Hz, that is about 256 to 512 samples.
## Where to go next
Review device behavior, protocol details, and compatibility notes.
See the transport package API and platform caveats in more detail.
Follow the tutorial version of this workflow with app-oriented steps.
Integrate browser EEG processing into an app before adding BLE transport.
Learn the frame schema and transport boundary used by EEG flows.
Pair this EEG workflow with the browser camera pipeline when you need multimodal biometrics.
# EEG in a browser App
Source: https://docs.elata.bio/sdk/guides/eeg-browser
Add Elata EEG WASM APIs to an existing browser application.
Use this page if you want the package model for browser EEG, not a full integration walkthrough.
If you want numbered steps for an existing app, use [Add EEG To An Existing Browser App](/sdk/tutorials/eeg-existing-app). If you want the scaffold path, use [Build Your First Elata App](/sdk/tutorials/first-app).
If you want a running app without manual wiring, scaffold with [create-elata-demo](/sdk/create-elata-demo) first.
EEG is **optional** in the usual Elata journey: **camera rPPG** is the primary browser integration for many apps. Add `eeg-web` when you need brain-signal analysis; add **`eeg-web-ble`** alongside it when you need **Web Bluetooth** for a Muse-compatible headset.
EEG measures electrical activity from the brain using a wearable headset. In browser apps, developers usually use EEG to stream headset samples, compute features such as band powers, and drive feedback, dashboards, adaptive UX, or interactive experiences.
Concrete app examples include:
* meditation or breath-training apps that adapt guidance to focus or calmness
* neurofeedback experiences that reward attention or steadiness
* simulated athletic-performance or training apps that react to mental state
* games or creative tools that change difficulty, pacing, or effects based on live EEG features
***
## Install
```bash pnpm theme={null}
pnpm add @elata-biosciences/eeg-web
```
```bash npm theme={null}
npm install @elata-biosciences/eeg-web
```
***
## What `eeg-web` Gives You
`@elata-biosciences/eeg-web` provides:
* browser-side EEG WASM initialization
* signal-processing and model exports such as `band_powers`
* shared types and contracts used by higher-level browser integrations
This package does not handle Bluetooth device connection by itself. Add `@elata-biosciences/eeg-web-ble` if you also need browser BLE transport.
***
## Minimal Integration
```ts theme={null}
import { initEegWasm, band_powers } from "@elata-biosciences/eeg-web";
await initEegWasm();
const eegData = new Float32Array([0, 1, 0, -1]);
const powers = band_powers(eegData, 256);
console.log("alpha", powers.alpha);
```
***
## Typical Integration Flow
1. Initialize the packaged WASM runtime with `initEegWasm()`.
2. Pass browser-side EEG sample buffers into the exported analysis functions.
3. If you later need live device transport, combine this package with `eeg-web-ble`.
***
## When To Use The EEG Template Instead
Prefer the scaffolded `eeg-demo` template when you want:
* a known-good Vite setup
* a reference for how bundled WASM assets should be served
* a synthetic-data app that runs without hardware
***
## Common Gotchas
* `band_powers()` takes a **single-channel** `Float32Array`, not `number[]`. Convert with `new Float32Array(samples[channelIdx])` before passing. Passing a plain array will cause a WASM runtime error. For a Muse headband (4 channels: TP9, AF7, AF8, TP10), use a frontal channel (AF7 = index 1, or AF8 = index 2) for cognitive state features, or average across channels.
* `WasmCalmnessModel.process()` expects **interleaved** samples: `[s0_ch0, s0_ch1, s1_ch0, s1_ch1, ...]`. The `frame.eeg.samples` layout is per-channel. Convert before passing.
* `WasmCalmnessModel` needs `channelCount` at construction, but `channelCount` only arrives on the first frame. Construct the model inside your first-frame handler.
* If `initEegWasm()` fails, your app may not be loading the packaged `wasm/` assets correctly.
* If you need a live headset connection, `eeg-web` alone is not enough.
* If you are only evaluating the SDK, the scaffolded app is faster than manual setup.
***
## Next
Step-by-step integration tutorial
Connect a Muse headset
Package API and exports
Common failures and fixes
# Example Applications
Source: https://docs.elata.bio/sdk/guides/example-apps
Open source browser apps built with Elata EEG, Web Bluetooth, and rPPG packages.
These are full product-shaped integrations with routing, sessions, charts, and game loops. They go beyond the `create-elata-demo` starter templates and show what a complete app looks like.
## Featured Apps
Guided breathing with live EEG and rPPG biometric monitoring.
3D flight simulator with post-session neural analytics.
Brain-reactive arcade gameplay that responds to mental state.
Chess against Stockfish with a neural HUD overlay.
Stress-modulated reaction game with results tracking.
***
## What These Apps Demonstrate
Each app is a standalone repository that uses the published `@elata-biosciences` packages:
| Package | Purpose |
| -------------------------------- | ----------------------------------- |
| `@elata-biosciences/eeg-web` | EEG signal processing and models |
| `@elata-biosciences/eeg-web-ble` | Web Bluetooth headband streaming |
| `@elata-biosciences/rppg-web` | Camera-based heart rate measurement |
These apps are useful as reference implementations when you need to see how a real product wires up SDK sessions, state management, charts, and cleanup.
***
## When To Use These vs. `create-elata-demo`
| If you want to... | Start here |
| ---------------------------------------------------------------------- | --------------------------------------------- |
| Get a working app in under a minute | [`create-elata-demo`](/sdk/create-elata-demo) |
| See a complete product with routing, game loops, or data visualization | One of the example apps above |
| Compare your integration against a known-good reference | Either path works |
***
## Next
Scaffold a starter app
Package decision guide
Primary browser integration
# Federated Learning roadmap and privacy
Source: https://docs.elata.bio/sdk/guides/federated-learning
How Elata plans to use federated learning, why it is on the roadmap, and how it preserves user privacy.
Federated learning is a roadmap direction, not the default production integration path today.
Federated learning is on the Elata roadmap for future model-improvement flows.
Today, Elata SDK focuses on local on-device and in-browser processing paths.
As federated capabilities are introduced, the goal is to let apps contribute to
global model quality improvements without sending raw biosignal data to a
central server.
## What Federated Learning Means Here
In an Elata federated setup, app instances would train or adapt model weights
locally, then send only constrained update artifacts for aggregation.
The intended direction is:
1. process and train locally in the user environment
2. send model updates instead of raw EEG or camera frames
3. aggregate updates across many participants
4. return improved shared model versions to clients
## Why It Is On The Roadmap
Federated learning is a strong fit for biosignal products because it can
improve model quality across device types and usage contexts while reducing
privacy exposure.
Planned benefits include:
* better cross-user and cross-device robustness over time
* faster model iteration without requiring centralized raw-data collection
* clearer privacy posture for sensitive physiological signals
## How It Preserves Privacy
Federated learning helps preserve privacy by changing what leaves the device:
* raw biosignal inputs stay local by default
* shared artifacts are model updates, not full raw signal streams
* aggregation combines many updates before model rollout
Federated learning is not a complete privacy guarantee by itself. In production
systems, it is typically paired with controls such as secure transport,
authentication, update validation, and additional privacy techniques.
## Current Status
Federated learning is a roadmap direction, not the default production
integration path today.
For current integrations, use the existing package entrypoints:
* EEG: `@elata-biosciences/eeg-web` and `@elata-biosciences/eeg-web-ble`
* rPPG: `@elata-biosciences/rppg-web`
## Next
Browser EEG integration
Camera-based rPPG integration
Browser and device support
# Remote sensing in a browser App
Source: https://docs.elata.bio/sdk/guides/rppg-browser
Add browser-based camera rPPG with the supported Elata session helpers.
Use this page if you want the integration model for browser rPPG.
If you want numbered steps for an existing app, use [Add Camera-Based rPPG To An Existing Browser App](/sdk/tutorials/rppg-existing-app). If you want the scaffold path, use [Build Your First Elata App](/sdk/tutorials/first-app).
This is the **primary** browser biosignal path for most products: no headset required before you ship meaningful signal UX.
rPPG stands for remote photoplethysmography. It estimates pulse-related changes from camera video, usually from a face region, without requiring a wearable sensor.
In browser apps, developers usually use rPPG to turn a live camera stream into heart-rate-style metrics, diagnostics, and wellness-oriented feedback.
Concrete app examples include:
* deception or bluffing games that react to pulse changes during key moments
* stress or arousal feedback in training and social experiences
* breathing and relaxation flows that show physiological response over time
* biofeedback-oriented health or wellness apps that want camera-based pulse signals without extra hardware
***
## Install
```bash pnpm theme={null}
pnpm add @elata-biosciences/rppg-web
```
```bash npm theme={null}
npm install @elata-biosciences/rppg-web
```
***
## Recommended vs. Advanced
Use `createRppgSession()` for browser apps. It handles WASM init, frame capture, ROI orchestration, diagnostics, and cleanup.
**Recommended:**
* Use `createRppgSession()` for browser apps.
* Use `createManagedRppgSession()` when you want built-in restart behavior after a terminal processor failure.
* Use `createRppgPipeline()` from `@elata-biosciences/eeg-web` only if you intentionally need low-level sample ingestion.
**Advanced:**
* Use `RppgProcessor`, `DemoRunner`, custom backends, or generated WASM bindings only if you need custom orchestration and understand the runtime lifecycle already.
* If you are not debugging the SDK itself, do not start with generated WASM exports.
***
## Minimal Integration
```ts theme={null}
import { createRppgSession } from "@elata-biosciences/rppg-web";
const session = await createRppgSession({
video: videoEl,
sampleRate: 30,
backend: "auto",
faceMesh: "off",
onDiagnostics: (diagnostics) => {
console.log(diagnostics.state.status, diagnostics.faceTrackingMode);
console.log(diagnostics.framesSeen, diagnostics.totalSamplesReceived);
console.log(diagnostics.issues, diagnostics.processorFailure);
},
onError: (error) => {
console.error(error.code, error.message);
},
});
console.log(session.getMetrics());
```
***
## Typical Integration Flow
1. Acquire a camera stream in the browser and attach it to a `video` element.
2. Call `createRppgSession({ video, backend: "auto" })`.
3. Read metrics from `session.getMetrics()`.
4. Surface diagnostics from `onDiagnostics` or `session.getDiagnostics()`.
5. Stop the session during cleanup with `await session.stop()`.
`createRppgSession()` owns WASM init, FaceMesh loading, frame scheduling, ROI selection, diagnostics, and cleanup.
Use `session.state` or `diagnostics.state` to distinguish:
* normal `running`
* startup fallback or degraded setup via `degraded`
* terminal runtime processor failure via `failed`
If you intentionally choose `faceMesh: "off"`, the session stays in supported `video_frame` mode and is not reported as a FaceMesh failure by default.
If your app needs explicit asset paths instead of the default `/pkg/*` lookup, pass one or more of:
```ts theme={null}
const session = await createRppgSession({
video: videoEl,
wasmJsUrl: "/assets/rppg_wasm.js",
wasmBinaryUrl: "/assets/rppg_wasm_bg.wasm",
});
```
Advanced apps can also provide `wasmImporter` directly.
***
## Managed Restart Flow
If you want the SDK to own restart timing after terminal processor failures, use the managed wrapper:
```ts theme={null}
import { createManagedRppgSession } from "@elata-biosciences/rppg-web";
const managed = await createManagedRppgSession({
video: videoEl,
faceMesh: "off",
maxRetries: 3,
retryDelayMs: 1500,
onStateChange: (state) => {
console.log(state.status, state.retryCount, state.lastError?.code);
},
});
```
The managed wrapper exposes high-level states such as `starting`, `running`, `retrying`, and `failed`, while still letting apps drop down to the underlying `RppgSession` when needed.
***
## Advanced Helpers
Use `getTraceSnapshot()` when you need recent waveform or debug points for charts, debug panels, or regression capture:
```ts theme={null}
const trace = session.getTraceSnapshot(300);
console.log(trace.points);
console.log(trace.lastSample);
console.log(trace.backendFailure);
```
For peak/threshold waveform debug from the trace data, use `computeTraceWaveformDebug()`:
```ts theme={null}
import { computeTraceWaveformDebug } from "@elata-biosciences/rppg-web";
const waveform = computeTraceWaveformDebug(session.getTraceSnapshot(300));
console.log(waveform.peaks);
```
Use `normalizeRppgError()` instead of parsing raw message text in app code:
```ts theme={null}
import { normalizeRppgError } from "@elata-biosciences/rppg-web";
const normalized = normalizeRppgError(session.lastError, session.getDiagnostics());
console.log(normalized?.code);
console.log(normalized?.message);
console.log(normalized?.guidance);
```
This gives apps stable categories such as `wasm_init_failed`, `backend_unavailable`, `camera_not_playing`, and `processor_failed`.
If you want a single app-facing snapshot with restart status, publish gating, trace data, and stable messages, use `createRppgAppAdapter()`:
```ts theme={null}
import {
createManagedRppgSession,
createRppgAppAdapter,
} from "@elata-biosciences/rppg-web";
const managed = await createManagedRppgSession({
video,
faceMesh: "off",
});
const adapter = createRppgAppAdapter();
const app = adapter.getSnapshot(managed);
if (app.canPublish) {
console.log(app.publishBpm);
}
console.log(app.status, app.message);
```
If you want the SDK to own the recurring snapshot loop too, use `createRppgAppMonitor()`:
```ts theme={null}
import {
createManagedRppgSession,
createRppgAppMonitor,
} from "@elata-biosciences/rppg-web";
const managed = await createManagedRppgSession({
video,
faceMesh: "off",
});
const monitor = createRppgAppMonitor(managed, { intervalMs: 500 });
monitor.subscribe((snapshot) => {
console.log(snapshot.status, snapshot.publishBpm);
});
monitor.start();
```
`createRppgSession()` now waits for the video element to start playing by default. If you need to coordinate that step yourself, call `ensureVideoPlaying()` directly:
```ts theme={null}
import { ensureVideoPlaying } from "@elata-biosciences/rppg-web";
await ensureVideoPlaying(video, { timeoutMs: 5000 });
```
***
## When To Use The rPPG Template Instead
Prefer the scaffolded `rppg-demo` template when you want:
* a known-good browser camera app
* a reference for packaged WASM asset loading
* a faster comparison point when debugging your own integration
***
## Common Gotchas
* If `session.backendMode` is `unavailable`, your app is probably not serving the packaged `pkg/` assets correctly.
* If `session.state.status` is `failed`, treat that processor backend as terminal and recreate the session instead of continuing to poll metrics from it.
* If you see "backend pipeline has no push\_sample API", you likely bypassed the safe wrapper path. Start with `createRppgSession()` for browser apps, or `initEegWasm()` plus `createRppgPipeline()` for low-level ingestion.
* If you hit `wasmrppgpipeline_new`, initialize the WASM module before creating low-level pipelines and avoid calling generated constructors directly.
* If you see deprecated init warnings, route startup through `initEegWasm()` instead of forwarding raw strings, URLs, or buffers to the generated init exports.
* If camera access fails, confirm the page has permission to use `getUserMedia`.
* If `session.lastError` is non-null, use its `code` and `message` to surface the real capture or processor failure instead of retrying blindly.
* If you are just evaluating the SDK, the scaffolded app is much faster than building the whole browser pipeline yourself.
***
## Version Guidance
If you install both `@elata-biosciences/rppg-web` and `@elata-biosciences/eeg-web`, prefer matching versions. They are developed and verified together in the same repo.
***
## Next
Step-by-step rPPG integration
Package API and exports
Pipeline design and components
Common failures and fixes
# Camera Integration
Source: https://docs.elata.bio/sdk/guides/rppg-camera
Add camera-based heart rate to a browser app with the Elata rPPG Web SDK.
Use this guide when you want live camera-based heart rate in a browser app. Start with the quick path first. Move to the manual pipeline only if you need lower-level control.
## What this guide covers
This guide shows how to:
* capture webcam frames in the browser
* detect a face and extract a face region
* feed samples into the rPPG processor
* read live BPM and signal quality
* improve estimates with Muse PPG when available
## Before you start
```bash pnpm theme={null}
pnpm add @elata-biosciences/rppg-web
```
```bash npm theme={null}
npm install @elata-biosciences/rppg-web
```
* `@elata-biosciences/rppg-web` installed
* Browser with camera access (`getUserMedia`) and WebAssembly support
* HTTPS or localhost for development
You need:
* `@elata-biosciences/rppg-web`
* a browser with camera access and WebAssembly support
* `https://` or `localhost`
rPPG works best when the face is well lit, mostly still, and fully visible in frame.
## Choose a starting point
Scaffold a working browser app if you want the fastest path to a reference implementation.
Learn the package entry points and the higher-level browser API.
See the lower-level camera and face-detection primitives used in this guide.
Add Muse-based calibration when you want stronger estimates.
## Quick integration
For most apps, use `DemoRunner`. It handles frame capture, face detection, ROI extraction, and processing in one flow.
```bash theme={null}
pnpm add @elata-biosciences/rppg-web
```
```bash theme={null}
npm install @elata-biosciences/rppg-web
```
```ts theme={null}
import {
RppgProcessor,
MediaPipeFrameSource,
DemoRunner,
} from "@elata-biosciences/rppg-web";
const source = new MediaPipeFrameSource();
const processor = new RppgProcessor("wasm", 30);
const runner = new DemoRunner(source, processor, {
useSkinMask: true,
onStats: () => {
const metrics = processor.getMetrics();
document.getElementById("bpm")!.textContent =
metrics.bpm?.toFixed(0) ?? "--";
document.getElementById("quality")!.textContent =
`${((metrics.quality ?? 0) * 100).toFixed(0)}%`;
},
});
await runner.start();
```
Show BPM only when signal quality is above your app's threshold. A threshold around `0.5` is a practical starting point.
## Manual pipeline
Use the manual path when you want tighter control over face tracking, sampling, or how your app renders intermediate results.
```ts theme={null}
import {
MediaPipeFaceFrameSource,
loadFaceMesh,
} from "@elata-biosciences/rppg-web";
const faceMesh = await loadFaceMesh();
const source = new MediaPipeFaceFrameSource(faceMesh);
```
```ts theme={null}
import { RppgProcessor } from "@elata-biosciences/rppg-web";
const processor = new RppgProcessor("wasm", 30, 10);
```
The third argument sets a 10-second analysis window.
```ts theme={null}
import { averageGreenInROI } from "@elata-biosciences/rppg-web";
source.onFrame = (frame) => {
if (!frame.roi) return;
const { x, y, w, h } = frame.roi;
const green = averageGreenInROI(frame, x, y, w, h);
processor.pushSample(
frame.timestampMs ?? performance.now(),
green,
);
};
```
```ts theme={null}
await source.start();
setInterval(() => {
const metrics = processor.getMetrics();
console.log("BPM:", metrics.bpm, "quality:", metrics.quality);
}, 1000);
```
## Add Muse calibration
If a Muse device is available, you can use its PPG as a reference signal to improve camera-based estimates.
```ts theme={null}
import { RppgProcessor } from "@elata-biosciences/rppg-web";
const processor = new RppgProcessor("wasm", 30);
processor.updateMuseMetrics(museBpm, 0.9, performance.now());
```
For a fuller fusion workflow, including `MuseFusionCalibrator` and calibration models, continue to [/sdk/rppg-web/calibration](/sdk/rppg-web/calibration).
## Quality checklist
Use this checklist before debugging the pipeline:
* keep lighting even across the face
* reduce head movement
* wait 5 to 10 seconds before trusting early BPM output
* make sure a face ROI is actually being detected
* enable `useSkinMask` when you use `DemoRunner`
## Where to go next
Learn the package surface and the higher-level browser session API.
Understand the camera and face-detection primitives in more detail.
Improve estimates with Muse-based calibration and multi-source fusion.
Start from a working reference app if you want a faster onboarding path.
# Web Bluetooth with supported devices
Source: https://docs.elata.bio/sdk/guides/web-bluetooth
Stream Muse-compatible EEG devices from supported Chromium browsers.
Use this page if you want the transport model and platform constraints, not a
full walkthrough.
If you want numbered steps for an existing app, use
[Stream Muse-Compatible EEG Over Web Bluetooth](/sdk/tutorials/eeg-ble-live-stream).
If you want the scaffold path, use [Build Your First Elata App](/sdk/tutorials/first-app).
**Role in the SDK:** camera **rPPG** is the usual first app. **EEG** is optional when you need brain signals. **Web Bluetooth** (`eeg-web-ble` with `eeg-web`) enables a live Muse-compatible headset. It is transport for EEG, not a parallel primary product to rPPG.
***
## Start With A Known-Good Scaffolded App
If you want the fastest path to a working browser BLE example, scaffold the EEG
starter app first:
```bash pnpm theme={null}
pnpm create @elata-biosciences/elata-demo my-app -- --template eeg-ble
cd my-app
pnpm install
pnpm run dev
```
```bash npm theme={null}
npm create @elata-biosciences/elata-demo my-app -- --template eeg-ble
cd my-app
npm install
npm run dev
```
Use the rest of this guide when you want to add the same browser BLE flow to an existing app.
***
## Requirements
* Chrome, Edge, or Bluefy on iOS
* `https://` or `localhost`
* Bluetooth enabled on the machine
* A supported Muse-compatible EEG device
Supported device classes:
* Muse 2 and Muse S classic BLE devices
* Muse S Athena protocol v2 devices
* The synthetic Muse-compatible BLE bridge used for testing
For browser BLE, use Chrome on desktop or Android, or Bluefy on iOS. Do not expect Safari itself to handle this workflow.
***
## Install
```bash pnpm theme={null}
pnpm add @elata-biosciences/eeg-web @elata-biosciences/eeg-web-ble
```
```bash npm theme={null}
npm install @elata-biosciences/eeg-web @elata-biosciences/eeg-web-ble
```
***
## Minimal Integration
```ts theme={null}
import { AthenaWasmDecoder } from "@elata-biosciences/eeg-web";
import { BleTransport } from "@elata-biosciences/eeg-web-ble";
const transport = new BleTransport({
deviceOptions: {
athenaDecoderFactory: () => new AthenaWasmDecoder(),
},
});
transport.onFrame = (frame) => {
console.log(frame.eeg.samples.length);
};
transport.onStatus = (status) => {
console.log(status.state, status.reason);
};
await transport.connect();
await transport.start();
```
***
## Typical Flow
1. Confirm the app is running in a secure context.
2. Construct `BleTransport`.
3. Provide `athenaDecoderFactory` if you need Athena support.
4. Subscribe to frame and status callbacks.
5. Call `connect()` and then `start()`.
***
## When To Use The BLE Template Instead
Prefer the scaffolded `eeg-demo` app, or the dedicated `eeg-ble` starter, when you want:
* a quick environment check for browser BLE support
* a reference for transport startup and status handling
* a simpler starting point than wiring the callbacks from scratch
***
## Common Gotchas
* If `navigator.bluetooth` is missing, you are likely in an unsupported browser or non-secure context.
* If the device chooser never appears, confirm Bluetooth is enabled and the page is served from `https://` or `localhost`.
* If Athena devices fail to decode, make sure you pass an `athenaDecoderFactory` backed by `@elata-biosciences/eeg-web`.
* If you need a normal iOS browser path, plan for a native bridge or hybrid strategy instead of Safari. The browser BLE guidance here assumes Bluefy on iOS.
***
## Next
Step-by-step streaming guide
Browser EEG package model
Transport API and options
Common failures and fixes
# Contributing EEG Transports
Source: https://docs.elata.bio/sdk/maintainers/contributing-eeg-transports
How to add headset transports beyond the built-in Muse path for the Elata browser EEG stack.
The canonical, detailed guide lives in the SDK monorepo:
**[docs/contributing-eeg-transports.md](https://github.com/Elata-Biosciences/elata-bio-sdk/blob/main/docs/contributing-eeg-transports.md)**
## Summary
* **`@elata-biosciences/eeg-web-ble`** is the shared **Web Bluetooth** transport package. It includes a **built-in Muse** implementation and is **not limited to Muse** for contributions.
* **Layout:** `src/transport/` (`BleTransport`) vs `src/devices/muse/` (Muse protocol). Add new vendors under `src/devices//`.
* New hardware should converge on **`HeadbandTransport`** and **`HeadbandFrameV1`** from **`@elata-biosciences/eeg-web`**.
* Prefer a **new device module** in `eeg-web-ble`, a **`BleTransport` `device` adapter**, or a **sibling package** under `packages/` when the integration is large or needs a bridge.
## Recommended Default For Generally Useful Devices
Use an **upstream contribution inside `eeg-web-ble`**:
* `packages/eeg-web-ble/src/devices//...` for protocol, GATT, and decode logic
* reuse `src/transport/bleTransport.ts` for transport and frame behavior
* type adapters against exported `BleDeviceLike` from `@elata-biosciences/eeg-web-ble`
* add mocked Web Bluetooth tests in `src/__tests__/`
* update package docs, maintainer docs, and SDK docs together
* add a changeset if the change should ship
Open a GitHub issue before large protocol or packaging changes. Follow the repo **[CONTRIBUTING guide](https://github.com/Elata-Biosciences/elata-bio-sdk/blob/main/CONTRIBUTING.md)** for PRs, tests, and changesets.
If you need a handoff-ready checklist for external partners, use:
* [Vendor Headset Onboarding Checklist](/sdk/maintainers/vendor-headset-onboarding)
# Related Repos
Source: https://docs.elata.bio/sdk/maintainers/related-repos
Developer-facing context for the public Elata protocol repository and package surfaces that matter when working on the SDK.
The SDK monorepo is not the whole public developer surface.
If you are maintaining browser integrations, validating real product behavior, or following app-launch flows end to end, you will usually need the public protocol repo alongside this one, plus the published npm package surface:
* `../elata-protocol`
* [@elata-biosciences/create-elata-demo on npm](https://www.npmjs.com/package/@elata-biosciences/create-elata-demo)
* [@elata-biosciences/eeg-web on npm](https://www.npmjs.com/package/@elata-biosciences/eeg-web)
* [@elata-biosciences/eeg-web-ble on npm](https://www.npmjs.com/package/@elata-biosciences/eeg-web-ble)
* [@elata-biosciences/rppg-web on npm](https://www.npmjs.com/package/@elata-biosciences/rppg-web)
Use this page as the quick map for when it matters and where developers should start.
## Public Developer Surfaces
| Surface | Reach for it when you need to... |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `../elata-protocol` | understand app launches, local chain setup, deployment outputs, ABIs, XP claim tooling, or protocol behavior that SDK-integrated apps depend on |
| `@elata-biosciences/*` on npm | send developers to the public install path, package docs, and canonical package entrypoints |
## Published Packages
When docs need public links for developers, prefer the package pages:
| Package | npm page |
| -------------------------------------- | ------------------------------------------------------------------------------ |
| `@elata-biosciences/create-elata-demo` | [npm page](https://www.npmjs.com/package/@elata-biosciences/create-elata-demo) |
| `@elata-biosciences/eeg-web` | [npm page](https://www.npmjs.com/package/@elata-biosciences/eeg-web) |
| `@elata-biosciences/eeg-web-ble` | [npm page](https://www.npmjs.com/package/@elata-biosciences/eeg-web-ble) |
| `@elata-biosciences/rppg-web` | [npm page](https://www.npmjs.com/package/@elata-biosciences/rppg-web) |
Lead with these package pages when the goal is helping developers install or evaluate the SDK without cloning repos.
## Example Applications
These browser apps are maintained as separate repositories and deploy to GitHub Pages. They are useful when validating real-world integration patterns, UX for neurofeedback, or multi-package composition:
| App | Demo | Source |
| --------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------- |
| Breathwork Trainer | [GitHub Pages](https://wkyleg.github.io/breathwork-trainer/) | [wkyleg/breathwork-trainer](https://github.com/wkyleg/breathwork-trainer) |
| NeuroFlight | [GitHub Pages](https://wkyleg.github.io/neuroflight/) | [wkyleg/neuroflight](https://github.com/wkyleg/neuroflight) |
| Monkey Mind: Inner Invaders | [GitHub Pages](https://wkyleg.github.io/monkey-mind/) | [wkyleg/monkey-mind](https://github.com/wkyleg/monkey-mind) |
| Neuro Chess | [GitHub Pages](https://wkyleg.github.io/neuro-chess/) | [wkyleg/neuro-chess](https://github.com/wkyleg/neuro-chess) |
| Reaction Trainer | [GitHub Pages](https://wkyleg.github.io/reaction-trainer/) | [wkyleg/reaction-trainer](https://github.com/wkyleg/reaction-trainer) |
Consumer-facing documentation for developers lives on [Example Applications](/sdk/guides/example-apps).
## `elata-protocol`
This repo owns the on-chain protocol surface: app launches, bonding curves, fee routing, staking, governance, XP, and deployment or config generation.
For SDK developers, the important question is usually not "how do I contribute to the contracts?" but "what does an app developer need to know about the protocol their app will launch against or read from?"
That makes this repo relevant when browser or WASM work intersects with:
* contract ABI changes that downstream apps consume
* local environments that need deployed protocol addresses
* XP claim and Merkle workflows
* app-launch lifecycle assumptions reflected in docs, demos, or SDK examples
* launch, staking, fee, or governance concepts that developers need to understand to build on the ecosystem correctly
### Start Here
| Need | Start with |
| ---------------------------------------------------- | --------------------------------------------- |
| Overview of protocol primitives and app-launch model | `../elata-protocol/README.md` |
| Fast local chain setup | `../elata-protocol/QUICKSTART.md` |
| App-launch lifecycle and developer assumptions | `../elata-protocol/docs/APP_LAUNCH_GUIDE.md` |
| Contract math, fee routing, and protocol rules | `../elata-protocol/docs/PROTOCOL_SUMMARY.md` |
| System design and contract relationships | `../elata-protocol/docs/ARCHITECTURE.md` |
| Deeper local environment details | `../elata-protocol/docs/LOCAL_DEVELOPMENT.md` |
### Common Developer Flows
```bash theme={null}
cd ../elata-protocol
npm run local:up
npm run xp:generate
cat deployments/local.json
```
### What SDK Developers Usually Need From It
* `npm run local:up` brings up Anvil, deploys contracts, seeds test data, and produces the addresses and state that app developers need for local launch and integration testing.
* `docs/APP_LAUNCH_GUIDE.md` is the best public place to understand what developers are actually launching into: app registration, token launch, vesting, graduation, and fee behavior.
* `docs/PROTOCOL_SUMMARY.md` is the better source when SDK docs need to explain fee routing, launch parameters, or on-chain assumptions without copying logic into this repo.
* If a developer report sounds like an SDK bug but only reproduces with real launches, fee routing, staking, or XP state, the protocol repo is usually the next place to verify assumptions.
## Working Model
Use the repos together in this order when tracing developer-facing issues:
1. Start in `elata-bio-sdk` for package APIs, docs, and browser-side behavior.
2. Point developers to the published `@elata-biosciences` npm packages when the task is installation, evaluation, or package-level integration.
3. Move to `../elata-protocol` if the issue depends on contracts, addresses, launches, fee routing, staking, governance, or XP roots.
## Keep The Default Path Clear
For consumer-facing docs, continue to lead with SDK packages and `@elata-biosciences/create-elata-demo`, ideally with links to the public npm package pages.
The public protocol repo is supporting context for SDK and ecosystem developers, not the default onboarding path for SDK consumers.
# Releasing
Source: https://docs.elata.bio/sdk/maintainers/releasing
Release, changeset, and recovery workflow for published Elata SDK packages.
## Published Packages
This repository currently publishes:
* `@elata-biosciences/eeg-web`
* `@elata-biosciences/eeg-web-ble`
* `@elata-biosciences/rppg-web`
* `@elata-biosciences/ppg-web`
* `@elata-biosciences/create-elata-demo`
## Quick Reference
| Step | Command |
| -------------------------------------- | ------------------------------------------------------ |
| Add a changeset | `./run.sh changeset` |
| Apply changesets and update changelogs | `./run.sh bump` |
| Run the release preflight | `./run.sh release-check all` |
| Build, publish, tag, and push | `./run.sh release` (defaults to npm dist-tag `latest`) |
| Semver bump all packages, then publish | `./run.sh release patch`, `minor`, or `major` |
| Publish on the `next` dist-tag | `./run.sh release next` or `./run.sh release all next` |
## Maintainer Workflow
1. Apply changesets with `./run.sh bump`.
2. Review the version and changelog diff, then commit it.
3. Run `./run.sh release-check all`.
4. Publish with `./run.sh release` (defaults to npm dist-tag `latest`), or `./run.sh release next` for `next`.
Optional: `./run.sh release patch`, `minor`, or `major` bumps every publishable package with `pnpm version` before publishing. Prefer `./run.sh bump` when cutting a Changesets release so changelogs stay accurate.
Release order is fixed in `run.sh`:
`eeg-web` -> `eeg-web-ble` -> `rppg-web` -> `ppg-web` -> `create-elata-demo`
## Contributor Expectation
If a user-facing package change should ship, include a changeset in the PR.
## Safe Publish Flow
Publish to a non-`latest` channel first:
```bash theme={null}
./run.sh release next
```
After verification, promote or publish as `latest`.
## If A Bad Version Is Published
You cannot overwrite an existing version number. Instead:
1. Deprecate the bad version.
2. Publish a fixed patch version.
3. Move the `latest` dist-tag to the fixed version.
`npm unpublish` is restricted and should not be part of normal recovery.
# Repo Workflows
Source: https://docs.elata.bio/sdk/maintainers/repo-workflows
Canonical maintainer commands and source-of-truth guidance for the Elata SDK monorepo.
## Canonical Commands
Use `run.sh` from the repo root whenever possible:
```bash theme={null}
./run.sh doctor
./run.sh dev all
./run.sh build all
./run.sh test
./run.sh verify-all
```
## What To Use For Common Jobs
| Job | Source of truth |
| ------------------------------------------------------ | --------------------------------------------------------------------------------------- |
| New demo app flow | `@elata-biosciences/create-elata-demo` |
| Consumer-facing install path and package landing pages | [npm package pages](https://www.npmjs.com/package/@elata-biosciences/create-elata-demo) |
| Consumer-facing package docs | package `README.md` files |
| SDK build, test, release orchestration | `run.sh` |
| Public app-launch, protocol, and XP context | `../elata-protocol` |
| Release policy and recovery | [/sdk/maintainers/releasing](/sdk/maintainers/releasing) |
| Repo and package ownership questions | `docs/repo-map.md` in `elata-bio-sdk` |
## Repo Interrogation Order
When you need to confirm whether something is current or canonical:
1. Check the repo `README.md`.
2. Check `run.sh` for command behavior.
3. Check the relevant package `package.json`.
4. Check the nearest package README or docs page.
5. Search the repo with `rg`.
## Verification Rules Of Thumb
* Scaffolder changes: `./run.sh test create-elata-demo`
* Consumer onboarding or packaging changes: `pnpm smoke:consumers`
* `run.sh` changes: run the narrowest affected command, then broaden if release paths changed
* Release tooling changes: `./run.sh verify-all` if feasible
* Docs that mention onboarding: verify the `create-elata-demo` path and the parent-workspace caveat
## Backward Compatibility Notes
* `scripts/dev-link.sh` remains a thin wrapper around `run.sh sync-to`.
* `sync-to` remains useful for local `packages/eeg-web` development.
* Consumer onboarding should still point to `create-elata-demo`, not `sync-to`.
## Related Repos
If you are debugging beyond package boundaries, also use the public protocol repo:
* `../elata-protocol` for contracts, local Anvil deployments, config generation, XP tooling, and simulations
See [Related Repos](/sdk/maintainers/related-repos) for the developer-focused map.
# Vendor Headset Onboarding Checklist
Source: https://docs.elata.bio/sdk/maintainers/vendor-headset-onboarding
Step-by-step checklist for external device companies adding headset support to the Elata browser EEG stack.
Use this checklist when a hardware vendor wants to add headset support.
The canonical repo guide lives in the SDK monorepo:
* [docs/vendor-headset-onboarding-checklist.md](https://github.com/Elata-Biosciences/elata-bio-sdk/blob/main/docs/vendor-headset-onboarding-checklist.md)
## Fast Path Summary
1. Confirm protocol and GATT docs: UUIDs, packet format, sample rate, channel map, and timestamps.
2. Choose the integration path:
* `eeg-web-ble` device module at `src/devices//`
* sibling package such as `packages/eeg-web-` for bridge-heavy flows
* app-local adapter for private proofs of concept
3. Converge on `HeadbandTransport` and `HeadbandFrameV1`.
4. Add mocked BLE and packet tests.
5. Update docs and add a changeset for publishable changes.
For architecture and package expectations, see:
* [Contributing EEG Transports](/sdk/maintainers/contributing-eeg-transports)
# Compatibility
Source: https://docs.elata.bio/sdk/operations/compatibility
Current browser, device, and Node.js expectations for the Elata SDK.
## Package And Tooling Expectations
| Surface | Browser runtime | Node.js | Notes |
| -------------------------------------- | ------------------------------------------ | ------- | ----------------------------------------------------- |
| `@elata-biosciences/create-elata-demo` | n/a | `>= 18` | CLI scaffolder only |
| `@elata-biosciences/eeg-web` | modern browser with WebAssembly | `>= 20` | browser usage depends on serving packaged WASM assets |
| `@elata-biosciences/eeg-web-ble` | Chrome or Edge with Web Bluetooth | `>= 20` | depends on `@elata-biosciences/eeg-web` |
| `@elata-biosciences/rppg-web` | modern browser with camera and WebAssembly | `>= 20` | packaged WASM assets must be reachable by the browser |
***
## Browser Support
| Workflow | Chrome / Edge | Safari macOS | Safari iOS | Notes |
| ----------------------------------- | --------------------------- | ------------- | --------------------------------- | -------------------------------- |
| `create-elata-demo` | n/a | n/a | n/a | scaffolder runs in Node |
| EEG WASM with `eeg-web` | Supported | Supported | Supported | requires packaged `wasm/` assets |
| Muse browser BLE with `eeg-web-ble` | Supported in secure context | Not supported | Not supported | requires Web Bluetooth |
| rPPG with `rppg-web` | Supported | Supported | Supported with camera permissions | requires packaged `pkg/` assets |
***
## Web Bluetooth Expectations
* Use Chrome or Edge.
* Run on `https://` or `localhost`.
* Enable Bluetooth on the machine.
* Expect Safari and iOS browser BLE to be unsupported for this workflow.
`eeg-web-ble` requires Web Bluetooth and an `https://` origin or `localhost`. Safari and iOS do not support Web Bluetooth for Muse browser workflows.
***
## Supported Device Classes
* Muse 2 and Muse S classic BLE devices
* Muse S Athena protocol v2 devices
* The synthetic Muse-compatible BLE bridge used for testing
***
## Safari And iOS Notes
* `eeg-web` can be used as a browser-side WASM package on Safari when your app serves its packaged assets correctly.
* `eeg-web-ble` is not a Safari or iOS browser workflow. Use a native app shell with CoreBluetooth or a companion bridge app streaming frames over WebSocket.
* `rppg-web` can run on Safari and iOS, but camera permissions and packaged WASM delivery still need to be correct.
***
## Package Manager Notes
Generated demo apps work with `pnpm` or `npm`. This repo prefers `pnpm` for
local development.
If you scaffold a demo app inside another `pnpm` workspace and do not add it to
that workspace, use:
```bash pnpm theme={null}
pnpm --dir my-app --ignore-workspace install
pnpm --dir my-app --ignore-workspace run dev
```
```bash npm theme={null}
cd my-app
npm install
npm run dev
```
***
## Related
Common failures and fixes
Package decision guide
Protocol details and browser support
# Troubleshooting
Source: https://docs.elata.bio/sdk/operations/troubleshooting
Common setup failures and the fastest checks for Elata SDK integrations.
## `pnpm install` Did Not Create `node_modules` In My Scaffolded App
You likely created the app inside another `pnpm` workspace.
Run from the parent directory:
```bash pnpm theme={null}
pnpm --dir my-app --ignore-workspace install
pnpm --dir my-app --ignore-workspace run dev
```
```bash npm theme={null}
cd my-app
npm install
npm run dev
```
***
## Web Bluetooth Is Unavailable
Check the following:
* Use Chrome or Edge
* Run on `https://` or `localhost`
* Make sure Bluetooth is enabled on the machine
* Do not expect this workflow to work in Safari or iOS
***
## `loadWasmBackend()` Returned `null`
Make sure your app is serving the packaged `pkg/rppg_wasm.js` and
`pkg/rppg_wasm_bg.wasm` assets from a path the browser can reach.
If you are unsure, compare your app with the scaffolded `rppg-demo` app.
***
## `initEegWasm()` Failed
Your app is probably not serving the packaged `wasm/` assets from
`@elata-biosciences/eeg-web` correctly.
Compare your asset layout with the scaffolded `eeg-demo` app.
If you are using Vite, the two common fixes are: add `vite-plugin-wasm` and `vite-plugin-top-level-await`, or import the WASM asset URL directly and pass it to `initEegWasm(wasmUrl)`.
***
## I Am Not Sure Which Package I Need
Start with [Choose The Right Package](/sdk/guides/choose-the-right-package).
If you still just want the fastest path, use `create-elata-demo`.
***
## Next
Package decision guide
Browser, device, and tooling support
# Getting Started
Source: https://docs.elata.bio/sdk/overview
Cross-platform SDK for EEG signal processing, BLE headband connectivity, and rPPG
## Elata SDK
The Elata SDK is a cross-platform biosignal SDK spanning EEG device pipelines, browser transports, and rPPG processing for web and native clients. It provides four npm packages and a set of Rust crates that compile to WASM and native targets.
**Repository**: [github.com/Elata-Biosciences/elata-bio-sdk](https://github.com/Elata-Biosciences/elata-bio-sdk)
***
## Architecture
```text theme={null}
┌──────────────────────────────────────────────────────┐
│ Elata SDK │
├──────────────────────────────────────────────────────┤
│ Platform Bindings │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ WASM │ │ Swift │ │ Kotlin │ │
│ │ (Browser)│ │ (iOS) │ │(Android) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ └─────────────┴─────────────┘ │
│ │ │
│ Models Layer │ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Alpha Bump │ │ Calmness │ │
│ │ Detector │ │ Model │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ └────────┬──────────┘ │
│ Signal Processing │ │
│ ┌─────────────────────────────────────┐ │
│ │ FFT │ Band Power │ Filtering │ │
│ └─────────────────────────────────────┘ │
│ ▲ │
│ HAL Layer │ │
│ ┌─────────────────────────────────────┐ │
│ │ EegDevice Trait │ │
│ │ connect() │ start_stream() │ read() │ │
│ └─────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
```
***
## Quick Start
The fastest path is to scaffold a demo app with `create-elata-demo`:
```bash theme={null}
# rPPG web demo (default template)
npm create @elata-biosciences/elata-demo my-app
# EEG web demo
npm create @elata-biosciences/elata-demo my-app -- --template eeg-demo
# EEG Web Bluetooth demo
npm create @elata-biosciences/elata-demo my-app -- --template eeg-ble
```
After scaffolding:
```bash theme={null}
cd my-app
pnpm install
pnpm run dev
```
If you're scaffolding inside an existing `pnpm` workspace, use `pnpm --dir my-app --ignore-workspace install` instead.
***
## Choose the Right Package
| Goal | Start here | Notes |
| ------------------------------------------------------ | -------------------------------------- | ---------------------------------------------------- |
| Scaffold a new demo app | `@elata-biosciences/create-elata-demo` | Fastest path for evaluation and onboarding |
| Run EEG WASM APIs in the browser | `@elata-biosciences/eeg-web` | Signal processing, models, and WASM helpers |
| Connect to a Muse-compatible EEG device in the browser | `@elata-biosciences/eeg-web-ble` | Requires `eeg-web` and Web Bluetooth |
| Run camera-based rPPG in a browser app | `@elata-biosciences/rppg-web` | Includes processor, backend loader, and demo helpers |
***
## npm Packages
| Package | Version | Description |
| ------------------------------------------------------------------------------------------------------------ | ------- | -------------------------------------------------------- |
| [`@elata-biosciences/create-elata-demo`](https://www.npmjs.com/package/@elata-biosciences/create-elata-demo) | 0.1.16 | Demo scaffolder with rPPG, EEG, and EEG BLE templates |
| [`@elata-biosciences/eeg-web`](https://www.npmjs.com/package/@elata-biosciences/eeg-web) | 0.1.16 | EEG WASM bindings: signal processing, band power, models |
| [`@elata-biosciences/eeg-web-ble`](https://www.npmjs.com/package/@elata-biosciences/eeg-web-ble) | 0.1.16 | Web Bluetooth transport for EEG headband devices |
| [`@elata-biosciences/rppg-web`](https://www.npmjs.com/package/@elata-biosciences/rppg-web) | 0.1.16 | rPPG pipeline: heart rate from camera via face detection |
### Add packages to an existing app
```bash theme={null}
# EEG signal processing (WASM)
pnpm add @elata-biosciences/eeg-web
# BLE headband connectivity (requires eeg-web)
pnpm add @elata-biosciences/eeg-web-ble @elata-biosciences/eeg-web
# rPPG (camera-based heart rate)
pnpm add @elata-biosciences/rppg-web
```
**Requirements**: Node.js 18+, modern browser with WebAssembly support.
***
## Rust Crates
The SDK is built on Rust crates that compile to WASM and native targets. The
primary public Rust crates are the core and protocol crates; synthetic and
binding crates are mainly internal packaging surfaces.
| Crate | Purpose |
| ------------------ | ------------------------------------------------------------- |
| `elata-eeg-hal` | Core HAL traits: `EegDevice`, `SampleBuffer`, `ChannelConfig` |
| `elata-eeg-signal` | FFT, band power analysis, filtering |
| `elata-eeg-models` | Alpha Bump Detector, Calmness Model |
| `elata-rppg` | rPPG pipeline core |
| `elata-muse-proto` | Muse classic and Athena protocol |
***
## Device Support
| Device | Protocol | Channels | Status |
| ---------------- | ----------- | --------------------------- | ------------ |
| Muse 2 | Classic BLE | 4 EEG (TP9, AF7, AF8, TP10) | Supported |
| Muse S | Classic BLE | 4 EEG + PPG | Supported |
| Muse S (Athena) | Athena v2 | 8 EEG + optics + accgyro | Supported |
| Synthetic Bridge | BLE bridge | Configurable | Testing only |
***
## Compatibility
| Surface | Chrome / Edge | Safari macOS | Safari iOS | Node.js |
| ------------------- | ------------- | ------------- | ----------------------------------- | ------------------------------ |
| `create-elata-demo` | n/a | n/a | n/a | `>= 18` |
| `eeg-web` | Supported | Supported | Supported | `>= 20` for local repo tooling |
| `eeg-web-ble` | Supported | Not supported | Not supported | `>= 20` for local repo tooling |
| `rppg-web` | Supported | Supported | Supported (with camera permissions) | `>= 20` for local repo tooling |
`eeg-web-ble` requires Web Bluetooth and an `https://` origin or `localhost`. Safari and iOS do not support Web Bluetooth for Muse browser workflows. Use a native app shell with CoreBluetooth or a companion bridge app streaming frames over WebSocket.
***
## Build from Source
```bash theme={null}
git clone https://github.com/Elata-Biosciences/elata-bio-sdk.git
cd elata-bio-sdk
./run.sh install # Install dependencies
./run.sh build all # Release build (WASM + TS)
./run.sh test # Run all tests
./run.sh demo eeg # Launch EEG demo
./run.sh demo rppg # Launch rPPG demo
```
***
## Next
Signal processing and models
Connect to headband devices
Heart rate from camera
Package decision guide
Full reference implementations
Scaffold a starter app
# Calibration & Fusion
Source: https://docs.elata.bio/sdk/rppg-web/calibration
Muse PPG calibration and multi-source heart rate fusion
## Overview
When a Muse headband with PPG is available alongside camera-based rPPG, the SDK supports **sensor fusion**, using the contact PPG as a reference to calibrate and improve camera-based heart rate estimates.
***
## MuseFusionCalibrator
Fuses Muse PPG readings with camera rPPG estimates for improved accuracy:
```typescript theme={null}
import { RppgProcessor, MuseFusionCalibrator } from "@elata-biosciences/rppg-web";
const processor = new RppgProcessor("wasm", 30);
const calibrator = new MuseFusionCalibrator();
// Feed Muse PPG readings (from headband)
calibrator.updateMuse(museBpm, quality, timestampMs);
// Feed camera rPPG readings
calibrator.updateCamera(cameraBpm, cameraQuality, timestampMs);
// Get fused result
const fused = calibrator.fuse(cameraBpm, cameraQuality, timestampMs);
```
The `RppgProcessor` also has a built-in Muse integration:
```typescript theme={null}
processor.updateMuseMetrics(museBpm, quality, timestampMs);
```
***
## MuseCalibrationModel
A simple regression model that learns the relationship between camera and contact BPM over time:
```typescript theme={null}
import { MuseCalibrationModel } from "@elata-biosciences/rppg-web";
const model = new MuseCalibrationModel();
// Train with paired observations
model.train(spectralBpm, acfBpm, trueBpm);
// Check if enough data collected
if (model.isTrained()) {
const predictedBpm = model.predict(spectralBpm, acfBpm);
}
// Persistence
const snapshot = model.getSnapshot();
model.loadSnapshot(snapshot);
model.reset();
```
***
## BPM Evidence Types
The processor produces evidence from multiple analysis methods:
```typescript theme={null}
type BpmEvidenceSource = "spectral" | "acf" | "tracker" | "muse" | "calibrated";
type BpmEvidence = {
source: BpmEvidenceSource;
bpm: number;
quality: number;
timestampMs: number;
};
type BpmResolutionResult = {
bpm: number;
quality: number;
source: BpmEvidenceSource;
evidence: BpmEvidence[];
};
type FusionSource = "camera" | "muse" | "fused";
```
***
## Muse PPG Filter
Apply Muse-style bandpass filtering to PPG samples:
```typescript theme={null}
import { museStyleFilter } from "@elata-biosciences/rppg-web";
const filtered = museStyleFilter(ppgSamples, sampleRate);
```
***
## Calibration Workflow
```mermaid theme={null}
flowchart TD
MUS["Muse Headband PPG"] -->|"updateMuse(bpm)"| CAL["MuseFusionCalibrator"]
CAM["Camera rPPG"] -->|"updateCamera(bpm)"| CAL
CAL -->|"fuse()"| FUSED["Fused BPM Estimate"]
CAM -->|"spectral + ACF"| MOD["MuseCalibrationModel"]
MUS -->|"true BPM"| MOD
MOD -->|"predict()"| CALIBRATED["Calibrated Camera BPM"]
```
1. Start with camera-only rPPG
2. When a Muse headband connects, feed its PPG as ground truth
3. The calibration model learns the camera-to-contact mapping
4. Once trained, camera-only estimates are corrected using the learned model
5. The fusion calibrator combines both sources for maximum accuracy
***
## Next
Camera capture and face detection
End-to-end webcam pipeline
# Frame Sources
Source: https://docs.elata.bio/sdk/rppg-web/frame-sources
Camera capture and face detection with MediaPipe
## FrameSource Interface
All frame sources implement this interface:
```typescript theme={null}
interface FrameSource {
onFrame: ((frame: Frame) => void) | null;
start(): Promise;
stop(): Promise;
}
type Frame = {
data: Uint8ClampedArray | number[]; // RGBA pixel data
width: number;
height: number;
roi?: ROI;
rois?: ROI[];
timestampMs?: number;
};
type ROI = { x: number; y: number; w: number; h: number };
```
***
## MediaPipeFrameSource
Captures camera frames and uses MediaPipe for face detection, providing a face ROI with each frame:
```typescript theme={null}
import { MediaPipeFrameSource } from "@elata-biosciences/rppg-web";
const source = new MediaPipeFrameSource();
source.onFrame = (frame) => {
// frame.roi contains the detected face bounding box
// frame.data contains the camera image pixels
console.log("Face ROI:", frame.roi);
};
await source.start();
// ... later
await source.stop();
```
***
## MediaPipeFaceFrameSource
A more specialized variant that uses MediaPipe FaceMesh for precise facial landmark detection. Provides multiple ROIs for forehead, cheeks, etc.:
```typescript theme={null}
import { MediaPipeFaceFrameSource, loadFaceMesh } from "@elata-biosciences/rppg-web";
const faceMesh = await loadFaceMesh();
const source = new MediaPipeFaceFrameSource(faceMesh);
source.onFrame = (frame) => {
// frame.rois may contain multiple face regions
console.log("Face regions:", frame.rois?.length);
};
await source.start();
```
***
## loadFaceMesh
Helper to load the MediaPipe FaceMesh model:
```typescript theme={null}
import { loadFaceMesh } from "@elata-biosciences/rppg-web";
const faceMesh = await loadFaceMesh();
```
***
## DemoRunner
`DemoRunner` orchestrates a frame source and `RppgProcessor` together, handling ROI extraction, skin masking, and stats reporting:
```typescript theme={null}
import { DemoRunner, MediaPipeFrameSource, RppgProcessor } from "@elata-biosciences/rppg-web";
import type { DemoRunnerOptions } from "@elata-biosciences/rppg-web";
const source = new MediaPipeFrameSource();
const processor = new RppgProcessor("wasm", 30);
const options: DemoRunnerOptions = {
useSkinMask: true,
onStats: (stats) => {
console.log(`Green: ${stats.intensity}, Skin: ${stats.skinRatio}, FPS: ${stats.fps}`);
},
};
const runner = new DemoRunner(source, processor, options);
await runner.start();
// ... later
await runner.stop();
```
### DemoRunnerOptions
| Option | Type | Default | Description |
| ------------------------- | ------------- | ------- | ---------------------------------------------- |
| `roi` | `ROI \| null` | `null` | Fixed ROI override (null = use face detection) |
| `sampleRate` | `number` | - | Override sample rate |
| `roiSmoothingAlpha` | `number` | - | Exponential smoothing for ROI jitter |
| `useSkinMask` | `boolean` | `false` | Apply YCbCr skin tone mask |
| `skinRatioSmoothingAlpha` | `number` | - | Smooth the skin ratio metric |
| `onStats` | `function` | - | Callback with per-frame statistics |
***
## ROI Helper
Extract average green channel intensity from a region of interest:
```typescript theme={null}
import { averageGreenInROI } from "@elata-biosciences/rppg-web";
const intensity = averageGreenInROI(frame, roi.x, roi.y, roi.w, roi.h);
```
***
## Next
Muse PPG calibration models
Install and configure rppg-web
# Getting Started
Source: https://docs.elata.bio/sdk/rppg-web/getting-started
Camera-based heart rate measurement with @elata-biosciences/rppg-web
## What is rPPG?
Remote photoplethysmography (rPPG) extracts heart rate from subtle color changes in facial skin captured by a standard camera. No contact sensor is needed, just a webcam.
***
## When To Use This Package
Use `@elata-biosciences/rppg-web` when your app needs:
* camera-based biosignal processing in the browser
* a higher-level rPPG session API instead of raw WASM orchestration
* diagnostics and app-facing helpers around rPPG sessions
***
## Installation
```bash pnpm theme={null}
pnpm add @elata-biosciences/rppg-web
```
```bash npm theme={null}
npm install @elata-biosciences/rppg-web
```
Requirements: Node.js 20+, browser with camera access and WebAssembly support.
***
## Recommended Usage: `createRppgSession`
For most browser apps, start with `createRppgSession()`. It handles WASM init, frame capture, ROI orchestration, diagnostics, and cleanup.
```typescript theme={null}
import { createRppgSession } from "@elata-biosciences/rppg-web";
const session = await createRppgSession({
video: videoEl,
sampleRate: 30,
backend: "auto",
faceMesh: "off",
onDiagnostics: (diagnostics) => {
console.log(diagnostics.state.status, diagnostics.faceTrackingMode);
console.log(diagnostics.framesSeen, diagnostics.totalSamplesReceived);
},
});
const metrics = session.getMetrics();
console.log("BPM:", metrics.bpm);
// Stop and release camera on cleanup
await session.stop();
```
Use `createManagedRppgSession()` if you also want automatic restart after terminal processor failures.
***
## Diagnostics Guidance
Every session diagnostics payload includes state, issue codes, sampling stats, and processor failure information.
* If `session.state.status` becomes `failed`, treat the underlying processor as terminal and recreate the session.
* If `backend: "auto"` falls back to an unavailable backend mode, diagnostics report that state instead of failing silently.
***
## Advanced: `RppgProcessor`
For custom orchestration, drop to `RppgProcessor` directly. Only use this when you need something `createRppgSession()` does not provide. If you are debugging, compare against `createRppgSession()` first.
```typescript theme={null}
import { RppgProcessor } from "@elata-biosciences/rppg-web";
const processor = new RppgProcessor("wasm", 30); // backend, sampleRate
// Feed green-channel intensity from face ROI each frame
processor.pushSample(performance.now(), greenIntensity);
const metrics = processor.getMetrics();
console.log("BPM:", metrics.bpm);
```
### Constructor
```typescript theme={null}
new RppgProcessor(backend: Backend, sampleRate: number, windowSeconds?: number)
```
| Parameter | Type | Description |
| --------------- | ---------------------- | ------------------------------------- |
| `backend` | `"wasm"` or `"native"` | Processing backend |
| `sampleRate` | `number` | Expected frames per second (e.g., 30) |
| `windowSeconds` | `number` | Analysis window length (default: 10) |
### Pushing Samples
```typescript theme={null}
processor.pushSample(timestampMs, intensity); // green channel only
processor.pushSampleRgb(timestampMs, r, g, b, skinRatio?);
processor.pushSampleRgbMeta(timestampMs, r, g, b, skinRatio?, motion?, clipRatio?);
```
### Metrics
| Field | Type | Description |
| ------------- | -------- | --------------------------- |
| `bpm` | `number` | Estimated heart rate in BPM |
| `quality` | `number` | Signal quality (0 to 1) |
| `spectralBpm` | `number` | Spectral analysis estimate |
| `acfBpm` | `number` | Autocorrelation estimate |
| `confidence` | `number` | Overall confidence |
***
## Key Exports
* `createRppgSession`
* `createManagedRppgSession`
* `RppgProcessor`
* `loadWasmBackend`
* `createRppgAppAdapter`
* `createRppgAppMonitor`
* `normalizeRppgError`
* `computeTraceWaveformDebug`
* `ensureVideoPlaying`
***
## Next
Step-by-step integration guide
MediaPipe face detection and camera capture
Muse fusion and calibration models
End-to-end rPPG setup
# Stream EEG over Web Bluetooth
Source: https://docs.elata.bio/sdk/tutorials/eeg-ble-live-stream
Build a browser EEG flow that discovers a supported device, connects, and starts receiving frames.
Use this page if you already have a browser app and need the recommended
existing-app BLE transport path.
If you want the scaffold path instead, use [Quickstart](/sdk/tutorials/first-app). If you
only need the transport model first, use [Web Bluetooth With Supported Devices](/sdk/guides/web-bluetooth).
This tutorial builds on the browser EEG package and adds live Web Bluetooth
transport with `@elata-biosciences/eeg-web-ble`.
Use this when your app needs real headset data in Chrome or Edge.
This step **turns on Web Bluetooth** for a Muse-compatible headset on top of the
EEG stack. It comes **after** you are committed to EEG (and usually **after**
[rPPG](/sdk/guides/rppg-browser) if your product also uses camera biosignals).
## Before You Start
You need:
* Chrome or Edge
* `https://` or `localhost`
* Bluetooth enabled on the machine
* a supported Muse-compatible device
* `@elata-biosciences/eeg-web` installed alongside `@elata-biosciences/eeg-web-ble`
Safari and iOS are not supported for this browser BLE workflow.
## What You Will Build
You will:
1. install the BLE transport package
2. create a `BleTransport`
3. subscribe to frame and status events
4. connect and start streaming
## Step 1: Install The Packages
```bash pnpm theme={null}
pnpm add @elata-biosciences/eeg-web @elata-biosciences/eeg-web-ble
```
```bash npm theme={null}
npm install @elata-biosciences/eeg-web @elata-biosciences/eeg-web-ble
```
## Step 2: Transport module + connect (single paste)
Use one module so imports, handlers, and lifecycle stay together (for example
`src/headbandBle.ts`). Call `connectHeadband()` from a **button** click (browser
BLE requires a user gesture).
```ts headbandBle.ts theme={null}
import { AthenaWasmDecoder } from "@elata-biosciences/eeg-web";
import { BleTransport } from "@elata-biosciences/eeg-web-ble";
let transport: BleTransport | null = null;
export function createHeadbandTransport() {
const next = new BleTransport({
deviceOptions: {
athenaDecoderFactory: () => new AthenaWasmDecoder(),
},
});
next.onStatus = (status) => {
console.log("status", status.state, status.reason);
};
next.onFrame = (frame) => {
console.log("eeg samples", frame.eeg.samples.length);
};
return next;
}
export async function connectHeadband() {
transport = createHeadbandTransport();
try {
await transport.startStreaming();
} catch (error) {
console.error("BLE start failed", error);
}
}
```
Why include `athenaDecoderFactory` up front:
* it keeps Athena-compatible devices working
* it uses the supported decoder path from `@elata-biosciences/eeg-web`
* it avoids a common failure mode later when testing across device variants
`startStreaming()` is the recommended default because it handles the common
`connect()` plus `start()` sequence in one call and avoids a frequent mistake
where apps connect successfully but never actually begin streaming.
## Step 3: Turn Frames Into App State
Once `onFrame` starts firing, move the data into your own app state instead of
leaving it only in `console.log`.
The usual pattern is:
1. receive `HeadbandFrameV1` frames
2. extract EEG samples or metadata
3. compute or forward the values your app cares about
4. render charts, scores, or adaptation logic
## Step 4: Clean Up On Route Or Component Exit
When the current view is leaving, stop streaming so later reconnect attempts
start from a clean state. Add this next to the helpers above (same module as
`transport`). Append to **headbandBle.ts**:
```ts theme={null}
export async function stopHeadband() {
if (!transport) return;
await transport.stop();
transport = null;
}
```
If you prefer finer-grained lifecycle control, you can still call
`connect()` and `start()` separately. The tutorial uses `startStreaming()`
because it is the safest default for most app integrations.
## Step 5: Handle Browser And Platform Constraints
If the chooser never appears or `navigator.bluetooth` is missing, check these
first:
* the page is running on `https://` or `localhost`
* you are using Chrome or Edge
* Bluetooth is enabled
* the target device is powered on and available
## Athena And Classic Devices
This repo supports:
* Muse 2 and Muse S classic BLE devices
* Muse S Athena protocol v2 devices
Including `athenaDecoderFactory` is the simplest supported way to keep both
flows covered.
## Common Problems
* `navigator.bluetooth` is undefined: unsupported browser or insecure context
* No device chooser appears: Bluetooth disabled or page not running on a secure origin
* Athena decoding fails: make sure you passed `athenaDecoderFactory`
* You need Safari or iOS support: this browser package is not the right path; use a native or bridge strategy
## Next
Transport API and options
EEG runtime and models
Transport model overview
# Add EEG to an existing browser app
Source: https://docs.elata.bio/sdk/tutorials/eeg-existing-app
Step by step, wire Elata EEG processing into a browser app and verify the WASM path works.
Use this page if you already have a browser app and want the recommended
existing-app EEG integration path.
If you want the scaffold path instead, use [Quickstart](/sdk/tutorials/first-app). If you
only need the package model first, use [EEG In A Browser App](/sdk/guides/eeg-browser).
This tutorial shows the recommended path for adding
`@elata-biosciences/eeg-web` to an existing browser application.
EEG is **optional** in the typical Elata journey. **Camera rPPG** is the usual
primary integration unless your product needs brain-signal features.
Use this when your app needs EEG analysis APIs in the browser.
If you also need live headset transport, you will add
`@elata-biosciences/eeg-web-ble` after this tutorial.
## What You Will Build
You will:
1. install `@elata-biosciences/eeg-web`
2. initialize the EEG WASM runtime
3. run a simple analysis call
4. verify your app can serve the packaged WASM assets
## Step 1: Install The Package
```bash pnpm theme={null}
pnpm add @elata-biosciences/eeg-web
```
```bash npm theme={null}
npm install @elata-biosciences/eeg-web
```
## Step 2: Create A Small EEG Module
Create a small module in your app so the integration stays isolated and easy to
test (for example `src/eeg.ts`):
```ts eeg.ts theme={null}
import { initEegWasm, band_powers } from "@elata-biosciences/eeg-web";
let eegInitPromise: Promise | null = null;
async function ensureEegReady() {
if (!eegInitPromise) {
eegInitPromise = initEegWasm();
}
await eegInitPromise;
}
export async function analyzeExampleEeg() {
await ensureEegReady();
const eegData = new Float32Array([0.2, 0.5, 0.1, -0.3, -0.4, 0.1, 0.2, 0.6]);
const powers = band_powers(eegData, 256);
return powers;
}
```
Why this shape:
* `initEegWasm()` should run before using analysis helpers
* the module-level Promise avoids repeated initialization, including concurrent calls
* keeping it in one file makes it easier to swap in real samples later
## Step 3: Call It From Your UI
For example, in a component or entry file:
```ts theme={null}
import { analyzeExampleEeg } from "./eeg";
const powers = await analyzeExampleEeg();
console.log("alpha", powers.alpha);
console.log("beta", powers.beta);
```
At this stage you are not using a real headset yet. You are proving that the
browser app can load the runtime and execute the EEG functions correctly.
## Step 4: Verify The WASM Asset Path
Run your app in development and confirm the module initializes without errors.
If `initEegWasm()` fails, the most likely problem is that your bundler or app
deployment is not serving the packaged `wasm/` assets correctly.
This is the first thing to fix before adding more product logic.
If you are using Vite, the two common fixes are:
1. add `vite-plugin-wasm` and `vite-plugin-top-level-await`, then keep using
`await initEegWasm()`
2. import the WASM asset URL directly and pass it to `initEegWasm(wasmUrl)`
The guide at [EEG In A Browser App](/sdk/guides/eeg-browser) shows both patterns.
If you are stuck, compare your app against the scaffolded `eeg-demo`
app before assuming the package itself is broken.
## Step 5: Replace Example Data With Real App Data
Once the example call works, swap the placeholder array with actual EEG sample
buffers from your app.
The common pattern is:
1. receive or load EEG samples
2. normalize them into typed arrays
3. call Elata analysis helpers
4. map the result into app state, feedback, scoring, or visualizations
That last step is where your product behavior lives.
## When To Add `eeg-web-ble`
Add `@elata-biosciences/eeg-web-ble` when you need live browser transport from
a supported Muse-compatible headset.
Do not start there if the plain EEG runtime is not working yet. Get WASM
loading working first, then add transport.
## Common Problems
* `initEegWasm()` throws: your app is probably not loading packaged `wasm/` assets correctly
* You expected device discovery: `eeg-web` does not do Bluetooth transport by itself
* You are evaluating the SDK from scratch: start with [Build Your First Elata App](/sdk/tutorials/first-app) instead of manual setup
## Next
Stream Muse EEG over BLE
Package API and exports
Integration overview
# Build your first Elata App
Source: https://docs.elata.bio/sdk/tutorials/first-app
Scaffold a working app first, then use it as the reference point for deeper SDK integration.
Use this page if you are new to the SDK and want one successful scaffolded app
running locally before you integrate anything manually.
If you want the full map (**rPPG primary**, **EEG optional**, **Bluetooth to
connect a headset**) with scaffold vs step-by-step vs overview, use
[Build A Browser App](/sdk/overview).
The goal is simple: get a working app running locally, understand which
template maps to which sensor workflow, and know where to go next.
## What You Will Build
You will scaffold one of the published starter apps with
`@elata-biosciences/create-elata-demo`.
Pick the template that matches your goal:
* `rppg-demo`: **primary**: camera-based pulse/rPPG app
* `eeg-demo`: **optional**: browser EEG processing with synthetic data
* `eeg-ble` alias: **Bluetooth**: use the EEG starter app with the BLE-focused alias when you want a Muse-compatible headset flow
## Why Start Here
This is the recommended default because it gives you:
* a working app structure
* pinned compatible package versions
* a reference implementation you can compare your own app against
Do this before cloning repo demos or using internal maintainer workflows.
## Step 1: Choose A Template
Use this decision table:
| If you want to... | Choose |
| ------------------------------------------------------------- | ------------------------------ |
| Build the default camera-based app (no extra hardware) | `rppg-demo` |
| Add browser EEG only (synthetic or offline samples first) | `eeg-demo` |
| Enable a Muse-compatible headset over Bluetooth (Chrome/Edge) | `eeg-ble` alias for `eeg-demo` |
If you are unsure, start with `rppg-demo`.
## Step 2: Scaffold The App
Start with the interactive chooser:
```bash pnpm theme={null}
pnpm create @elata-biosciences/elata-demo my-app
```
```bash npm theme={null}
npm create @elata-biosciences/elata-demo my-app
```
If you prefer the explicit default path instead, scaffold `rppg-demo`
directly:
```bash pnpm theme={null}
pnpm create @elata-biosciences/elata-demo my-app -- --template rppg
```
```bash npm theme={null}
npm create @elata-biosciences/elata-demo my-app -- --template rppg
```
If you want to see the full template list first:
```bash pnpm theme={null}
pnpm dlx @elata-biosciences/create-elata-demo -- --list-templates
```
```bash npm theme={null}
npx @elata-biosciences/create-elata-demo -- --list-templates
```
The full alias and template matrix lives on [create-elata-demo](/sdk/create-elata-demo).
## Step 3: Install And Run
Copy **one** column (pnpm or npm), then run each line in order:
```bash pnpm theme={null}
cd my-app
pnpm install
pnpm run dev
```
```bash npm theme={null}
cd my-app
npm install
npm run dev
```
## Step 4: Confirm What You Have
Once the app starts, verify the expected behavior:
* `rppg-demo`: asks for camera access and starts a pulse-style session
* `eeg-demo`: loads EEG processing in the browser and shows synthetic-data-driven output
* `eeg-ble` alias for `eeg-demo`: adds Bluetooth pairing and streaming guidance for a supported Muse-compatible headset on top of EEG
If your goal is just evaluation, stop here first and learn from the generated
app before integrating into an existing codebase.
## Step 5: Understand The Generated App
Each scaffolded app gives you:
* a minimal Vite + React shell
* Elata packages already wired in
* a `README.md` with template-specific notes
* a `build` script so you can confirm the app compiles cleanly
This is meant to be your known-good baseline.
When a manual integration goes wrong later, compare your app against this
generated one before assuming the package is broken.
## Common Gotcha: Scaffolding Inside Another `pnpm` Workspace
If you create `my-app` inside another repository that already has a
`pnpm-workspace.yaml`, `pnpm install` may attach to the parent workspace
instead of the generated app.
Run these **one line at a time** from the parent directory (adjust `my-app` if needed):
```bash pnpm theme={null}
pnpm --dir my-app --ignore-workspace install
pnpm --dir my-app --ignore-workspace run dev
```
```bash npm theme={null}
cd my-app
npm install
npm run dev
```
## Where To Go Next
You already have a running scaffold. The recommended order is **camera rPPG
first** (most people’s primary app), **browser EEG second if the product needs
brain signals**, then **Web Bluetooth to connect a headset** (transport on top
of EEG). Each step splits the same way: **stay on the scaffold** or **integrate
into an app you already have**.
### Next: Camera (rPPG): primary
Pick the row that matches you:
| Your situation | What to do next |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **New app**: you will keep building from the generated `rppg-demo` scaffold | Use [rPPG In A Browser App](/sdk/guides/rppg-browser) for the integration model, then [rppg-web](/sdk/rppg-web/getting-started) while you change the template. For scaffold CLI details: [create-elata-demo](/sdk/create-elata-demo). |
| **Existing app**: you need rPPG inside a codebase you already ship | Follow [Add Camera-Based rPPG To An Existing Browser App](/sdk/tutorials/rppg-existing-app) step by step. |
### Then: Browser EEG: optional (no headset yet)
| Your situation | What to do next |
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **New app**: extend or respin `eeg-demo` | [EEG In A Browser App](/sdk/guides/eeg-browser), then [eeg-web](/sdk/eeg-web/getting-started). Scaffold reference: [create-elata-demo](/sdk/create-elata-demo). |
| **Existing app** | [Add EEG To An Existing Browser App](/sdk/tutorials/eeg-existing-app). |
### Then: Enable Bluetooth headset (Muse-compatible)
| Your situation | What to do next |
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **New app**: extend `eeg-demo` via the `eeg-ble` alias path | [Web Bluetooth With Supported Devices](/sdk/guides/web-bluetooth), then [eeg-web-ble](/sdk/eeg-web-ble/getting-started) (and [eeg-web](/sdk/eeg-web/getting-started)). |
| **Existing app** | [Stream Muse-Compatible EEG Over Web Bluetooth](/sdk/tutorials/eeg-ble-live-stream). If WASM is not wired yet, do the EEG row above first. |
### Full map
For the same choices in one place in **priority order** (rPPG, then EEG, then
Bluetooth), use [Build A Browser App](/sdk/overview).
### Example Apps
Full product-shaped reference implementations
Package decision guide
# Add camera-based rPPG to an existing browser App
Source: https://docs.elata.bio/sdk/tutorials/rppg-existing-app
Build a working browser rPPG flow with createRppgSession, diagnostics, and cleanup.
Use this page if you already have a browser app and want the recommended existing-app integration path.
If you want the scaffold path instead, use [Quickstart](/sdk/tutorials/first-app). If you only need the mental model first, use [rPPG In A Browser App](/sdk/guides/rppg-browser).
This tutorial shows the recommended app integration path for `@elata-biosciences/rppg-web`.
Camera rPPG is the **primary** browser integration for many Elata products. No headset required.
The core idea is simple: let `createRppgSession()` own the browser runtime, video processing loop, and diagnostics while your app owns the UI.
This is the usual **next tutorial** after [Build Your First Elata App](/sdk/tutorials/first-app) when you choose the **existing app** branch for camera rPPG. If you are **extending the scaffold** instead, stay on `rppg-demo` and use [rPPG In A Browser App](/sdk/guides/rppg-browser) plus [rppg-web](/sdk/rppg-web/getting-started).
## What You Will Build
You will:
1. install `@elata-biosciences/rppg-web`
2. request camera access
3. attach the stream to a `video` element
4. start `createRppgSession()`
5. read metrics and diagnostics
6. stop the session during cleanup
## Step 1: Install The Package
```bash pnpm theme={null}
pnpm add @elata-biosciences/rppg-web
```
```bash npm theme={null}
npm install @elata-biosciences/rppg-web
```
## Step 2: Prepare A Video Element
Your app needs a `video` element that can receive the camera stream.
```html index.html (or your component markup) theme={null}
```
The important parts are:
* `autoplay` so playback can start once the stream is attached
* `playsinline` for mobile browser behavior
* `muted` to keep autoplay rules out of the way
## Step 3: Acquire Camera Access
```ts camera setup theme={null}
const videoEl = document.getElementById("camera") as HTMLVideoElement;
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: "user" },
audio: false,
});
videoEl.srcObject = stream;
await videoEl.play();
```
At this point your browser app should already be showing the camera preview.
## Step 4: Start `createRppgSession()`
```ts rPPG session theme={null}
import { createRppgSession } from "@elata-biosciences/rppg-web";
const session = await createRppgSession({
video: videoEl,
sampleRate: 30,
backend: "auto",
faceMesh: "off",
onDiagnostics: (diagnostics) => {
console.log("status", diagnostics.state.status);
console.log("frames", diagnostics.framesSeen);
console.log("samples", diagnostics.totalSamplesReceived);
console.log("issues", diagnostics.issues);
},
onError: (error) => {
console.error(error.code, error.message);
},
});
```
This is the recommended starting point for most browser apps.
It handles:
* packaged WASM backend init
* frame capture
* ROI/session orchestration
* diagnostics emission
* cleanup support
## Step 5: Read Metrics In Your UI
```ts theme={null}
const metrics = session.getMetrics();
console.log(metrics);
```
In a real app you would poll or subscribe through your own UI state layer and show the values that matter to your product.
## Step 6: Clean Up Correctly
When the component, route, or page is leaving, stop the session and release the camera stream. Run these in order (same `session` and `stream` as above):
```ts theme={null}
await session.stop();
```
```ts theme={null}
for (const track of stream.getTracks()) {
track.stop();
}
```
This matters more than it looks. It keeps later sessions from inheriting stale camera or runtime state.
## Full example (single paste)
Use this when you want one file to drop into a Vite + React app (for example replace the contents of `src/App.tsx`). It includes camera setup, session start, and cleanup on unmount.
```tsx App.tsx theme={null}
import { useEffect, useRef, useState } from "react";
import { createRppgSession, type RppgSession } from "@elata-biosciences/rppg-web";
export default function App() {
const videoRef = useRef(null);
const sessionRef = useRef(null);
const streamRef = useRef(null);
const [line, setLine] = useState("Starting…");
useEffect(() => {
let cancelled = false;
async function run() {
const video = videoRef.current;
if (!video) return;
let stream: MediaStream;
try {
stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: "user" },
audio: false,
});
} catch {
setLine("Camera permission denied.");
return;
}
if (cancelled) {
stream.getTracks().forEach((t) => t.stop());
return;
}
streamRef.current = stream;
video.srcObject = stream;
await video.play().catch(() => undefined);
const sampleRate = stream.getVideoTracks()[0]?.getSettings().frameRate ?? 30;
try {
const session = await createRppgSession({
video,
sampleRate,
backend: "auto",
faceMesh: "off",
onDiagnostics: (d) => {
setLine(`status=${d.state.status} backend=${d.backendMode}`);
},
onError: (e) => setLine(`${e.code}: ${e.message}`),
});
if (cancelled) {
await session.dispose();
return;
}
sessionRef.current = session;
} catch (e) {
setLine(e instanceof Error ? e.message : "Session failed");
}
}
void run();
return () => {
cancelled = true;
void sessionRef.current?.dispose();
sessionRef.current = null;
streamRef.current?.getTracks().forEach((t) => t.stop());
streamRef.current = null;
};
}, []);
return (
{line}
);
}
```
This example uses a React `ref` on `