# MDK Docs (/v0-2-0) } title={Learn what MDK is} href="/v0-2-0/concepts/about" description={ Product overview, architecture, and the MDK packages } /> } title={Get started path picker} href="/v0-2-0/get-started" description={ Discover your path, including backend tools and the raw client } /> } title={Ship a dashboard fast} description={ <> Two ways to ship a mining dashboard: Build with your AI agent{' '} Browse the UI Kit{' '} } /> } title={Use UI Core without a dashboard} href="/v0-2-0/how-to/ui/core/use-ui-core-headlessly" description={ @tetherto/mdk-ui-core is headless: Zustand stores and a QueryClient factory you can wire into any runtime } /> I'm an AI agent |{' '} I am building with one } subtitle="Optimize your development workflow by bridging the gap between large language models and high-performance mining infrastructure." > } title={Build dashboards with your AI agent} href="/v0-2-0/agents" description={ Wire your LLM to MDK with the UI CLI, then build from plain-language prompts } /> } title={Full docs in one file} href="/v0-2-0/llms-full.txt" description={ Every page of these docs in one plain-text fileβ€”open in the browser to copy or save } /> # Build dashboards with your AI agent (/v0-2-0/agents) MDK is built so your AI coding agent can build mining dashboards straight from plain-language prompts, without you wiring components by hand. There are two steps: wire your IDE once, then describe what you want.
Build any dashboard with an agent Stats Lab walkthrough with UI CLI commands β€” no fleet backend Runtime agents via MCP Server Monitor and control a live fleet at runtime through the MCP server
## Wire your IDE once ### Run init Point the UI CLI at your IDE from your project root: ```bash npx @tetherto/mdk-ui-cli init --ide cursor ``` Use `--ide claude` for Claude. This writes a `.mdk/context.md` agent-context file and an IDE rule (`.cursor/rules/mdk.mdc` for Cursor, `CLAUDE.md` for Claude) so every AI session knows about MDK automatically. ### Prompt your agent Describe the dashboard you want in plain language: > Build me an operations dashboard with live hashrate and a device list. Your agent takes it from there: it finds the right components and hooks, scaffolds the page, and checks that it compiles. You review and run it. ## What your agent does for you Behind a single prompt, the agent: - Finds the right MDK components and hooks for your intent - Wires real adapter hooks and state, with no guessed imports or props - Scaffolds a page and verifies that it compiles - Reaches for stable, supported components by default, so the result is something you can ship The outcome: the agent pulls real metadata, real examples, and real types instead of hallucinating an API. ## How it works You do not need any of this to use the tooling, it is here to explain why the results are trustworthy. MDK ships a small set of machine-readable files that describe every component, hook, and store. Your agent reads those local files, makes no network or model calls of its own to discover them, and only reaches for exports that MDK marks as stable. Because it works from generated metadata rather than guesswork, it does not invent props or imports. ```mermaid flowchart LR prompt["Your prompt"] agent["AI agent"] manifests["MDK local manifests"] page["Scaffolded, verified page"] prompt --> agent agent --> manifests manifests --> agent agent --> page ``` For the full command surface those manifests power, see the [UI CLI reference](/v0-2-0/reference/app-toolkit/ui-cli). The complete contract that keeps the metadata honest lives in the [MDK repositories](/v0-2-0/resources/repositories). ## Build any dashboard (example walkthrough) MDK is not mining-only. This concept page replays the same agent flow above and ships a **statistics lab** dashboard with charts, tables, and mock data. ## Next steps - [UI CLI reference](/v0-2-0/reference/app-toolkit/ui-cli): every command your agent (or you) can run - [Ship a dashboard fast](/v0-2-0/ui/react/quickstart): wire the React packages by hand - [UI Kit](/v0-2-0/ui): browse the component library your agent draws from - [AI agents and the MCP Server](/v0-2-0/concepts/architecture#ai-agents-and-the-mcp-server): runtime path for live fleet control # Code of Conduct (/v0-2-0/community/code-of-conduct) ## Our Commitment MDK is committed to fostering an open, professional, and respectful community. We welcome contributors of all backgrounds and experience levels. Participation in the MDK community should be harassment-free and inclusive for everyone. --- ## Expected Behavior All participants in the MDK community are expected to: - Be respectful and constructive in communication - Provide helpful and professional feedback - Assume good intent - Focus on what is best for the project - Accept constructive criticism gracefully --- ## Unacceptable Behavior The following behaviors are not tolerated: - Harassment, discrimination, or hateful conduct - Personal attacks or insulting language - Public or private harassment - Trolling, intimidation, or deliberate disruption - Publishing private information without consent - Any conduct that would be considered unprofessional in a workplace setting --- ## Scope This Code of Conduct applies to: - GitHub repositories - Issues and pull requests - Discussions and community channels - Official MDK communication platforms - Any other space officially associated with the MDK project --- ## Enforcement The Community Manager is responsible for enforcing this Code of Conduct. **Community Manager:** Gio\ **Lead Maintainer:** Hemant T If you experience or witness unacceptable behavior, report it privately to the Community Manager. Reports will be handled confidentially and reviewed in coordination with the MDK core team. --- ## Enforcement Guidelines The MDK core team may take any action deemed appropriate, including: - Warning the participant - Temporarily restricting access - Permanently banning a participant from the community - Removing content that violates this Code of Conduct Decisions regarding enforcement are final. --- ## Amendments This Code of Conduct may be updated from time to time by the MDK core team to reflect evolving community needs. # Contributing (/v0-2-0/community/contributing) # Contribute to MDK Thank you for your interest in contributing to [MDK](https://github.com/tetherto/mdk/). This document outlines the contribution workflow for the MDK repository, from setting up your development environment to submitting pull requests and participating in releases. ## Security If you discover a security vulnerability, do not report it in a public issue. Please follow the private disclosure instructions in [SECURITY.md](https://github.com/tetherto/mdk/blob/main/SECURITY.md). ## Monorepo structure MDK is a monorepo with separate backend and frontend workspaces: - Backend: - `backend/core/`: Backend services, container modules, and integration/unit tests (npm-based) - `backend/workers/`: Protocol-translator worker packages (miners, miner-pools, power-meter, temperature, containers), per-worker mock servers, and per-worker tests (npm-based) - Frontend: `ui/`: Frontend packages, demo app, and shared UI foundation (npm + Turbo-based) Choose the backend or frontend workflow that matches the area you are contributing to. ### Root configuration must be domain-aware The repo top level is a fixed set of domains (`ui/`, `backend/`, `docs/`, `examples/`) plus tooling and repo-meta files. Shared root config (today just `.gitignore`) is read across all of them, so every pattern must be written so it cannot silently match another domain's source: - **Anchor anything that targets one domain's build or runtime output.** Use `/name/` for the repo root or `domain/**/name/` for a subtree. A bare `status` / `store` / `tmp` / `Checklist*` matches a file or directory of that name *anywhere*, including UI source. That is exactly what caused a prior root ignore regression, where bare `status` / `store` swallowed `ui/packages/ui-core/src/store/`. - **Keep per-domain ignores in that domain's own `.gitignore`** (`ui/.gitignore`, the per-package backend `.gitignore`s), not the root. Things like `dist`, `.turbo`, and `build` belong to a domain. - **Lint/format/type config is domain-owned, not shared at the root.** `ui/` ships its own `eslint.config.mjs` / `tsconfig.base.json` / `.prettierrc`; backend uses `standard`. Do not add a root-level eslint/tsconfig/prettier that would apply across domains. - **A genuinely shared convention is fine if it applies identically to every domain** - e.g. committing `config/*.json.example` while ignoring the generated `config/*.json`. Note it as shared so the intent is clear. ## Get started ### Prerequisites Before contributing, ensure you have the following installed: - **Node.js** (version >=24) - **Git** (latest stable version) - **npm** (version 11 or higher) ### Licensing MDK is released under the [**Apache License 2.0**](https://github.com/tetherto/mdk/blob/main/LICENSE). By contributing, you agree that: - You retain copyright over your contributions - You grant a perpetual, worldwide, royalty-free license for their use - Contributions are provided **β€œAS IS”**, without warranty ## Development environment setup
1. Fork and clone 1. Fork [the repository](https://github.com/tetherto/mdk.git) on GitHub. 2. Clone your fork locally and navigate into the project directory: ```bash git clone https://github.com/username/mdk.git cd mdk ``` 3. Add the upstream remote: ```bash git remote add upstream https://github.com/tetherto/mdk.git ```
2. Stay in sync Keep your fork in sync with the main repository. For example: ```bash git fetch upstream git merge --ff-only upstream/main # fails loudly if main has diverged ```
### Backend contribution setup Use this workflow when contributing to backend code under `backend/core/`. ```bash cd backend/core npm install ``` #### Common commands ```bash # Lint backend code npm run lint # Run backend test suite (lint + unit + integration + package tests) npm test ``` ### Frontend contribution setup Use this workflow when contributing to frontend code under `ui/`. ```bash cd ui npm install ``` #### Common commands ```bash # Build packages npm run build # Run dev mode (all packages + demo) npm run dev # Lint and type-check npm run lint npm run typecheck # Run tests npm test ``` ## Pull request workflow ### Conventional types MDK uses Conventional Commits-style types for both branch names and PR titles. | Type | Use for | |---|---| | `feat` | New features | | `fix` | Bug fixes | | `docs` | Documentation changes | | `refactor` | Code refactoring without behaviour change | | `test` | Test additions or changes | | `chore` | Tooling, dependencies, repo maintenance | | `perf` | Performance improvements | | `style` | Formatting only (no logic change) | | `ci` | CI configuration changes | | `build` | Build system or external dependency changes | ### Branch naming convention Create branches using the following pattern: ```bash {type}/{short-description} ``` Where `{type}` is one of the [conventional types](#conventional-types). #### Branch naming examples ```bash # New feature git checkout -b feat/mdk-new-device # Bug fix git checkout -b fix/timeout-handling ``` ### Pull request steps 1. Sync your local main with upstream `main`. 2. Create a branch from local `main`. 3. Make your code changes. 4. Write or update tests. 5. Run linting and tests locally in the workspaces you changed: - `core`: `npm run lint && npm test` - `ui`: `npm run lint && npm test` (and `npm run typecheck` for TypeScript changes) 6. Commit changes with meaningful messages. 7. Push your branch and open a Pull Request targeting the upstream `main`. ### PR checklist Before submitting your PR, ensure that: - [ ] Code builds locally (`npm run build` for `ui` changes) - [ ] Tests pass in affected workspaces (`npm test`) - [ ] Linting passes (`npm run lint`) - [ ] Type-check passes for frontend TypeScript changes (`npm run typecheck`) - [ ] New features include tests - [ ] Public behavior or APIs changes have a [`docs-needed` issue](https://github.com/tetherto/mdk/issues/new?template=docs-needed.yml) linked to the PR ### PR title format Use the following convention: ```bash {type}({scope}): {description} ``` Where `{type}` is one of the [conventional types](#conventional-types) and `{scope}` is the affected area, for example `miner` or `ui`. Examples: - `feat(miner): add Antminer S21 support` - `fix(timeout): resolve action timeout handling` - `docs(api): update stats documentation` ## PR review All pull requests go through the following review steps: 1. **Automated checks**: Linting and tests must pass. 2. **Code review**: At least 2 maintainer approvals are required. 3. **Feedback resolution**: All requested changes must be addressed. 4. **Squash and merge**: Maintainers squash commits to keep history clean. ### Workflow diagram ```mermaid flowchart TB subgraph contributor [Contributor] start((Start)) --> createBranch[Create branch from main] createBranch --> test[Run tests] test --> testGw{Tests pass?} testGw -->|No| fix[Fix issues] fix --> test testGw -->|Yes| createPR[Create PR] createPR --> review address[Address feedback] --> pushFixes[Push fixes] pushFixes --> test end subgraph reviewer [Reviewer / Maintainer] review[Code review] reviewGw{Approved?} review --> reviewGw reviewGw -->|Request changes| address reviewGw -->|Rejected| cancel[Close PR] reviewGw -->|Approved| merge[Merge to main] merge --> tagGw{Tag release?} tagGw -->|No| endNoTag((End)) tagGw -->|Yes| tag[Tag version] tag --> deploy[Deploy] deploy --> deployGw{Deploy success?} deployGw -->|Yes| endSuccess((End)) deployGw -->|No| rollback[Rollback] rollback --> fix end ``` ## Code standards MDK uses **StandardJS** style to keep the codebase consistent and easy to review across repositories. Key rules: - 2-space indentation - No semicolons - Single quotes for strings - Space after keywords (`if`, `for`, `while`) - No unused variables ## Versioning and tagging ### Version tagging ```bash git checkout main git pull origin main git tag -a v1.2.0 -m "Release v1.2.0: Add RTD support" git push origin main git push origin v1.2.0 ``` ### Versioning scheme MDK follows **Semantic Versioning**: - **MAJOR** (`1.x.x`): breaking changes - **MINOR** (`x.1.x`): new backward-compatible features - **PATCH** (`x.x.1`): backward-compatible bug fixes Happy contributing, and thanks for helping improve MDK! πŸš€ # Governance (/v0-2-0/community/governance) This document describes how the MDK project is governed and how decisions are made. MDK is an open-source project. While the code is publicly available and community contributions are welcome, final decision-making authority rests with the MDK core team. --- ## Project Roles ### Users Anyone who uses MDK and provides feedback, bug reports, or feature requests. ### Contributors Community members who contribute code, documentation, tests, or other improvements via pull requests or issues. Contributors do not have merge access. --- ### Maintainers Maintainers are responsible for reviewing pull requests, maintaining code quality, and ensuring alignment with the project roadmap. Maintainers may be appointed by the Lead Maintainer. The following GitHub IDs currently hold maintainer status for MDK: - arif-dewi - eugeneglova - robdll - eskawl - boris91 - habrahamyanbf - efr-nox - rob-aslanian - mukama - paragmore - tekwani Only the GitHub accounts listed above have maintainer privileges within the MDK repositories. Maintainer status is granted based on demonstrated technical expertise, sustained contribution, and alignment with project goals. --- ### MDK core team The MDK Core Team consists of all active developers, the Project Manager, and the Community Manager. The Core Team is responsible for the overall health, sustainability, and long-term success of the project. While specific authorities are defined for the Lead Maintainer (technical) and the Community Manager (strategic), the Core Team operates as the primary collaborative decision-making body. ### Lead Maintainer **Hemant T** is the Lead Maintainer and Technical Lead for MDK. The Lead Maintainer has final authority over: - Pull request approval and merging - Architecture and design decisions - Release planning and versioning - Accepting or rejecting features - Appointing or removing maintainers If consensus cannot be reached among maintainers, the Lead Maintainer makes the final decision. --- ### Community Manager **Gio** is the Community Manager for MDK. The Community Manager is responsible for: - Managing community communication channels - Moderating discussions and enforcing the Code of Conduct - Supporting contributors during onboarding - Acting as a bridge between the community and the MDK core team - Defining, maintaining, and communicating the project vision and long-term direction - Leading roadmap prioritization and strategic planning --- ## Decision-Making Process - Community members may propose changes via issues or pull requests - Maintainers review contributions for quality, security, and alignment with MDK goals - The Lead Maintainer has final approval authority on all technical decisions - Strategic, roadmap, or breaking changes are determined by the MDK core team - The Community Manager has final decision-making authority over all strategic and directional matters, including project vision, roadmap prioritization, major feature introductions, partnerships, and significant pivots - In cases where strategic direction and technical considerations intersect, the Community Manager determines the final strategic outcome, while the Lead Maintainer determines the final technical implementation approach. --- ## Contribution Review Process - All contributions must follow `CONTRIBUTING.md` - Pull requests require review by a maintainer - Final approval and merge is performed by the Lead Maintainer - The MDK team reserves the right to decline contributions that do not align with the project direction --- ## Inactivity and Removal Maintainers who become inactive for an extended period or violate project policies may be removed by the Lead Maintainer. --- ## Code of Conduct All participants are expected to follow the project's Code of Conduct. Violations are handled by the Community Manager in coordination with the MDK core team. See [Code of Conduct](/v0-2-0/community/code-of-conduct) for details. --- ## Changes to Governance This governance model may evolve over time. Any changes will be proposed and approved by the MDK core team. # About MDK (/v0-2-0/concepts/about) ## Introducing MDK MDK, the Mining Development Kit, is an [open-source platform](/v0-2-0/community/contributing#licensing) that delivers a modern, transparent, and modular infrastructure for Bitcoin mining operations. MDK enables Bitcoin mining operations to start small, scale smoothly, and remain in full control, without lock-in, rewrites, or hidden complexity. ## The problem The Bitcoin mining industry has long been constrained by closed systems, proprietary tooling, and vendor lock-in. MDK changes that. ## The solution MDK delivers a modular mining stack that empowers operators and developers to build, monitor, control, and scale mining operations with full ownership: from a single device to gigawatt-scale facilities β€” without architectural rewrites. MDK ships three packages: 1. [Orchestration kernel (ORK)](#the-orchestration-kernel). 2. [Universal SDK](#the-universal-sdk). 3. [MDK App Toolkit](#mdk-app-toolkit). All three communicate through the **MDK Protocol**. Clients β€” browsers and [AI agents](#ai-ready-with-unified-intelligence) alike β€” reach the kernel exclusively through the App Node, the secure gateway your team builds with the SDK. Tying everything together is a **single contract per device type**: the same [`mdk-contract.json`](/v0-2-0/concepts/architecture#responsibilities-and-the-capability-contract) serves the UI (data labels), the orchestrator (validation rules), and AI agents (reasoning context). One file, three audiences, no drift. ### The orchestration kernel ORK, the Orchestration Kernel, is distributed as [`@tetherto/mdk-ork`](https://github.com/tetherto/mdk/blob/main/backend/core/ork/index.js). It's the central coordination engine of MDK and serves as a controller: it knows which devices are online, routes commands to the right place, monitors health, and collects performance data. `@tetherto/mdk-ork` communicates with devices through a standardized language called the **MDK Protocol**, a common set of messages that every device in the system understands, regardless of manufacturer or model. Adding a new device type never impacts `@tetherto/mdk-ork` thanks to the Worker, a device-specific translator that sits between the kernel and your hardware: it speaks the MDK Protocol upward, and the device's native API downward. The kernel is **pull-only**, **device-agnostic**, and **self-healing**. Learn more about the [internal modules, recovery flows, and protocol specs](https://github.com/tetherto/mdk/blob/main/backend/core/ork/lib/ork.manager.js) that back those guarantees. ### The universal SDK `@tetherto/mdk-client` is the universal SDK, a connection library that applications use to talk to `@tetherto/mdk-ork`. It serves as a universal adapter: handling all the connection details so developers can focus on building their application. - **Multi-language support**: available for Node.js, Python, Go, and more; use whatever language your team prefers - **Automatic connection handling**: manages reconnection, retries, and transport selection behind the scenes - **No lock-in**: developers bring their own stack and connect via the SDK. No framework requirements. ### MDK App Toolkit For teams that want to ship fast, the [**MDK App Toolkit**](/v0-2-0/concepts/architecture/app-toolkit) is the optional, batteries-included application layer that sits on top of `@tetherto/mdk-ork`. It ships in three parts: - **Frontend tools**: a headless state brain ([`@tetherto/mdk-ui-core`](/v0-2-0/reference/app-toolkit/ui-core)), framework adapters ([`@tetherto/mdk-react-adapter`](/v0-2-0/ui/react/get-started) for React today), and a production-tested React UI Kit ([`@tetherto/mdk-react-devkit`](/v0-2-0/ui/react/get-started)) for dashboards. - **Backend tools**: a plug-and-play library that drops into Fastify or Express to handle JWT auth, RBAC, and command proxying, with hooks for custom routes and aggregations. - **Plugins**: drop-in modules that pair a frontend tools widget with a backend tools route, so third parties can ship whole features without forking the App Node. The toolkit is fully optional: teams can write directly against `@tetherto/mdk-client` instead. See [MDK App Toolkit](/v0-2-0/concepts/architecture/app-toolkit) for the full breakdown. ## Who MDK is for MDK is built for everyone involved in mining Bitcoin: - **Mining operators**: monitor and control fleets with real-time dashboards. Get fleet-wide summaries (total hashrate, power usage, temperature alerts) across all your sites. - **Hardware manufacturers**: integrate new devices by building a Worker and writing one [`mdk-contract.json`](/v0-2-0/concepts/architecture#responsibilities-and-the-capability-contract). No involvement from MDK maintainers needed. - **Software developers**: build custom mining applications in any language, or leverage the [MDK App Toolkit](/v0-2-0/concepts/architecture/app-toolkit)'s frontend and backend tools for rapid development. - **AI/Automation teams**: [connect intelligent agents](#ai-ready-with-unified-intelligence) that can monitor, diagnose, and act on device issues autonomously ## Architecture overview `@tetherto/mdk-ork` is [the kernel](https://github.com/tetherto/mdk/blob/main/backend/core/ork/index.js). Above it, you build dashboards and business logic; AI agents drive the fleet through an **MCP endpoint** MDK provides on the App Node. **The App Node** is your secure gateway above the kernel: all [user authentication](https://github.com/tetherto/mdk/blob/main/backend/core/app-node/worker.js), [role-based access control (RBAC)](https://github.com/tetherto/mdk/blob/main/backend/core/app-node/worker.js), and fleet-wide aggregation happen there, keeping `@tetherto/mdk-ork` secure and focused on orchestration. Within the App Node you have two build paths: write custom business logic directly using `@tetherto/mdk-client`, or adopt the [MDK App Toolkit](/v0-2-0/concepts/architecture/app-toolkit), which ships **frontend tools** for the dashboard and **backend tools** for custom routes. Below the kernel, **devices are the source of truth**. The actual hardware state is reported by the Worker to `@tetherto/mdk-ork`, which orchestrates a synchronized view across the fleet. For the full layer-by-layer view with transports and discovery flows, see the [MDK stack](/v0-2-0/concepts/architecture#mdk-stack) on the Architecture page. ## AI-ready with unified intelligence MDK is designed from the ground up for [AI-driven operations](/v0-2-0/concepts/architecture#ai-agents-and-the-mcp-server). Rather than bolting AI on as an afterthought, intelligence is woven directly into the device definition itself. In addition to the technical schemas, every device's contract file ([`mdk-contract.json`](/v0-2-0/concepts/architecture#responsibilities-and-the-capability-contract)) contains: - **Safety rules**: for example, "Outlet temperature > 85Β°C requires immediate intervention" - **Operational constraints**: limits on command frequency, power thresholds, cooling requirements - **Troubleshooting guides**: if/then recovery steps that AI agents can follow autonomously This means an AI agent connecting to MDK doesn't need a separate knowledge base or custom prompts per device. The intelligence travels with the device; the same contract that validates commands and generates dashboards also determines how AI reasons about that hardware. ## What you can build - Operational dashboards (hashrate, power, temperature) - Multisite fleet management with centralized oversight - Alerts and notifications for critical device events - Overheating detection and automated remediation - AI-driven autonomous monitoring and control - Custom analytics and reporting pipelines - White-labeled hosted mining platforms - Third-party device integrations and plugins ## Scaling MDK [scales](/v0-2-0/concepts/architecture#scaling) naturally without architectural changes: - **More devices?** Add more Workers. Each Worker owns a specific set of devices, and `@tetherto/mdk-ork` routes commands to the right one automatically. - **More sites?** Each physical site runs its own `@tetherto/mdk-ork` instance. A single App Node connects to all of them, giving you one view across your entire operation. - **Site isolation**: `@tetherto/mdk-ork` instances are fully independent. A problem at one site has zero impact on any other. Learn more about: - [Architecture](/v0-2-0/concepts/architecture) - [MDK App Toolkit](/v0-2-0/concepts/architecture/app-toolkit) - [Connecting intelligent agents](/v0-2-0/concepts/architecture#ai-agents-and-the-mcp-server) ## Next steps - [Get started](/v0-2-0/get-started) Learn more about: - [Architecture](/v0-2-0/concepts/architecture) - [MDK App Toolkit](/v0-2-0/concepts/architecture/app-toolkit) - [Connecting intelligent agents](/v0-2-0/concepts/architecture#ai-agents-and-the-mcp-server) # Architecture (/v0-2-0/concepts/architecture) Status: 🚧 MDK is in active development. This page describes the target architecture and may evolve as real-world implementations land. ## How MDK works MDK is built around a small kernel with one job: route validated commands to whichever Worker owns a device, and pull telemetry back. Everything else (authentication, business logic, UI, AI agents) sits outside the kernel as composable layers: keeping the kernel small and the application surface open. To prevent unbound flexibility from manifesting as system rigidity, the architecture draws a hard line between what is standardized and what is delegated. It's: - **Opinionated where needed**: strict transport envelopes, unified JSON schema, unidirectional flows - **Flexible where it matters**: isolated Workers handle translation logic, enabling integrations without polluting the core infrastructure Five layers compose the stack, with strict, unidirectional flows between them. The kernel itself is **ORK**, the Orchestration Kernel, distributed as `@tetherto/mdk-ork`. ## MDK stack ```mermaid graph TB subgraph consumers ["Layer 1: Consumers"] UI["UI / Frontend"] AI["AI Agent"] end subgraph appNode ["Layer 2: App Node"] WebApp["HTTP / API Router"] MCPServer["MCP Server Endpoint"] end subgraph orkKernel ["Layer 3: @tetherto/mdk-ork"] ORK["ORK
β€’ Command routing
β€’ Health monitoring
β€’ Device registry
β€’ Telemetry collection"] end subgraph workers ["Layer 4: Workers"] Workers["WORKERS"] end subgraph devices ["Layer 5: Physical Devices"] Devices["Physical devices
β€’ Miners
β€’ Containers
β€’ Sensors"] end UI -->|"HTTP / WebSocket"| WebApp AI -->|"MCP Protocol"| MCPServer WebApp -->|"MDK Protocol via @tetherto/mdk-client / HRPC"| ORK MCPServer -->|"MDK Protocol via @tetherto/mdk-client / HRPC"| ORK Workers -.->|"join known DHT topic"| ORK ORK -->|"MDK Protocol: pull (identity / schema / telemetry) + command"| Workers Workers -->|"device libs"| Devices style consumers fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A style appNode fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A style orkKernel fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A style workers fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A style devices fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A ``` The MDK components that compose those layers: | Component | What it does | |---|---| | [`@tetherto/mdk-ork`](#the-ork-kernel) | Central coordination: routes commands, collects telemetry, monitors health | | [`@tetherto/mdk-client`](#the-sdk) | Universal SDK applications use to talk to `@tetherto/mdk-ork` | | [MDK Protocol](#the-mdk-protocol) | Standardized message envelope every layer speaks | | [MDK App Toolkit](/v0-2-0/concepts/architecture/app-toolkit) | Optional frontend tools, backend tools, and plugins on top of `@tetherto/mdk-ork` | ## Storage [Hypercore](https://github.com/holepunchto/hypercore)-backed stores (such as [Hyperbee](https://github.com/holepunchto/hyperbee)) are recommended across the `@tetherto/mdk-ork`, Worker, and App Node layers. This choice satisfies all storage requirements without the operational baggage of a centralized database. ## The MDK Protocol The MDK Protocol is the contract that crosses every layer of the stack. Version 0.0.1 is pull-based: Workers passively join a known DHT topic and `@tetherto/mdk-ork` initiates every RPC call. Workers issue no callbacks, emit no fan-out events, and make no exceptions to the direction of flow. For the full [envelope schema](https://github.com/tetherto/mdk/blob/main/backend/core/ork/lib/protocol/envelope.js), [action catalogue](https://github.com/tetherto/mdk/blob/main/backend/core/ork/lib/protocol/actions.js), and [base command set](https://github.com/tetherto/mdk/blob/main/backend/core/ork/lib/protocol/schemas.js), see the [Protocol reference](https://github.com/tetherto/mdk/blob/main/backend/core/ork/README.md). ### Design principles - **Transport-agnostic**: identical messages over [in-process calls](https://github.com/tetherto/mdk/blob/main/backend/core/ork/lib/transport/ipc-gateway.js), [Holepunch RPC (HRPC)](https://github.com/tetherto/mdk/blob/main/backend/core/ork/lib/transport/hrpc-gateway.js), or API calls - **Strictly unidirectional**: Workers never initiate RPC calls to `@tetherto/mdk-ork`. They join a known [Distributed Hash Table (DHT) topic](https://github.com/tetherto/mdk/blob/main/backend/core/ork/lib/discovery/dht-listener.js); `@tetherto/mdk-ork` discovers their presence passively and initiates all subsequent communication downwards (identity, capabilities, telemetry, commands). - **Generic interface**: the accepted interface is defined dynamically at the Worker level via a self-describing capabilities schema containing both structure and semantic context for AI agents ### Governance To maintain structural integrity and contract stability across `@tetherto/mdk-ork`, App Node, and Workers, MDK Protocol messages are governed and strictly validated using [Hyperschema](https://github.com/holepunchto/hyperschema). Hyperschema also aligns natively with the system's underlying Hyperbee storage. ### Discovery, telemetry, and command flows ```mermaid sequenceDiagram participant W as Worker participant DHT as DHT Topic (Hyperswarm) participant O as @tetherto/mdk-ork participant G as Gateway (App Node / MCP) Note over W,O: Worker discovery and registration W->>DHT: Joins known topic O-->>DHT: Detects new peer connection O->>W: identity.request W-->>O: identity.response (devices) O->>O: Save Worker to Registry O->>W: capability.request W-->>O: capability.response (schema) Note over O,W: Telemetry pull loop O->>W: telemetry.pull W-->>O: metrics and pending commands Note over G,W: Command execution G->>O: MDK Protocol HRPC envelope O->>W: command.request (routed by deviceId) W-->>O: command.result O-->>G: result ``` ## The ORK kernel [`@tetherto/mdk-ork`](https://github.com/tetherto/mdk/blob/main/backend/core/ork/index.js) is the trusted coordination layer at the heart of MDK. It [routes commands](https://github.com/tetherto/mdk/blob/main/backend/core/ork/lib/modules/command-dispatcher/index.js), [monitors device health](https://github.com/tetherto/mdk/blob/main/backend/core/ork/lib/modules/health-monitor/index.js), [registers Workers](https://github.com/tetherto/mdk/blob/main/backend/core/ork/lib/modules/worker-registry/index.js), and [pulls telemetry](https://github.com/tetherto/mdk/blob/main/backend/core/ork/lib/modules/telemetry-collector/index.js) β€” all on a [pull-only model](https://github.com/tetherto/mdk/blob/main/backend/core/ork/lib/modules/scheduler/index.js), so the kernel cannot be overwhelmed by upstream pressure. ## Workers Workers wrap a device library and expose it via the MDK Protocol. They are the integration handlers between physical hardware and `@tetherto/mdk-ork`, and the unyielding source of truth for that hardware: `@tetherto/mdk-ork` itself operates purely as a synchronised state machine over Worker-reported state. ### Discovery model 1. **DHT presence**: the Worker joins a known [Hyperswarm](https://github.com/holepunchto/hyperswarm) DHT topic. It does not send any RPC messages; it becomes a reachable peer. 2. **Peer detection**: `@tetherto/mdk-ork` continuously listens on the same DHT topic and detects the new peer connection automatically. 3. **Identity request**: `@tetherto/mdk-ork` initiates the first RPC call, requesting the Worker's identity and managed devices. 4. **Registration**: after receiving the identity, `@tetherto/mdk-ork` saves the Worker and its RPC keys into its registry. 5. **Capability declaration**: `@tetherto/mdk-ork` then explicitly queries the Worker to declare its full capabilities (`mdk-contract.json`). Communication initiation is **strictly unidirectional**: `@tetherto/mdk-ork` initiates every RPC call; Workers only ever respond. ### Responsibilities and the capability contract `mdk-contract.json` is the canonical source of truth for a Worker's programmatic capabilities **and** its AI context. MDK deliberately merges formal validation and semantic guidance into a single JSON contract: - `description` does double duty as the human UI label and AI edge-case rule (for example, *"Outlet temperature > 85C requires intervention"*) - `constraints` governs orchestration limits - `troubleshooting` provides if/then recovery behaviours alongside the payload it evaluates The exhaustive JSON Schema is `mdk-contract.schema.json`, with a [reference instance at `mdk-contract.json`](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/whatsminer/mdk-contract.json). ### Adding new hardware External integrators add new hardware by building a Worker package that conforms to the strict Device-Lib Contract: 1. Reference `mdk-contract.schema.json` to author the `mdk-contract.json`, validating strict data schemas while injecting explanations, constraints, and troubleshooting directly into the relevant nodes. 2. Subclass [`@tetherto/worker-base`](https://github.com/tetherto/mdk/blob/main/backend/workers/base/README.md) and implement the two translation hooks, `onTelemetryPull` and `onCommand`, in `src/hardware.js`. All HRPC plumbing is inherited from [the base class](https://github.com/tetherto/mdk/blob/main/backend/workers/base/lib/mdk-worker-adapter.js). 3. Boot the Worker instance, connect to devices, and join the known DHT topic. `@tetherto/mdk-ork` detects the peer and pulls its identity and capabilities. *(See [`mdk-whatsminer-worker.js`](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/whatsminer/lib/mdk-whatsminer-worker.js) for a reference subclass implementing both hooks against a real device.)* ## The SDK The [`@tetherto/mdk-client`](https://github.com/tetherto/mdk/blob/main/backend/core/client/index.js) SDK is the transport abstraction layer used to connect to `@tetherto/mdk-ork` reliably. It is the essential glue between the kernel and any consumer layer developers choose to build on top. **Responsibility**: connects the MDK Protocol over native transports (HRPC or IPC) seamlessly, offering: - **Transport abstraction**: handles MDK Protocol message construction and reconnection logic with exponential backoff. - **Automatic transport selection**: the SDK picks the transport mechanism based entirely on the URL scheme provided by the developer. - `hrpc://` connects over encrypted Hyperswarm streams for remote server-to-server production. - `ipc://` connects via direct local sockets for low-latency local testing. - **Major language support**: `@tetherto/mdk-client` will be built for all major languages (Node.js, Python, Go, and others), allowing developers to dispatch commands, subscribe to live streams, or pull status snapshots from any stack. ## App Node The [App Node](https://github.com/tetherto/mdk/blob/main/backend/core/app-node/worker.js) is the developer-owned API boundary, the mandatory gateway between the client-facing world and `@tetherto/mdk-ork`. The UI never connects to `@tetherto/mdk-ork` directly; an App Node must act as the gateway. Developers have two paths: - **Direct**: [write business logic, aggregation routes, and authentication](https://github.com/tetherto/mdk/blob/main/backend/core/app-node/workers/http.node.wrk.js) directly in the App Node using `@tetherto/mdk-client` in any language (Node.js, Go, Python, and others). - **Toolkit**: adopt the [MDK App Toolkit](/v0-2-0/concepts/architecture/app-toolkit), which ships backend tools middleware (drops into Fastify or Express, handles JWT auth, RBAC, and command proxying), frontend tools, and a plugins shell for plug-and-play extensions. Both approaches are fully supported; the choice depends on the team's preference for control versus convention. ### Responsibilities - **Fleet aggregation**: computes site hashrate, average temperature, cross-rack efficiency - **Auth and RBAC**: [guards all access](https://github.com/tetherto/mdk/blob/main/backend/core/app-node/workers/lib/auth.js) to `@tetherto/mdk-ork` with JWTs and session management - **API surface**: exposes REST or GraphQL to the UI ### Routing contract UI and AI agents should only provide `deviceId`; the App Node (via `@tetherto/mdk-client`) passes this down. `@tetherto/mdk-ork` resolves the owning Worker internally and dispatches the `command.request`. ### Authentication The App Node validates a JWT (Bearer Token) (signature, expiry, claims) before proxying any traffic into `@tetherto/mdk-ork`. **Allowlisting at the kernel**: `@tetherto/mdk-ork` does not perform user-level authentication. Instead, it maintains a strict allowlist of approved App Node and client connections. Once allowlisted securely (for example, via HRPC keys), `@tetherto/mdk-ork` implicitly trusts the origin of HRPC messages. ## AI agents and the MCP server AI agents connect to MDK through an **MCP endpoint** on the App Node, not directly to `@tetherto/mdk-ork`. They are treated as ordinary authenticated clients and subject to the same JWT validation, rate limits, and RBAC as a human user. This is intentional: the kernel does not perform user-level [authentication](#authentication), so routing agents through the App Node keeps them inside the same security envelope as every other consumer. What makes the integration distinctive is **[runtime tool derivation](https://github.com/tetherto/mdk/blob/main/docs/reference/maintainers/agent-ready-sdk.md)**. The tools exposed to an agent (for example, `get_device_telemetry` or `reboot_device`) are not hardcoded; they are parsed at runtime from each registered Worker's [`mdk-contract.json`](#responsibilities-and-the-capability-contract). When a new device type joins the network, the agent gains the ability to query and control it without any change to the App Node. ## End-to-end data flows Two scenarios show the full request path from consumer to device and back: a [human user clicking through the UI](#human-ui-scenario), and an [AI agent executing a multi-step prompt](#ai-agent-scenario). ### AI agent scenario A user instructs the AI Agent: *"Keep the fleet healthy."* The agent monitors continuously, catches `wm002` overheating, reboots it, and notifies the user. ```mermaid sequenceDiagram actor User participant AI as AI Agent participant Node as App Node (MCP) participant ORK as @tetherto/mdk-ork participant Worker as Generic Worker User->>AI: "Keep the fleet healthy." Note over AI,ORK: Step 1: Fleet discovery (read) AI->>Node: Call MCP tool get_fleet_alerts (token auth) Node->>Node: Validate agent token and RBAC Node->>ORK: HRPC query (via @tetherto/mdk-client) ORK-->>Node: Metrics Node-->>AI: Tool result (wm002 is overheating) Note over AI,ORK: Step 2: Execution (write) AI->>Node: Call MCP tool reboot_device (deviceId wm002) Node->>Node: Validate token and device:write RBAC Node->>ORK: dispatch generic protocol message ORK->>ORK: Resolve deviceId ORK->>Worker: command.request (HRPC) Worker-->>ORK: command.result ORK-->>Node: result OK Node-->>AI: Tool result (Success) AI-->>User: "wm002 was overheating and has been rebooted." ``` ### Human UI scenario A user clicks "Reboot" on device `wm001` in the UI. ```mermaid sequenceDiagram actor User participant UI as React UI participant Node as App Node participant ORK as @tetherto/mdk-ork participant Worker as Generic Worker User->>UI: Click "Reboot" on wm001 UI->>Node: POST { deviceId, action, payload } Note over Node,ORK: Delegation Node->>ORK: dispatch generic protocol message ORK->>ORK: Verify against capabilities ORK->>ORK: Resolve Worker for deviceId Note over ORK,Worker: Execution ORK->>Worker: command.request (HRPC) Worker-->>ORK: Ack start Worker->>Worker: Hardware-specific translation Worker-->>ORK: command.result ORK-->>Node: result OK Node-->>UI: HTTP 200 Note over Worker,ORK: State reflection ORK->>Worker: telemetry.pull (tick) Worker-->>ORK: Updated status (rebooting) ``` ## Scaling As MDK deployments scale to large mining sites (5,000+ devices), the system must explicitly manage parallel Workers and parallel `@tetherto/mdk-ork` instances. The kernel is only an execution layer; it does not perform application-level aggregation or cross-regional business logic. Scaling here means *how many* Workers and kernels you run. That is independent of [deployment topology](/v0-2-0/concepts/deployment-topologies) β€” *how those processes are packaged* on a host (one process vs many). ### Parallel Workers Multiple Workers of the same type (for example, `whatsminer-worker`) can be active concurrently and connected to the same `@tetherto/mdk-ork` kernel. ```mermaid flowchart TD subgraph kernel ["Single @tetherto/mdk-ork kernel"] ORK["ORK"] end W1["Worker 1"] W2["Worker 2"] D1["Devices wm001 to wm500"] D2["Devices wm501 to wm999"] ORK -->|Routes commands| W1 ORK -->|Routes commands| W2 W1 --- D1 W2 --- D2 style kernel fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A ``` **Device-level routing and ownership**: Workers never share devices. When a Worker connects, its `identity.register` payload explicitly lists the `deviceId`s it exclusively manages. The Worker Registry maintains this strict mapping and deterministically routes arriving commands to the designated Worker. ### Multi-site deployments A deployment may need to manage multiple massive physical boundaries (for example, a Texas Site and an Iceland Site). Each location runs its own dedicated site-level `@tetherto/mdk-ork` kernel, but all are overseen globally by a single App Node and AI Agent. ```mermaid flowchart TD Global["Global App Node / AI Agent"] subgraph texas ["Texas site"] ORK_TX["@tetherto/mdk-ork"] W1_TX["Whatsminer Worker"] W2_TX["Antminer Worker"] D1_TX["Whatsminers"] D2_TX["Antminers"] ORK_TX -->|Routes| W1_TX ORK_TX -->|Routes| W2_TX W1_TX --- D1_TX W2_TX --- D2_TX end subgraph iceland ["Iceland site"] ORK_IC["@tetherto/mdk-ork"] W1_IC["Whatsminer Worker"] W2_IC["Avalon Worker"] D1_IC["Whatsminers"] D2_IC["Avalons"] ORK_IC -->|Routes| W1_IC ORK_IC -->|Routes| W2_IC W1_IC --- D1_IC W2_IC --- D2_IC end Global <-->|MDK Protocol via HRPC| ORK_TX Global <-->|MDK Protocol via HRPC| ORK_IC style texas fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A style iceland fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A ``` The single App Node and AI Agent connect globally to all distributed `@tetherto/mdk-ork` kernels via the native HRPC mesh (Hyperswarm). Parallel `@tetherto/mdk-ork` instances remain entirely isolated from one another: they do not federate registries, share queues, or synchronize state. A crash at one site has zero impact on any other. Cross-site aggregation is handled purely at the App Node layer, where routes query multiple Workers via `@tetherto/mdk-ork` and merge the responses before returning them to the UI or Agent. ## Next steps Learn more about: - [About MDK](../../index) - [MDK App Toolkit](/v0-2-0/concepts/architecture/app-toolkit) - [ORK](/v0-2-0/concepts/architecture/ork) - [Get started](/v0-2-0/get-started) # MDK App Toolkit (/v0-2-0/concepts/architecture/app-toolkit) Status: 🚧 The MDK App Toolkit is under active development. The v0.2.0 release ships the [React UI Kit](#react-ui-kit). ## Introducing the MDK App Toolkit The MDK App Toolkit is an [open-source](/v0-2-0/community/contributing#licensing), reusable package for building applications on top of [`@tetherto/mdk-ork`](/v0-2-0/concepts/architecture/ork). The App Toolkit ships in three parts: 1. [Frontend tools](#frontend-tools). 2. [Backend tools](#backend-tools). 3. [Plugins](#plugins). While `@tetherto/mdk-ork` is entirely unopinionated, the App Toolkit provides a batteries-included application layer. It plugs into existing stacks (leveraging the low-level `@tetherto/mdk-client` for `@tetherto/mdk-ork` connectivity) so teams can ship operator-ready dashboards and custom domain logic without rebuilding the App Node from scratch. ## The problem Building an application on top of hardware infrastructure historically forces developers to face four friction points across the full stack: 1. **Frontend repeated logic**: every frontend developer integrating with backend APIs ends up reinventing solutions to the same plumbing challenges: - Throttling fast-moving telemetry streams - Managing optimistic UI state transitions - Detecting silent communication timeouts 2. **Backend repeated logic**: every backend developer ends up writing the same App Node plumbing (JWT auth, RBAC, command proxying, and fleet aggregation) before they can ship any custom business logic. 3. **The UI rigidity trap**: platforms try to solve the repeated logic problem by shipping a UI component library. UI is inherently subjective; when external developers are forced to use generic components, they get locked out of customizing the CSS to match their brand, leading to abandoned toolkits and identical-looking dashboards. 4. **The extension bottleneck**: when an external manufacturer builds a brand new miner or device, there is no clear path for injecting a custom dashboard widget and custom aggregator into an existing deployment. ## The solution The App Toolkit decouples logic from styling, separates frontend and backend concerns, and provides an explicit plug-and-play extension architecture: 1. It extracts complex API state and caching logic, such as handling server disconnects and buffering data from the App Node gateway, into a purely headless frontend layer (`@tetherto/mdk-react-devkit/core`). 2. It ships a reusable App Node library that drops into Fastify or Express, handling JWT auth, RBAC, and `@tetherto/mdk-ork` proxying out of the box, while exposing hooks for developers to plug in their own routes and aggregations. 3. It embraces the *shadcn/ui* pattern by providing reference UI components that developers copy and paste, giving them full control over CSS and layout while still leveraging the underlying data hooks. 🚧 Components will soon be available via npm. 4. It provides the MDK-App plugin architecture: an out-of-the-box, extensible shell framework where third-party frontend widgets and backend routes can be injected dynamically at runtime as a single drop-in package. ## The scope While MDK targets Bitcoin mining, the App Toolkit's architecture is deliberately domain-agnostic. Anywhere there is a fleet of devices reporting telemetry, with operators that need real-time visibility and control, the same patterns apply: IoT sensor networks, industrial telemetry, energy grid monitoring, autonomous vehicle fleets, and AI-agent control planes are all natural fits. The portable layers are everything above the protocol boundary: the headless state machine handles buffering, optimistic UI, and stale detection regardlessof what telemetry it receives; the framework adapters translate that state into reactive lifecycles for any React, Vue, or Svelte app; the reference UI primitives and the plug-and-play shell are generic UI building blocks; the App Node middleware library is generic Fastify/Express; and the plugin pattern, register a backend route alongside a widget that consumes it, works for any backend with the same shape. The MDK-specific glue lives below: `@tetherto/mdk-client` speaks the MDK Protocol to `@tetherto/mdk-ork`, the App Node middleware proxies commands over Holepunch RPC, and `@tetherto/mdk-react-devkit/foundation` adds mining-domain components such as vendor container UIs and the operations centre. Swap that protocol layer and the App Toolkit pattern carries to any domain with the same shape. ## Frontend tools Frontend tools are the building blocks for dashboards on top of `@tetherto/mdk-ork`. They decompose into three layers: **[UI Core](/v0-2-0/reference/app-toolkit/ui-core)** (headless state), **framework adapters** (e.g., React bindings), and **UI Kit** (styled React components), so business logic is shared while styling stays under developer control. ### UI Core The UI Core ([`@tetherto/mdk-ui-core`](/v0-2-0/reference/app-toolkit/ui-core)) is the framework-agnostic headless layer. Headless: the same store logic can back React, Vue, Svelte, or plain JS utilities. Framework adapters bind `@tetherto/mdk-ui-core` into reactive lifecycles for each UI runtime. Headless primitives [will land in this package](/v0-2-0/reference/app-toolkit/ui-core#whats-not-here-yet). ### Framework adapters `@tetherto/mdk-ui-core` stores do not trigger UI re-renders on their own. Framework adapters subscribe to those stores and expose reactive hooks. For React, [`@tetherto/mdk-react-adapter`](/v0-2-0/ui/react/get-started) provides ``, store hooks (`useAuth`, `useDevices`, `useActions`, and others), and TanStack Query re-exports. Future adapters (`@tetherto/mdk-vue`, `@tetherto/mdk-svelte`, `@tetherto/wc`) will follow the same pattern. ### UI Kit The toolkit optionally ships styled reference components (for example, ``). These follow the *shadcn/ui* pattern: they are copy-pasted into the developer's source tree. Developers own the styling completely. #### React UI Kit For React, [`@tetherto/mdk-react-devkit`](/v0-2-0/ui/react/get-started) is a production-tested UI Kit available as a ready-made implementation of this layer. Highlights: - 100+ production-tested components - Built on React 19 and *shadcn/ui* - Zero CSS-in-JS runtime overhead 🚧 UI Kits for other frameworks (Vue, Svelte, and so on) will follow the same pattern and ship alongside their framework adapters. ### Developer entry points The toolkit can be adopted at any of the following entry points, from most batteries-included to least. | Entry point | Package | What ships | What you write | When to choose | |---|---|---|---|---| | UI Kit | `@tetherto/mdk-react-devkit` (`/core` + `/foundation` entrypoints) | Pre-built React components, shell layout, ready-made ops dashboard | Data wiring, optional theming | You want a dashboard up fast | | Framework adapter | `@tetherto/mdk-react-adapter` (React today; Vue/Svelte/WC planned) | ``, store hooks, TanStack Query re-exports | Your own components and layout | You have a design system already | | UI Core | [`@tetherto/mdk-ui-core`](/v0-2-0/reference/app-toolkit/ui-core) | Zustand vanilla stores, `QueryClient` factory | Framework bindings or headless utilities | You need store access outside React or are building a new adapter | | Raw SDK | `@tetherto/mdk-client` | MDK Protocol client, connection management, reconnection | Everything above the wire: state, framework, UI | You are building a non-UI consumer (CLI, agent, backend service) | ## Backend tools Backend tools are a library for implementing custom business logic on top of `@tetherto/mdk-ork`, loosely coupled to the App Node and completely plug-and-play. Developers do not have to write the App Node gateway from scratch: the library drops directly into Fastify or Express, ships the boilerplate every operator needs, and exposes hooks for the custom routes and aggregations that make each deployment unique. ### Drop-in App Node middleware Pre-built middleware that wires up the standard App Node responsibilities so developers do not reinvent them: - Exposing standard `/auth` endpoints - Validating incoming user JWTs - Proxying commands securely down to `@tetherto/mdk-ork` over Holepunch RPC (HRPC) using `@tetherto/mdk-client` ### Route extension API Hooks allowing developers to bind new REST or WebSocket endpoints (for example, `POST /mining/stats`) that perform complex aggregations using `@tetherto/mdk-ork` capabilities via `@tetherto/mdk-client`. Custom routes live in a developer-owned package and snap into the middleware without forking it. ## Plugins A plugin pairs a frontend tools widget with a backend tools route as a single drop-in module. For developers looking for an out-of-the-box solution, the toolkit pairs the frontend and backend halves into the MDK-App plugin architecture: an extensible, multi-tenant shell. ### The prebuilt shell Instead of building a custom React layout and a custom Fastify server, developers spin up the MDK UI Shell and MDK Generic App Node. These are ready-to-use binaries fully wired together. ### Writing a plugin External developers write plugins consisting of two tightly coupled pieces of code that register dynamically into the shell at runtime: 1. **MDK-App server**: a package of business logic that registers custom backend routes (for example, `/mining/stats`) into the backend tools middleware hooks. 2. **MDK-App widget**: a custom frontend component that mounts into the MDK UI Shell's grid layout and natively queries `/mining/stats`. **Plug-and-play reusability**: this explicit convention ensures that an external company can build a completely new dashboard widget and backend aggregator for a new device type, publish it as a single npm package, and allow any user to drop it into their existing MDK deployment without modifying upstream source code. ## Architecture overview ```mermaid flowchart TD subgraph frontend ["frontend tools (browser)"] direction TB UI_CORE["@tetherto/mdk-ui-core
Zustand stores Β· QueryClient factory"] FRAMEWORKS["Framework adapters
(@tetherto/mdk-react-adapter, …)"] UI_COMPS["UI Kit
(@tetherto/mdk-react-devkit)"] UI_COMPS -->|consumes adapter hooks| FRAMEWORKS FRAMEWORKS -->|binds headless stores| UI_CORE end subgraph backend ["backend tools (App Node)"] direction TB ROUTER["App Node middleware
(Express / Fastify, JWT auth, route extension API)"] CLIENT["@tetherto/mdk-client
isolated native SDK"] ROUTER -->|translates REST to HRPC via| CLIENT end UI_CORE <-->|HTTP| ROUTER CLIENT -->|MDK Protocol| ORK["@tetherto/mdk-ork"] style frontend fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A style backend fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A ``` ## Next steps Learn more: - [About MDK](../../../index) - [Architecture: the orchestration kernel](..) - [Connecting intelligent agents](../index#ai-agents-and-the-mcp-server) - [See the license](/v0-2-0/community/contributing#licensing) # ORK (/v0-2-0/concepts/architecture/ork) `@tetherto/mdk-ork` is the trusted coordination layer of the stack. It splits internal responsibilities across single-purpose modules with their own state machines, so domains can evolve independently without coupling to each other. The design is inspired by Kubernetes: a pull-only model bounds the pace of execution so the kernel cannot be overwhelmed by upstream pressure. ## Module overview ```mermaid flowchart TD MCP["MCP Handler"] -->|Validates and routes| CD["Command Dispatcher"] CD -->|Enqueues| CSM["Command State Machine"] CSM <-->|HRPC req / res| W["Worker"] SCH["Scheduler"] -->|Triggers state pull| CSM CSM -->|state.pull| W WR["Worker Registry"] -->|Routing lookup| CD HM["Health Monitor"] -->|Updates status| WR SCH -->|Triggers interval| HM SCH -->|Triggers pull| TC["Telemetry Collector"] TC -->|Pulls metrics| W ``` ## Module catalogue `@tetherto/mdk-ork`'s coordination splits across single-purpose modules. Each owns its own state machine, persistence boundary, and scaling characteristics. Six modules ship in v0.0.1; two more are deferred to a later release. | Module | Role | |---|---| | [Command Dispatcher](/v0-2-0/reference/ork/modules#command-dispatcher) | Validates and resolves the destination Worker for incoming commands. | | [Command State Machine](/v0-2-0/reference/ork/modules#command-state-machine) | Tracks command lifecycle from `QUEUED` to `SUCCESS` or `FAILED`. | | [Worker Registry](/v0-2-0/reference/ork/modules#worker-registry) | Authoritative lookup of active Workers, their RPC keys, and managed devices. | | [Telemetry Collector](/v0-2-0/reference/ork/modules#telemetry-collector) | Stateless proxy between callers and Worker-local telemetry stores. | | [Scheduler](/v0-2-0/reference/ork/modules#scheduler) | System metronome; drives all interval-based pulls (telemetry, state, health). | | [Health Monitor](/v0-2-0/reference/ork/modules#health-monitor) | Liveness probes against Workers; reports status to the Registry. | | [Fault Supervisor](/v0-2-0/reference/ork/modules#fault-supervisor) (deferred) | Circuit-breaker patterns to contain cascading failures. | | [Concurrency Manager](/v0-2-0/reference/ork/modules#concurrency-manager) (deferred) | Per-device locking and queue-depth limits. | For the full state machines, transition rules, interface signatures, and recovery details, see the [ORK modules reference](/v0-2-0/reference/ork/modules). ## System recovery On a full system crash and restart, `@tetherto/mdk-ork` modules orchestrate recovery without user intervention: 1. **Worker Registry** loads last known Worker and device states from Hyperbee. 2. **Command State Machine** sweeps the WAL for stranded `EXECUTING` tasks and forces them to timeout or retry. 3. **Health Monitor** begins firing immediate pings to verify which Workers are still active. 4. **Connections**: the network layer awaits incoming HRPC reconnect storms from persistent Workers. Recovery is local and predictable. Worker crashes do not bring down the runtime; supervisors (PM2, Docker, Kubernetes) handle process restarts in multi-process deployments, and Workers rejoin the system after recovery. ## Next steps - [Architecture](../index) β€” how ORK fits the broader MDK stack - [ORK modules reference](/v0-2-0/reference/ork/modules) β€” per-module state machines, interfaces, and transition rules # Deployment topologies (/v0-2-0/concepts/deployment-topologies) MDK's runtime pieces β€” the [ORK kernel](/v0-2-0/concepts/architecture), the App Node, and one or more Workers β€” can run together in a single process or be split across many. This is a **packaging and operations** choice, and it is independent of how MDK [scales logically](/v0-2-0/concepts/architecture#scaling) (adding Workers, adding sites). This page explains the two supported deployment shapes and when to pick each. If ORK, worker, manager, or thing are unfamiliar, read [`terminology.md`](/v0-2-0/concepts/terminology) first. ## The two shapes ```mermaid flowchart TB subgraph single [Single process] direction TB sOrk["ORK"] sApp["App Node"] sW1["Worker A"] sW2["Worker B"] end subgraph multi [Multiple processes] direction TB mOrk["ORK process"] mApp["App Node process"] mW1["Worker A process"] mW2["Worker B process"] end ``` - **Single-process**: ORK, the App Node, and every Worker run inside one Node.js heap and event loop. Lowest footprint, simplest to start, nothing external to supervise. This is the shape behind the [single-process site how-to](/v0-2-0/how-to/deployment/run-single-process-site). - **Multiple processes (microservices)**: Each service runs as its own OS process or container, supervised by pm2 or Docker. This is the shape behind the [microservices site how-to](/v0-2-0/how-to/deployment/run-microservices-site). ## The trade-off Pick **single-process** when: - You are developing locally, running demos, or want a self-contained site for tests - Footprint matters more than isolation (minimal or embedded deployments) - You do not need supervisor-managed restarts Pick **multiple processes** when: - You want to allocate resources per service β€” CPU and memory limits per process or container - You need to restart or scale one Worker independently of the others - A Worker crash must not take down the App Node or its siblings (failure isolation) - You are orchestrating many Workers across one or more hosts ## Where `worker.js` fits The multi-process shape is built on [`backend/core/mdk/worker.js`](https://github.com/tetherto/mdk/blob/main/backend/core/mdk/worker.js), a shared process entry compatible with pm2, Docker, or a direct `node worker.js`. It is driven by environment variables (`SERVICE`, and for a Worker `WORKER`/`TYPE`/`RACK`) rather than CLI flags. One `worker.js` runs per service, and the supervisor (pm2 or Docker) owns its lifecycle and resource limits. The single-process shape instead calls the programmatic APIs (`getOrk`, `startWorker`, `startAppNode`) directly in one process. The [standalone `worker.js` install pattern](https://github.com/tetherto/mdk/blob/main/backend/workers/docs/install-pattern.md#standalone-via-workerjs) defines the per-Worker mechanics. ## Relationship to scaling Topology is orthogonal to scale. [Logical scaling](/v0-2-0/concepts/architecture#scaling) is about *how many* Workers and ORK kernels you run (parallel Workers, per-site kernels, multi-site oversight). Deployment topology is about *how those processes are packaged* on a given host. You choose both: for example, a production site typically runs multiple processes (this page) and multiple parallel Workers per kernel ([scaling](/v0-2-0/concepts/architecture#scaling)). ## Next steps Use the how-to guide for the topology you want to run: - Run a self-contained local site: [Single-process site](/v0-2-0/how-to/deployment/run-single-process-site) - Run supervised services: [Microservices site](/v0-2-0/how-to/deployment/run-microservices-site) - Register one miner before packaging a whole site: [Run a miner worker](/v0-2-0/how-to/miners) # Terminology (/v0-2-0/concepts/terminology) This page explains the terms you need to familiarize yourself with, using an Antminer rack as an example. ## Terms you need to name | Term | What it is | Lives at | | --- | --- | --- | | **ORK** (Orchestration Kernel) | The pull-only kernel that owns the device registry, routes commands, and aggregates telemetry. | [`backend/core/ork/index.js`](https://github.com/tetherto/mdk/blob/main/backend/core/ork/index.js) | | **App Node** | The developer-owned gateway between non-Node clients (UI, AI agents) and ORK. Mandatory whenever a non-Node consumer reaches the kernel; not used in the in-process Antminer-rack example below. | [`backend/core/app-node/`](https://github.com/tetherto/mdk/blob/main/backend/core/app-node/worker.js) | | **Worker** | A device-family translator. Speaks the MDK Protocol upward to ORK and the vendor's native API downward to one device family (one miner brand, one container type, one pool API). | [`backend/workers/docs/install-pattern.md`](https://github.com/tetherto/mdk/blob/main/backend/workers/docs/install-pattern.md) | | **Manager class** | The JavaScript class a worker exports, one per supported device model. Instances drive a single rack of devices. | e.g. `AM_S19XP`, `AM_S21` in [`backend/workers/miners/antminer/index.js`](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/antminer/index.js) | | **Thing** | One registered device instance. Created by calling `manager.registerThing({ info, opts })`. Identified by a generated `deviceId`. | runtime, in `manager.mem.things` | ## How they compose, for an Antminer rack ```mermaid flowchart TB subgraph clientLayer ["Your code"] Client["your script (e.g., client.js)"] end subgraph kernel ["Kernel"] Ork["ORK
device registry Β· command routing Β· telemetry pull"] end subgraph workerLayer ["Antminer worker"] AntminerWorker["e.g., AM_S21PRO"] end subgraph devices ["Antminer devices (real or mock)"] Miners["Antminers (HTTP / digest auth)"] end Client -->|"HRPC or IPC"| Ork Ork -->|"Hyperswarm DHT"| AntminerWorker AntminerWorker --> Miners ``` The same shape repeats for every other device family (Whatsminer, container vendors, pool APIs). For a multi-worker view, parallel workers, and multi-site deployments, see [`architecture.md#scaling`](/v0-2-0/concepts/architecture#scaling). ## What this section does NOT cover - Multi-process discovery across machines β€” [worker discovery](/v0-2-0/concepts/worker-discovery). - App Node implementation details (HTTP routing, JWT auth, RBAC) β€” see [`backend/core/app-node/worker.js`](https://github.com/tetherto/mdk/blob/main/backend/core/app-node/worker.js). - Building your own worker for a new device family β€” see [`backend/workers/docs/install-pattern.md`](https://github.com/tetherto/mdk/blob/main/backend/workers/docs/install-pattern.md). - Per-device contract details (telemetry units, command shapes, error codes) β€” those live in each worker's `mdk-contract.json`, e.g. [`backend/workers/miners/antminer/mdk-contract.json`](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/antminer/mdk-contract.json). ## Next steps - You are ready to run the example in [Run the stack](/v0-2-0/tutorials/backend-stack/run). # Worker discovery (/v0-2-0/concepts/worker-discovery) If ORK, worker, or thing are unfamiliar, read [terminology](/v0-2-0/concepts/terminology) first. ## Overview Workers and ORK find each other over a shared Hyperswarm DHT topic β€” a 32-byte rendezvous key both sides join before any RPC takes place. No coordination server is required. Once a worker joins the topic, ORK detects the peer and runs the [discovery and registration sequence](/v0-2-0/concepts/architecture#discovery-model). ## Operating modes A DHT round-trip is only needed when ORK and the worker run in separate processes: | Mode | How worker connects | When to use | | --- | --- | --- | | **Same-process** | `startWorker(W, { ork })` registers directly with the in-memory ORK instance β€” no DHT join | Development, get-started tutorials, [single-process sites](/v0-2-0/how-to/deployment/run-single-process-site) | | **Multi-process** | `startWorker(W)` joins the DHT topic; ORK detects it as a new peer | [Production microservices](/v0-2-0/how-to/deployment/run-microservices-site), workers on separate hosts | The [deployment topologies](/v0-2-0/concepts/deployment-topologies) page covers when to pick each packaging shape. ## Topic coordination In multi-process mode, ORK and the worker must join the same topic. Two options: **Auto-generated (default)** β€” `startWorker()` generates a random 32-byte hex topic, writes it to `/tmp/mdk/.dht-topic`, and joins the DHT; `getOrk()` reads the same file and joins the matching topic. **Explicit topic** β€” pass the same value to both calls directly: ```js const ork = await getOrk({ topic: '<32-byte-hex>' }) const { manager } = await startWorker(WorkerClass, { orkTopic: '<32-byte-hex>' }) ``` The worker must join the topic before ORK starts listening. Start the worker process first, then start ORK. `waitForDiscovery()` polls the registry until discovered workers reach `READY` state. The multi-process pattern is demonstrated end-to-end in [`dht-worker.js`](https://github.com/tetherto/mdk/blob/main/backend/core/examples/mdk-e2e/dht-worker.js) and [`dht-ork.js`](https://github.com/tetherto/mdk/blob/main/backend/core/examples/mdk-e2e/dht-ork.js). ## Next steps - Operators: Configure how often ORK polls discovered workers: [orchestrator cadences](https://github.com/tetherto/mdk/blob/main/backend/workers/docs/orchestrator.md#configuration-cadences) - Operators: Diagnose startup hangs when outbound network is restricted: [miner troubleshooting](/v0-2-0/how-to/miners/troubleshooting) - Integrators: see how workers register themselves on the DHT: [`MDKWorkerAdapter`](https://github.com/tetherto/mdk/blob/main/backend/workers/base/lib/mdk-worker-adapter.js) # Get started with MDK (/v0-2-0/get-started) ## New to MDK? Go from stack to dashboard Three short tutorials take you from a running MDK stack to a browser dashboard demo. First you watch the stack run, then you drive it from the CLI, then you launch the demo dashboard on top. Climb straight through, or jump to the rung you need. } className="@max-lg:col-span-1" title={1. Run the stack} href="/v0-2-0/tutorials/backend-stack/run" description={ Observe β€” one command brings up ORK with a mock device registered } /> } className="@max-lg:col-span-1" title={2. Control from the CLI} href="/v0-2-0/tutorials/backend-stack/cli" description={ Interact β€” drive a running stack from a REPL over IPC } /> } className="@max-lg:col-span-1" title={3. Run a dashboard demo} href="/v0-2-0/tutorials/full-stack/dashboard" description={ Demo β€” run a React dashboard with live charts on the stack } /> ## Build with MDK } title={Add MDK data to an existing app} href="#framework-quickstarts" description={ Use the frontend tools to bring MDK telemetry and controls into a React, Vue, or Svelte app } /> } title={Connect directly to ORK from any client} href="/v0-2-0/tutorials/backend-stack" description={ <> Start with the backend stack tutorials: run ORK with a mock device, then control it from a CLI Start the backend stack path β†’ } /> Build a full MDK app from scratch} href="/v0-2-0/resources/roadmap" description={ <> Coming soon: Adopt the App Toolkit shell. Frontend and backend wired together, extended via plugins Learn about the release schedule β†’ } /> Align the MDK backend with your business logic} href="/v0-2-0/resources/roadmap" description={ <> Coming soon: Use the backend tools to plug a library of custom routes and aggregations into the MDK App Node Learn about the release schedule β†’ } /> ## Framework quickstarts Building with Cursor or Claude? [Build with your AI agent](/v0-2-0/agents). } title={React} href="/v0-2-0/ui/react/get-started" description={ Pick a path: lean Quickstart, full Tutorial, or browse the component packages } /> Vue} href="/v0-2-0/resources/roadmap" description={ <> Reactive hooks for Vue Learn about the release schedule β†’ } /> Svelte} href="/v0-2-0/resources/roadmap" description={ <> Reactive hooks for Svelte Learn about the release schedule β†’ } /> Web Components} href="/v0-2-0/resources/roadmap" description={ <> Framework-agnostic Web Components Learn about the release schedule β†’ } /> ## Next steps - Learn more about the high-level [architecture](/v0-2-0/concepts/architecture): runtime stack and deployment modes - Compare [deployment topologies](/v0-2-0/concepts/deployment-topologies): single process or supervised services - Run a site from the [deployment how-tos](/v0-2-0/how-to/deployment) - [Contribute](/v0-2-0/community/contributing) # Run an MDK site (/v0-2-0/how-to/deployment) ## Overview Use these guides to choose a site deployment shape. If ORK, App Node, worker, manager, or thing are unfamiliar, read [terminology](/v0-2-0/concepts/terminology) first. If you are choosing between topologies, read [deployment topologies](/v0-2-0/concepts/deployment-topologies). ## Choose a guide | Goal | Guide | | --- | --- | | Run ORK, App Node, and workers in one Node.js process | [Run a single-process site](/v0-2-0/how-to/deployment/run-single-process-site) | | Run App Node and workers as supervised PM2 or Docker services | [Run a microservices site](/v0-2-0/how-to/deployment/run-microservices-site) | ## Next steps - Understand the trade-offs before you choose β€” [Deployment topologies](/v0-2-0/concepts/deployment-topologies) - Run the smallest site shape β€” [Run a single-process site](/v0-2-0/how-to/deployment/run-single-process-site) - Run supervised services β€” [Run a microservices site](/v0-2-0/how-to/deployment/run-microservices-site) # Run a microservices site (/v0-2-0/how-to/deployment/run-microservices-site) This thin page directs you to the correct location for the prerequisites, config fields, run command, smoke test, and troubleshooting. ## Overview Use the **microservices** site example when you want App Node and workers to run as separate OS processes or containers. This page is the task guide for the microservices topology. The [deployment topologies](/v0-2-0/concepts/deployment-topologies) concept explains when to choose microservices instead of single-process. ## Use this topology when - You need supervisor-managed restarts and logs - You want to restart or scale one service without restarting the others - You want a production-like layout for App Node and workers ## Run the example Follow the [microservices site example](https://github.com/tetherto/mdk/tree/main/examples/core/site): - Start with the [prerequisites](https://github.com/tetherto/mdk/tree/main/examples/core/site#prerequisites) - Use the [PM2 steps](https://github.com/tetherto/mdk/tree/main/examples/core/site#pm2) for local process supervision on one host - Use the [Docker steps](https://github.com/tetherto/mdk/tree/main/examples/core/site#docker) when you want containerized services or Compose-managed startup ## Next steps - Compare the supported shapes: [Deployment topologies](/v0-2-0/concepts/deployment-topologies) - Run the simpler local topology β€” [Run a single-process site](/v0-2-0/how-to/deployment/run-single-process-site) - Register a single miner before building a site config β€” [Run a miner worker](/v0-2-0/how-to/miners) # Run a single-process site (/v0-2-0/how-to/deployment/run-single-process-site) This thin page directs you to the correct location for the prerequisites, config fields, run command, smoke test, and troubleshooting. ## Overview Use the **single-process** site example when you want ORK, the App Node, and worker to share one Node.js process. This page is the task guide for the single-process topology. The [deployment topologies](/v0-2-0/concepts/deployment-topologies) concept explains when to choose single-process instead of microservices. ## Use this topology when - You are developing locally, running demos, or writing self-contained tests - You want a minimal-footprint deployment - You do not need per-service restart isolation ## Run the example Follow the [single-process site example](https://github.com/tetherto/mdk/tree/main/examples/core/site-single-process): - Start with its [prerequisites](https://github.com/tetherto/mdk/tree/main/examples/core/site-single-process#prerequisites) - Use the example [quickstart](https://github.com/tetherto/mdk/tree/main/examples/core/site-single-process#quickstart) ## Next steps - Compare the supported shapes: [Deployment topologies](/v0-2-0/concepts/deployment-topologies) - Run the supervised topology β€” [Run a microservices site](/v0-2-0/how-to/deployment/run-microservices-site) - Register a single miner before building a site config β€” [Run a miner worker](/v0-2-0/how-to/miners) # Run a miner worker (/v0-2-0/how-to/miners) ## Overview MDK drives each miner brand through its own worker. These guides are task-focused and **independent** β€” you only need the one for the hardware you operate. If ORK, worker, manager, or thing are unfamiliar, read [terminology](/v0-2-0/concepts/terminology) first. ## Pick your hardware The authoritative model list for every worker is the generated [supported-hardware catalogue](/v0-2-0/reference/supported-hardware#miners). For example, you may: - [Run an Antminer worker](/v0-2-0/how-to/miners/run-antminer-worker) - [Run a Whatsminer worker](/v0-2-0/how-to/miners/run-whatsminer-worker) - [Run an Avalon worker](/v0-2-0/how-to/miners/run-avalon-worker) ## Prerequisites Every guide assumes: - Node.js >=24 (LTS) - npm >=11 - Commands are run from the repo root - Outbound network access for ORK discovery For the mock/development path: - No physical miner is required - The runnable example for your model starts the bundled mock device and registers it Each runnable example starts an ORK whose control plane is peer-to-peer over a Hyperswarm DHT. Without outbound network access, startup will stall while the ORK tries to reach DHT bootstrap nodes. See [how workers connect](/v0-2-0/concepts/worker-discovery) for the ORK/DHT mechanics. For the deployment path: - A Node.js service or script in your deployment that runs the MDK worker and registers devices - A supported miner reachable from the machine or container running the worker - Access to the miner's native API and credentials, if that API requires them - The worker's `USAGE.md` for the exact `registerThing` options ## Next steps - Browse [supported hardware](/v0-2-0/reference/supported-hardware) - New to the moving parts? Read [terminology](/v0-2-0/concepts/terminology) (ORK, worker, manager, thing, mock) - If an example does not start or a mock port is busy, use [troubleshooting](/v0-2-0/how-to/miners/troubleshooting) - Drive the registered device from the CLI or dashboard β€” [Get started](/v0-2-0/tutorials/backend-stack) # Run an Antminer worker (/v0-2-0/how-to/miners/run-antminer-worker) ## Overview This page details how to run the Bitmain Antminer worker. Select the development (mock) or real-device path. ## Prerequisites Review the [common deployment prerequisites](/v0-2-0/how-to/miners#prerequisites) before you start. Deployment-specific requirements: - A Node.js service or script in your deployment that runs the MDK worker and registers devices - A supported Antminer device reachable from the machine or container running the worker - The miner API reachable over HTTP, typically port `80` - Digest-auth credentials for the miner. Antminer devices commonly default to username `root` and password `root`, but use your site's configured credentials ## Development
Run against a mock To support development, each model ships a runnable example that starts an ORK, boots a mock device, and registers it. This guide uses the S21 example: ```bash node backend/workers/miners/antminer/examples/run-s21.js ``` It prints the ORK HRPC key and the registered device ID, then stays running until Ctrl+C. To run another model, use the matching example listed in [USAGE.md](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/antminer/USAGE.md#runnable-examples).
## Connect a miner ### Pick your model Use the Antminer worker's [USAGE.md](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/antminer/USAGE.md) to choose the manager class and mock example for your model. This guide uses `AM_S21` and `run-s21.js` as the example; replace them with the values for your miner. ### Register your miner Antminer devices use an HTTP API with digest authentication. Add this code to the Node.js service or script that runs the MDK worker in your deployment. The snippet shows the minimum `registerThing` call for one Antminer device; replace the example IP address and credentials with your miner's values: ```js const { getOrk, startWorker } = require('@tetherto/mdk') const { AM_S21 } = require('@tetherto/miner-antminer') const ork = await getOrk() const { manager } = await startWorker(AM_S21, { ork }) await manager.registerThing({ info: { container: 'site-1', serialNum: 'AM-001' }, opts: { address: '192.168.1.20', port: 80, username: 'root', password: 'root' } }) ``` Make sure each miner's IP is reachable from the machine or container running the worker before registering. Commands act on physical hardware β€” prioritize thermal safety. Before running in a deployment, generate the worker config (`common.json` for worker identity, `base.thing.json` for device defaults and per-model alert thresholds): ```bash cd backend/workers/miners/antminer ./setup-config.sh ``` For the full `registerThing` option reference, the mock `createServer` options, and the per-model alert blocks, see the worker's [USAGE.md](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/antminer/USAGE.md) and the shared [install pattern](https://github.com/tetherto/mdk/blob/main/backend/workers/docs/install-pattern.md). ## Troubleshooting The development example on this page uses `run-s21.js`. A working run prints `ORK HRPC key:` and `Device:`, then stays running until Ctrl+C. If the example does not print both values, or if its mock port is already in use, follow [miner troubleshooting](/v0-2-0/how-to/miners/troubleshooting). ## Next steps - Decide how to run the worker service β€” [Deployment topologies](/v0-2-0/concepts/deployment-topologies) - Review telemetry units, command shapes, and error codes β€” [`mdk-contract.json`](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/antminer/mdk-contract.json) # Run an Avalon worker (/v0-2-0/how-to/miners/run-avalon-worker) ## Overview This page details how to run the Canaan Avalon worker. Select the development (mock) or real-device path. ## Prerequisites Review [common deployment prerequisites](/v0-2-0/how-to/miners#prerequisites) before you start. Deployment-specific requirements: - A Node.js service or script in your deployment that runs the MDK worker and registers devices - A supported Avalon device reachable from the machine or container running the worker - The miner API reachable over the native CGMiner TCP API, typically port `4028` - No API username or password. The Avalon CGMiner API is unauthenticated ## Development
Run against a mock To support development, the A1346 ships a runnable example that starts an ORK, boots a mock device, and registers it: ```bash node backend/workers/miners/avalon/examples/run-a1346.js ``` It prints the ORK HRPC key and the registered device ID, then stays running until Ctrl+C. For the manager class and example file, see [USAGE.md](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/avalon/USAGE.md#runnable-example).
## Connect a miner ### Pick your model Use the Avalon worker's [USAGE.md](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/avalon/USAGE.md) to confirm the manager class and mock example for your model. This guide uses `AV_A1346` and `run-a1346.js` as the example. ### Register your miner Avalon devices use the native CGMiner TCP API on port 4028, which is unauthenticated (no username or password). Add this code to the Node.js service or script that runs the MDK worker in your deployment. The snippet shows the minimum `registerThing` call for one Avalon device; replace the example IP address with your miner's value: ```js const { getOrk, startWorker } = require('@tetherto/mdk') const { AV_A1346 } = require('@tetherto/miner-avalon') const ork = await getOrk() const { manager } = await startWorker(AV_A1346, { ork }) await manager.registerThing({ info: { container: 'site-1', serialNum: 'AV-001' }, opts: { address: '192.168.1.30', port: 4028 } }) ``` Make sure each miner's IP is reachable from the machine or container running the worker before registering. Commands act on physical hardware β€” prioritize thermal safety. Before running in a deployment, generate the worker config (`common.json` for worker identity, `base.thing.json` for device defaults and per-model alert thresholds): ```bash cd backend/workers/miners/avalon ./setup-config.sh ``` For the full `registerThing` option reference, the mock `createServer` options, and the per-model alert blocks, see the worker's [USAGE.md](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/avalon/USAGE.md) and the shared [install pattern](https://github.com/tetherto/mdk/blob/main/backend/workers/docs/install-pattern.md). ## Troubleshooting The development example on this page uses `run-a1346.js`. A working run prints `ORK HRPC key:` and `Device:`, then stays running until Ctrl+C. If the example does not print both values, or if its mock port is already in use, follow [miner troubleshooting](/v0-2-0/how-to/miners/troubleshooting). ## Next steps - Decide how to run the worker service β€” [Deployment topologies](/v0-2-0/concepts/deployment-topologies) - Review telemetry units, command shapes, and error codes β€” [`mdk-contract.json`](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/avalon/mdk-contract.json) # Run a Whatsminer worker (/v0-2-0/how-to/miners/run-whatsminer-worker) ## Overview This page details how to run the MicroBT Whatsminer worker. Select the development (mock) or real-device path. ## Prerequisites Review the [common deployment prerequisites](/v0-2-0/how-to/miners#prerequisites) before you start. Deployment-specific requirements: - A Node.js service or script in your deployment that runs the MDK worker and registers devices - A supported Whatsminer device reachable from the machine or container running the worker - The miner API reachable over encrypted TCP, typically port `14028` - The Whatsminer API password. The worker negotiates a session token from it; there is no separate username ## Development
Run against a mock To support development, each model ships a runnable example that starts an ORK, boots a mock device, and registers it. This guide uses the M56S example: ```bash node backend/workers/miners/whatsminer/examples/run-m56s.js ``` It prints the ORK HRPC key and the registered device ID, then stays running until Ctrl+C. To run another model, use the matching example listed in [USAGE.md](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/whatsminer/USAGE.md#runnable-examples).
## Connect a miner ### Pick your model Use the Whatsminer worker's [USAGE.md](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/whatsminer/USAGE.md) to choose the manager class and mock example for your model. This guide uses `WM_M56S` and `run-m56s.js` as the example; replace them with the values for your miner. ### Register your miner Whatsminer devices use an encrypted TCP API on port 14028 with token-based authentication; the worker negotiates a session token from the device password (there is no separate username). Add this code to the Node.js service or script that runs the MDK worker in your deployment. The snippet shows the minimum `registerThing` call for one Whatsminer device; replace the example IP address and password with your miner's values: ```js const { getOrk, startWorker } = require('@tetherto/mdk') const { WM_M56S } = require('@tetherto/miner-whatsminer') const ork = await getOrk() const { manager } = await startWorker(WM_M56S, { ork }) await manager.registerThing({ info: { container: 'site-1', serialNum: 'WM-001' }, opts: { address: '192.168.1.10', port: 14028, password: 'admin' } }) ``` Make sure each miner's IP is reachable from the machine or container running the worker before registering. Commands act on physical hardware β€” prioritize thermal safety. Before running in a deployment, generate the worker config (`common.json` for worker identity, `base.thing.json` for device defaults and per-model alert thresholds): ```bash cd backend/workers/miners/whatsminer ./setup-config.sh ``` For the full `registerThing` option reference, the mock `createServer` options, and the per-model alert blocks, see the worker's [USAGE.md](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/whatsminer/USAGE.md) and the shared [install pattern](https://github.com/tetherto/mdk/blob/main/backend/workers/docs/install-pattern.md). ## Troubleshooting The development example on this page uses `run-m56s.js`. A working run prints `ORK HRPC key:` and `Device:`, then stays running until Ctrl+C. If the example does not print both values, or if its mock port is already in use, follow [miner troubleshooting](/v0-2-0/how-to/miners/troubleshooting). ## Next steps - Decide how to run the worker service β€” [Deployment topologies](/v0-2-0/concepts/deployment-topologies) - Review telemetry units, command shapes, and error codes β€” [`mdk-contract.json`](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/whatsminer/mdk-contract.json) # Troubleshoot miner workers (/v0-2-0/how-to/miners/troubleshooting) ## Overview This page covers the mock/development examples used by the Antminer, Whatsminer, and Avalon miner guides. The examples start a bundled mock miner, start an ORK, register one device, print the identifiers you need, and keep running until you stop them. ## Expected output A working example prints an ORK key and a registered device ID: ```text ORK HRPC key: Device: Ctrl+C to stop. ``` If you do not see both `ORK HRPC key:` and `Device:`, use the following checks. ## Find the right port Mock examples and real miners use different sources for ports. ### Mock examples Each runnable example starts a mock miner on the port declared in that example file. To find the mock port for your model: 1. Open the worker's `USAGE.md` and choose the runnable example for your model: - Antminer: [USAGE.md](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/antminer/USAGE.md#runnable-examples) - Whatsminer: [USAGE.md](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/whatsminer/USAGE.md#runnable-examples) - Avalon: [USAGE.md](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/avalon/USAGE.md#runnable-example) 2. Open the matching `examples/run-*.js` file. 3. Look for the `createServer({ port: ... })` call. The cross-worker manifest also records the expected mock type and default port for each variant: [workers manifest](https://github.com/tetherto/mdk/blob/main/backend/workers/docs/workers-manifest.yaml). ### Real miners Real devices use their native APIs: - Antminer: HTTP, usually port `80`, with digest-auth credentials. - Whatsminer: encrypted TCP, usually port `14028`, with the API password. - Avalon: CGMiner TCP API, usually port `4028`, with no username or password. Before registering a real miner, confirm the miner is reachable from the machine or container running the worker. ## Clean up a mock port If an example exits with `EADDRINUSE` or says a port is already in use, find the process using that port: ```bash lsof -nP -iTCP: -sTCP:LISTEN ``` Replace `` with the mock port for your example. The output includes a process ID (`PID`). If the process is an old miner mock or example that you no longer need, stop it: ```bash kill ``` Run `lsof` again to confirm the port is free before restarting the example. ## Example does not print an ORK key Each example starts an ORK. The ORK control plane uses Hyperswarm DHT discovery, so the machine running the example needs outbound network access. If outbound access or network-interface inspection is blocked, startup may hang or fail before printing `ORK HRPC key:`. Check: - The machine has outbound network access. - Local security tooling, containers, or sandboxes are not blocking UDP/network-interface access. - You are running the command from the repository root. - Dependencies have been installed for `backend/core` and `backend/workers`. ## File lock or socket errors The examples call `getOrk()` with default local paths. If another ORK, app-node, or example is already running with the same defaults, you may see file lock or local socket errors. Stop stale example processes before starting another example. If you need to run several examples side by side for development, run each process with a different temporary directory so each ORK gets separate local state: ```bash TMPDIR=/tmp/mdk-antminer-s21 node backend/workers/miners/antminer/examples/run-s21.js ``` Keep the temporary path short on macOS and Linux because ORK also creates a Unix socket under that directory. ## Still blocked When asking for help on [Discord](https://discord.com/invite/tetherdev) or [GitHub issues](https://github.com/tetherto/mdk/issues) collect: - The exact example command - The model and mock port - The full stdout and stderr - `node --version` and `npm --version` - Any process currently listening on the mock port # Use UI Core headlessly (/v0-2-0/how-to/ui/core/use-ui-core-headlessly) @tetherto/mdk-ui-core [`@tetherto/mdk-ui-core`](/v0-2-0/reference/app-toolkit/ui-core) is the framework-agnostic headless layer of the MDK App Toolkit. This how-to walks through installing it on its own and driving its Zustand stores from a non-React runtime — a Node script, a Vue or Svelte adapter you're authoring, a CLI tool, or a test helper. ## When to reach for this Use headless UI Core when: - You're authoring a framework adapter (Vue, Svelte, Web Components) and need raw access to the Zustand stores. - You're building a Node CLI or backend service that has to read MDK telemetry and act on it. - You're writing test helpers or fixtures that need to seed and inspect store state without a React renderer. - You need to subscribe to store changes from non-UI code — logging, websocket bridges, metrics. For a React app, the [React adapter](/v0-2-0/ui/react/get-started) wraps UI Core with `` and adapter hooks. Use that path instead so most React code never touches `@tetherto/mdk-ui-core` directly. ## Install `@tetherto/mdk-ui-core` has no peer dependencies on React or any UI framework. ```bash npm install @tetherto/mdk-ui-core ``` ## Subpath imports Pull only the pieces you need from the relevant subpath. Subpath imports give tree-shakers a smaller surface than the top-level barrel: ```ts ``` The [subpath exports table](/v0-2-0/reference/app-toolkit/ui-core#subpath-exports) on the reference lists every supported entry. ## Create a QueryClient `createMdkQueryClient` returns a TanStack Query Core client wired to your App Node. Pass an explicit `baseUrl`, or let the factory resolve one from environment variables: ```ts const queryClient = createMdkQueryClient({ baseUrl: 'https://app-node.example.com', }) ``` Without an explicit `baseUrl`, the factory checks `VITE_MDK_API_URL` then `MDK_API_URL` before falling back to `http://localhost:3000`. The [resolution order](/v0-2-0/reference/app-toolkit/ui-core#queryclient-factory) on the reference covers every case. ## Read store state Each store is a Zustand vanilla singleton. `getState()` returns the current snapshot: ```ts const { token, permissions } = authStore.getState() console.log('current token', token) ``` ## Write store state `setState()` accepts either a partial object or a function that receives the previous state: ```ts devicesStore.setState({ selectedDeviceId: 'wm-002' }) devicesStore.setState((prev) => ({ devices: [...prev.devices, newDevice], })) ``` ## Subscribe to changes `subscribe()` runs a callback on every state change and returns an unsubscribe function: ```ts const unsubscribe = notificationStore.subscribe((state) => { console.log('unread notifications:', state.count) }) unsubscribe() ``` ## A complete Node example A small Node script that authenticates against the App Node, fetches the device list once, and then tails unread notification count changes: ```ts authStore, devicesStore, notificationStore, } from '@tetherto/mdk-ui-core/store' async function main() { const queryClient = createMdkQueryClient({ baseUrl: process.env.MDK_API_URL ?? 'http://localhost:3000', }) authStore.setState({ token: process.env.MDK_TOKEN ?? '' }) const devices = await queryClient.fetchQuery({ queryKey: ['devices', 'list'], queryFn: async () => { const res = await fetch(`${process.env.MDK_API_URL}/api/devices`, { headers: { Authorization: `Bearer ${authStore.getState().token}` }, }) return res.json() }, }) devicesStore.setState({ devices }) console.log(`Found ${devices.length} devices`) const unsubscribe = notificationStore.subscribe((state) => { console.log(`unread notifications: ${state.count}`) }) process.on('SIGINT', () => { unsubscribe() process.exit(0) }) } main().catch((err) => { console.error(err) process.exit(1) }) ``` Run it with: ```bash MDK_TOKEN=ey... MDK_API_URL=https://app-node.example.com node script.ts ``` For the prebuilt query and mutation factories (`authQuery`, `devicesQuery`, `deviceQuery`, `telemetryQuery`), check the [QueryClient factory section](/v0-2-0/reference/app-toolkit/ui-core#queryclient-factory) on the reference. ## Next steps - [UI Core reference](/v0-2-0/reference/app-toolkit/ui-core): full store list, query helpers, and the `createMdkQueryClient` resolution order. - [MDK App Toolkit architecture](/v0-2-0/concepts/architecture/app-toolkit): where UI Core fits in the frontend stack. - [React adapter](/v0-2-0/ui/react/get-started): if you decide to layer React on top. # Build any dashboard with an agent (/v0-2-0/how-to/ui/react/build-any-dashboard-with-an-agent) This page walks a **realistic agent session** end to end: the same two-step flow as [Build dashboards with your AI agent](/v0-2-0/agents). ## Overview MDK's React UI Kit's library of presentational and composable building blocks can be used wherever suits you. Mining is one of many disciplines that benefits from charts, tables, tabs, and stat cards. Your telemetry; your dash. You bring *your* labels, *your* mock data, and *your* layout. The first thing we built was [Mining Dashboards](/v0-2-0/ui/react/get-started). What will you build? IoT fleet backend reporting, workout metrics motivation app, weather stats? Your data; your choice. The prompt asked for a **statistics tutorial page** so students can explore distributions, trends, and raw grades. You see every: - [UI CLI](/v0-2-0/reference/app-toolkit/ui-cli) command the agent would run - Resulting page code - Run instructions We use mock JSON in the browser. No ORK worker, pool API, or fleet backend is required. Presentational imports from `@tetherto/mdk-react-devkit/core` are enough for a highly visual, shippable UI. ## What this concept page proves 1. **Domain is yours.** Component contracts describe shape and behavior (chart datasets, table columns), not industry vocabulary. 2. **The agent path is unchanged.** `init` β†’ plain-language intent β†’ local manifests β†’ scaffold β†’ `check`. 3. **Visual density is supported.** Bar charts, line trends, sortable tables, and stat cards compose into a dashboard that *looks* like a product, not a wireframe. The demo app is **Stats Lab**: a fictional intro statistics course where Teaching Assistants review quiz score histograms, weekly class averages, and per-student grades. ## Prerequisites - **Node.js** >=24 - **npm** >=11 - **React** 19+ and **react-dom** 19+ {/* npm packages are not yet published to the registry; clone the MDK UI monorepo and use workspace dependencies until publish. */} - A project folder inside the MDK UI monorepo (this walkthrough uses `apps/stats-lab`, same pattern as [Wire a React app](/v0-2-0/tutorials/ui/react/tutorial#create-your-app)) - [Build dashboards with your AI agent](/v0-2-0/agents) wired once (`mdk-ui init`) or willingness to run `init` in step 1 ## Agent session walkthrough ### Wire the IDE (same as agents) From your app or monorepo project root: ```bash npx @tetherto/mdk-ui-cli init --ide cursor ``` This writes `.mdk/context.md` and `.cursor/rules/mdk.mdc` so the session already knows the UI CLI surface. See [init](/v0-2-0/reference/app-toolkit/ui-cli#init). ### State a non-mining intent Paste a prompt that names the domain explicitly so the agent does not reach for hashrate widgets: > Build a **statistics tutorial dashboard** for students in `apps/stats-lab`. Include: > > - A **histogram** of final exam scores (bar chart buckets 50–59 through 90–100) > - A **line chart** of weekly class average over six weeks > - A **sortable table** of students with midterm, final, and section > - Three **summary stat cards**: mean final, median final, enrollment count > > Use mock data in the repo. Import only from `@tetherto/mdk-react-devkit/core` and `@tetherto/mdk-react-devkit/foundation` where > needed. No mining APIs. The agent's job is unchanged from [Build dashboards with your AI agent](/v0-2-0/agents#prompt-your-agent): discover exports, scaffold, and verify compile. ### Discovery commands the agent runs Behind the prompt, a well-behaved session issues deterministic CLI lookups (no model calls). A representative transcript: ```bash npx @tetherto/mdk-ui-cli suggest "statistics dashboard histogram line chart data table stat cards" ``` Typically returns chart-category components near the top β€” `BarChart`, `LineChart`, and closely related chart primitives. General-purpose components like `DataTable`, `SingleStatCard`, and `Tabs` score lower on chart-focused queries and are best found with the `find --category` commands in the next accordion. Because we asked for mock data only, the agent skips adapter hooks like `useDevices`. ```bash npx @tetherto/mdk-ui-cli find --category charts --format table npx @tetherto/mdk-ui-cli find --category tables --format table ``` Narrows to chart primitives and table components with stable exports. ```bash npx @tetherto/mdk-ui-cli docs BarChart npx @tetherto/mdk-ui-cli docs LineChart npx @tetherto/mdk-ui-cli example LineChart npx @tetherto/mdk-ui-cli docs DataTable ``` The agent copies real prop shapes (`LineChartData` millisecond `x` values, `DataTableColumnDef` accessors) instead of inventing APIs. ```bash npx @tetherto/mdk-ui-cli add page StatsLab \ --component BarChart \ --component LineChart \ --component DataTable \ --component SingleStatCard npx @tetherto/mdk-ui-cli check apps/stats-lab/src/App.tsx ``` `add page` emits a starter layout; the agent fills in mock datasets and labels. `check` validates imports and component prop contracts against the real package barrels; the app build in the local run verifies the final Vite project. Full command reference: [UI CLI](/v0-2-0/reference/app-toolkit/ui-cli). ### Review the scaffolded page After the agent edits `App.tsx`, a Stats Lab dashboard might look like this: ```tsx BarChart, LineChart, DataTable, Tabs, TabsList, TabsTrigger, TabsContent, } from '@tetherto/mdk-react-devkit/core' type Student = { id: string name: string section: 'A' | 'B' midterm: number final: number } const students: Student[] = [ { id: '1', name: 'Alex Kim', section: 'A', midterm: 82, final: 88 }, { id: '2', name: 'Jordan Lee', section: 'B', midterm: 74, final: 79 }, { id: '3', name: 'Sam Rivera', section: 'A', midterm: 91, final: 94 }, { id: '4', name: 'Taylor Ng', section: 'B', midterm: 68, final: 72 }, { id: '5', name: 'Casey Park', section: 'A', midterm: 85, final: 90 }, ] const weekStart = (weekIndex: number): number => new Date(2025, 0, 6 + weekIndex * 7).valueOf() const StatsLab = (): React.JSX.Element => { const [sorting, setSorting] = useState([]) const finals = students.map((s) => s.final) const meanFinal = finals.reduce((a, b) => a + b, 0) / finals.length const sortedFinals = [...finals].sort((a, b) => a - b) const medianFinal = sortedFinals[Math.floor(sortedFinals.length / 2)] const histogram = useMemo( () => ({ labels: ['50–59', '60–69', '70–79', '80–89', '90–100'], datasets: [ { label: 'Students', data: [1, 2, 4, 6, 3], backgroundColor: '#6366f1', }, ], }), [], ) const weeklyAverage = useMemo( () => ({ datasets: [ { label: 'Class average', borderColor: '#22c55e', data: [71, 74, 76, 79, 81, 83].map((y, i) => ({ x: weekStart(i), y, })), }, ], }), [], ) const columns: DataTableColumnDef[] = [ { accessorKey: 'name', header: 'Student' }, { accessorKey: 'section', header: 'Section' }, { accessorKey: 'midterm', header: 'Midterm' }, { accessorKey: 'final', header: 'Final' }, ] return (

Stats Lab

Intro statistics β€” interpret distributions and trends

Charts Roster

Final exam distribution

Weekly class average

) } ```
Nothing in this file references pools, workers, or TH/s. The same components appear on mining pages because the **data model is generic**.
## Run Stats Lab locally Follow the same monorepo workflow as [Wire a React app](/v0-2-0/tutorials/ui/react/tutorial). If you already completed that tutorial, skip to **Run the app** with `stats-lab` as the workspace name. ### Clone and build the UI monorepo ```bash git clone https://github.com/tetherto/mdk.git cd mdk ``` ```bash git clone git@github.com:tetherto/mdk.git cd mdk ``` ```bash npm install npm run build ``` ### Scaffold `apps/stats-lab` ```bash cd apps npm create vite@latest stats-lab -- --template react-ts cd stats-lab ``` Add workspace dependencies to `package.json`: ```jsonc "@tetherto/mdk-react-devkit": "*", "@tetherto/mdk-react-adapter": "*", "@tetherto/mdk-ui-core": "*", ``` Install from the monorepo root: ```bash cd ../.. npm install ``` ### Wrap with `MdkProvider` In `apps/stats-lab/src/main.tsx`, mirror [Wire a React app β€” Wrap your app in MdkProvider](/v0-2-0/tutorials/ui/react/tutorial#wrap-your-app-in-mdkprovider): ```tsx // … ``` Mock data does not call the API; the provider satisfies components that expect React context. ### Run the app From the monorepo root: ```bash npm -w stats-lab run build ``` Then start Vite: ```bash npm -w stats-lab run dev ``` Or from the app folder: ```bash cd apps/stats-lab npm run dev ``` Open the URL Vite prints (typically `http://localhost:5173`). You should see **Stats Lab** with histogram, trend line, stat cards, and a sortable roster tab. ### Optional: compare with the MDK demo app Same commands as [Wire a React app β€” Run the demo app](/v0-2-0/tutorials/ui/react/tutorial#run-the-demo-app): ```bash npm run dev:catalog ``` Open [http://localhost:5173/mdk](http://localhost:5173/mdk) to browse mining-oriented examples, then contrast with Stats Lab: **same primitives, different story**. ## Why the agent stays accurate Manifests list real exports, `check` catches many invented props, and `docs` / `example` ground the session in shipped contracts. The app build remains the final TypeScript and Vite verification. ## Next steps - [Build dashboards with your AI agent](/v0-2-0/agents): the two-step entry point - [UI CLI reference](/v0-2-0/reference/app-toolkit/ui-cli): every command in the discovery transcript - [Chart components](/v0-2-0/ui/react/core/components/charts): `BarChart`, `LineChart`, and related data shapes - [Data display](/v0-2-0/ui/react/core/components/data-display): `DataTable` sorting and pagination - [Wire a React app](/v0-2-0/tutorials/ui/react/tutorial): hands-on monorepo setup without an agent # Upgrade npm for the MDK UI monorepo (/v0-2-0/how-to/ui/react/upgrade-npm) The MDK UI workspace (`mdk/ui`) declares `engines.npm` `>=11.0.0` and pins `packageManager` to `npm@11.12.0`. **Node 22 LTS** often bundles **npm 10.9.x**, so `npm install` at the `ui/` workspace root can fail with an engine warning before you clone, build, or run the demo. ## Check your npm version ```bash npm -v ``` If the major version is **10** (for example `10.9.4`), upgrade before running `npm install` at the `ui/` workspace root. ## Upgrade npm Pick one approach. ### Option A β€” global npm 11 (simplest) ```bash npm install -g npm@11 npm -v ``` ### Option B β€” Corepack (match upstream `packageManager`) ```bash corepack enable corepack prepare npm@11.12.0 --activate npm -v ``` You should see `11.x` (Option B targets `11.12.0`). ## Retry at the ui workspace root ```bash cd /path/to/mdk/ui npm install npm run build ``` ## Next steps - [Explore the demo](/v0-2-0/ui/react/explore-the-demo) (`npm run dev:catalog`) - Follow the [quickstart](/v0-2-0/ui/react/quickstart) for app integration - Follow [Wire a React app](/v0-2-0/tutorials/ui/react/tutorial) (`npm -w my-app run dev`) # Reference (/v0-2-0/reference) The Reference section indexes the canonical specs for everything MDK exposes: field semantics, signatures, transition rules, and contracts. Reach for it when you need exact shapes. For narrative explanations, see [Architecture](/v0-2-0/concepts/architecture); for step-by-step instructions, see [Get started](/v0-2-0/get-started). ## Browse by stack area ### App Toolkit - **[UI Kit](/v0-2-0/reference/app-toolkit/ui-kit)**: constants, hooks, types, and utilities for the React UI Kit - **[UI Core](/v0-2-0/reference/app-toolkit/ui-core)**: framework-agnostic headless Zustand stores and TanStack Query factory - **[Hooks](/v0-2-0/reference/app-toolkit/hooks)**: complete hook catalog: data, state, component, and utility hooks across the adapter and devkit packages - **[UI CLI](/v0-2-0/reference/app-toolkit/ui-cli)**: command reference for the UI CLI (agent registry, scaffold, and verify) ### ORK - **[ORK](/v0-2-0/reference/ork)**: kernel module specs, state machines, transition tables, and recovery behavior ### MDK Protocol - **[Protocol](/v0-2-0/reference/protocol)**: envelope schema, request/response examples, action catalogue, and base command set - *Capability contract*: coming soon ### Hardware - **[Supported hardware](/v0-2-0/reference/supported-hardware)**: miners, containers, power meters, sensors, and mining-pool integrations # Hooks (/v0-2-0/reference/app-toolkit/hooks) Complete hook catalog for the MDK App Toolkit, covering hooks from both `@tetherto/mdk-react-adapter` (data, state, utilities) and `@tetherto/mdk-react-devkit` (component hooks). Grouped by what each hook depends on rather than which package ships it β€” this matches the [Developer entry points](/v0-2-0/concepts/architecture/app-toolkit#developer-entry-points) model where the adapter and the UI Kit are siblings, so you can adopt only the layers you need. ## At a glance | Bucket | Page | What it covers | Needs | |---|---|---|---| | Components | [Component hooks](/v0-2-0/reference/app-toolkit/hooks/components) | Hooks coupled to MDK styled components or shell layout (notifications, forms, charts, dashboards, filters, widgets, tables, reporting) | `@tetherto/mdk-react-devkit` and (for some) `` | | Data | [Data hooks](/v0-2-0/reference/app-toolkit/hooks/data) | Adapter hooks that fetch and shape site, pool, dashboard, chart, and auth data | `` from `@tetherto/mdk-react-adapter` | | State | [State hooks](/v0-2-0/reference/app-toolkit/hooks/state) | React-bound views of the headless `@tetherto/mdk-ui-core` Zustand stores | `` from `@tetherto/mdk-react-adapter` | | Utilities | [Utility hooks](/v0-2-0/reference/app-toolkit/hooks/utilities) | Generic React helpers, mining-domain transforms, permission checks, and TanStack Query re-exports | `@tetherto/mdk-react-adapter` and (for some) `` | ## All hooks ### Components @tetherto/mdk-react-devkit | Sub-group | Hooks | |---|---| | Charts | [`useChartDataCheck`](/v0-2-0/reference/app-toolkit/hooks/components#usechartdatacheck), [`useEbitda`](/v0-2-0/reference/app-toolkit/hooks/components#useebitda), [`useEnergyBalanceViewModel`](/v0-2-0/reference/app-toolkit/hooks/components#useenergybalanceviewmodel) | | Dashboards | [`usePoolConfigs`](/v0-2-0/reference/app-toolkit/hooks/components#usepoolconfigs), [`useSiteOverviewDetailsData`](/v0-2-0/reference/app-toolkit/hooks/components#usesiteoverviewdetailsdata) | | Filters | [`useReportTimeFrameSelectorState`](/v0-2-0/reference/app-toolkit/hooks/components#usereporttimeframeselectorstate), [`useTimeframeControls`](/v0-2-0/reference/app-toolkit/hooks/components#usetimeframecontrols) | | Forms | [`useFormField`](/v0-2-0/reference/app-toolkit/hooks/components#useformfield), [`useFormReset`](/v0-2-0/reference/app-toolkit/hooks/components#useformreset) | | Notifications | [`useNotification`](/v0-2-0/reference/app-toolkit/hooks/components#usenotification) | | Reporting | [`useHashrate`](/v0-2-0/reference/app-toolkit/hooks/components#usehashrate), [`useEnergyReportSite`](/v0-2-0/reference/app-toolkit/hooks/components#useenergyreportsite) | | Shell | [`useHeaderControls`](/v0-2-0/reference/app-toolkit/hooks/components#useheadercontrols), [`useSidebarExpandedState`](/v0-2-0/reference/app-toolkit/hooks/components#usesidebarexpandedstate), [`useSidebarSectionState`](/v0-2-0/reference/app-toolkit/hooks/components#usesidebarsectionstate) | | Tables | [`useGetAvailableDevices`](/v0-2-0/reference/app-toolkit/hooks/components#usegetavailabledevices) | | Widgets | [`useFinancialDateRange`](/v0-2-0/reference/app-toolkit/hooks/components#usefinancialdaterange) | ### Data @tetherto/mdk-react-adapter | Sub-group | Hooks | |---|---| | Auth and token | [`useAuthToken`](/v0-2-0/reference/app-toolkit/hooks/data#useauthtoken), [`useTokenPolling`](/v0-2-0/reference/app-toolkit/hooks/data#usetokenpolling) | | Chart data | [`useConsumptionChartData`](/v0-2-0/reference/app-toolkit/hooks/data#useconsumptionchartdata), [`useHashrateChartData`](/v0-2-0/reference/app-toolkit/hooks/data#usehashratechartdata), [`usePowerModeTimelineData`](/v0-2-0/reference/app-toolkit/hooks/data#usepowermodetimelinedata) | | Dashboard | [`useDashboardDateRange`](/v0-2-0/reference/app-toolkit/hooks/data#usedashboarddaterange), [`useDashboardExport`](/v0-2-0/reference/app-toolkit/hooks/data#usedashboardexport), [`useDashboardTimeRange`](/v0-2-0/reference/app-toolkit/hooks/data#usedashboardtimerange) | | Incidents | [`useActiveIncidents`](/v0-2-0/reference/app-toolkit/hooks/data#useactiveincidents) | | Pool data | [`usePoolRows`](/v0-2-0/reference/app-toolkit/hooks/data#usepoolrows), [`usePoolStats`](/v0-2-0/reference/app-toolkit/hooks/data#usepoolstats) | | Site data | [`useSiteConsumption`](/v0-2-0/reference/app-toolkit/hooks/data#usesiteconsumption), [`useSiteConsumptionChartData`](/v0-2-0/reference/app-toolkit/hooks/data#usesiteconsumptionchartdata), [`useSiteContainerCapacity`](/v0-2-0/reference/app-toolkit/hooks/data#usesitecontainercapacity), [`useSiteEfficiency`](/v0-2-0/reference/app-toolkit/hooks/data#usesiteefficiency), [`useSiteHashrate`](/v0-2-0/reference/app-toolkit/hooks/data#usesitehashrate), [`useSiteMinerCounts`](/v0-2-0/reference/app-toolkit/hooks/data#usesiteminercounts), [`useSiteMinerStats`](/v0-2-0/reference/app-toolkit/hooks/data#usesiteminerstats), [`useSitePowerMeter`](/v0-2-0/reference/app-toolkit/hooks/data#usesitepowermeter), [`useSitesOverviewData`](/v0-2-0/reference/app-toolkit/hooks/data#usesitesoverviewdata) | ### State @tetherto/mdk-react-adapter | Hook | Summary | |---|---| | [`useAuth`](/v0-2-0/reference/app-toolkit/hooks/state#useauth) | React view of the headless `authStore` (token, permissions) | | [`useDevices`](/v0-2-0/reference/app-toolkit/hooks/state#usedevices) | React view of the headless `devicesStore` (device list, selection) | | [`useTimezone`](/v0-2-0/reference/app-toolkit/hooks/state#usetimezone) | React view of the headless `timezoneStore` (operator timezone) | | [`useNotifications`](/v0-2-0/reference/app-toolkit/hooks/state#usenotifications) | React view of the headless `notificationStore` (unread counter) | | [`useActions`](/v0-2-0/reference/app-toolkit/hooks/state#useactions) | React view of the headless `actionsStore` (pending submissions) | ### Utilities @tetherto/mdk-react-adapter + @tetherto/mdk-react-devkit/foundation | Sub-group | Hooks | |---|---| | Device and IP | [`usePduViewer`](/v0-2-0/reference/app-toolkit/hooks/utilities#usepduviewer) | | Domain transforms | [`useCostSummary`](/v0-2-0/reference/app-toolkit/hooks/utilities#usecostsummary), [`useHashBalance`](/v0-2-0/reference/app-toolkit/hooks/utilities#usehashbalance), [`useSubsidyFees`](/v0-2-0/reference/app-toolkit/hooks/utilities#usesubsidyfees), [`useUpdateExistedActions`](/v0-2-0/reference/app-toolkit/hooks/utilities#useupdateexistedactions) | | Generic React | [`useLocalStorage`](/v0-2-0/reference/app-toolkit/hooks/utilities#uselocalstorage), [`useKeyDown`](/v0-2-0/reference/app-toolkit/hooks/utilities#usekeydown), [`useWindowSize`](/v0-2-0/reference/app-toolkit/hooks/utilities#usewindowsize), [`usePlatform`](/v0-2-0/reference/app-toolkit/hooks/utilities#useplatform), [`useDeviceResolution`](/v0-2-0/reference/app-toolkit/hooks/utilities#usedeviceresolution), [`useBeepSound`](/v0-2-0/reference/app-toolkit/hooks/utilities#usebeepsound), [`usePagination`](/v0-2-0/reference/app-toolkit/hooks/utilities#usepagination), [`useSubtractedTime`](/v0-2-0/reference/app-toolkit/hooks/utilities#usesubtractedtime), [`useTimezoneFormatter`](/v0-2-0/reference/app-toolkit/hooks/utilities#usetimezoneformatter) | | Permissions | [`useCheckPerm`](/v0-2-0/reference/app-toolkit/hooks/utilities#usecheckperm), [`useHasPerms`](/v0-2-0/reference/app-toolkit/hooks/utilities#usehasperms), [`useIsFeatureEditingEnabled`](/v0-2-0/reference/app-toolkit/hooks/utilities#useisfeatureeditingenabled) | | TanStack Query re-exports | [Re-exports table](/v0-2-0/reference/app-toolkit/hooks/utilities#tanstack-query-re-exports) | ## Imports ```tsx // Data hooks ``` # Component hooks (/v0-2-0/reference/app-toolkit/hooks/components) @tetherto/mdk-react-devkit Hooks that wrap or compose styled MDK components β€” notifications, sidebar/header shell, forms, filters, widgets, tables, charts, and dashboards. Adopt these when you are using `@tetherto/mdk-react-devkit` for your UI. If you bring your own components, you may not need anything here. Start with [State hooks](/v0-2-0/reference/app-toolkit/hooks/state) or [Utility hooks](/v0-2-0/reference/app-toolkit/hooks/utilities) instead. ## At a glance | Sub-group | Hooks | |---|---| | [Notifications](#notifications) | `useNotification` | | [Shell](#shell) | `useHeaderControls`, `useSidebarExpandedState`, `useSidebarSectionState` | | [Forms](#forms) | `useFormField`, `useFormReset` | | [Filters](#filters) | `useReportTimeFrameSelectorState`, `useTimeframeControls` | | [Widgets](#widgets) | `useFinancialDateRange` | | [Tables](#tables) | `useGetAvailableDevices` | | [Charts](#charts) | `useChartDataCheck`, `useEbitda`, `useEnergyBalanceViewModel` | | [Dashboards](#dashboards) | `usePoolConfigs`, `useSiteOverviewDetailsData` | | [Reporting](#reporting) | `useHashrate`, `useEnergyReportSite` | ## Prerequisites - Complete the [@tetherto/mdk-react-devkit installation and add the dependency](/v0-2-0/ui/react/foundation#prerequisites) - For hooks that read from headless stores (`useNotification`, view-model hooks): [wrap your app in ``](/v0-2-0/ui/react/quickstart#wrap-your-app-in-mdkprovider) ## Import ```tsx useChartDataCheck, useEbitda, useEnergyBalanceViewModel, useFinancialDateRange, useGetAvailableDevices, useHeaderControls, useNotification, usePoolConfigs, useReportTimeFrameSelectorState, useSiteOverviewDetailsData, useTimeframeControls, } from '@tetherto/mdk-react-devkit/foundation' useFormField, useFormReset, useSidebarExpandedState, useSidebarSectionState, } from '@tetherto/mdk-react-devkit/core' ``` ## Notifications ### `useNotification` @tetherto/mdk-react-devkit/foundation Show toast notifications backed by the headless `notificationStore`. Supports success, error, info, and warning variants. ```tsx ``` #### Returns | Member | Type | Description | |--------|------|-------------| | `notifySuccess` | `function` | Show success toast | | `notifyError` | `function` | Show error toast | | `notifyInfo` | `function` | Show info toast | | `notifyWarning` | `function` | Show warning toast | #### Method signature ```tsx notifySuccess(message: string, description?: string, options?: NotificationOptions) ``` #### Options Notification methods accept an optional third `options` argument: | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `duration` | Optional | `number` | `3000` | Duration in milliseconds (`0` = no autoclose) | | `position` | Optional | `ToastPosition` | `'top-left'` | Toast position on screen | | `dontClose` | Optional | `boolean` | `false` | When `true`, prevents autoclose | #### Example ```tsx function SaveButton() { const { notifySuccess, notifyError } = useNotification() const handleSave = async () => { try { await saveData() notifySuccess('Saved', 'Your changes have been saved.') } catch (error) { notifyError('Error', 'Failed to save changes.', { dontClose: true }) } } return } ``` ## Shell ### `useHeaderControls` @tetherto/mdk-react-devkit/foundation Read/write hook for the global header-controls store (toggles, sticky flag, theme). ```tsx ``` #### Returns | Member | Type | Description | |--------|------|-------------| | `preferences` | `HeaderPreferences` | Current visibility state for each header item | | `isLoading` | `boolean` | Loading state | | `error` | `Error \| null` | Error state | | `handleToggle` | `function` | Toggle a header item visibility | | `handleReset` | `function` | Reset to default preferences | `handleToggle` and `handleReset` both call `notifySuccess` internally. Every invocation produces a toast notification; avoid calling them in response to fast-changing state. #### Example ```tsx function HeaderSettings() { const { preferences, handleToggle, handleReset } = useHeaderControls() return (
{Object.entries(preferences).map(([key, visible]) => ( handleToggle(key, value)} /> ))}
) } ``` ### `useSidebarExpandedState` @tetherto/mdk-react-devkit/core Persist sidebar expanded/collapsed state in `localStorage` so the layout survives reloads. ```tsx ``` #### Example ```tsx function AppSidebar() { const [expanded, setExpanded] = useSidebarExpandedState(false) return ( ) } ``` ### `useSidebarSectionState` @tetherto/mdk-react-devkit/core Persist individual sidebar section open/closed states in `localStorage`. ```tsx ``` #### Example ```tsx function SidebarSection({ id, title, children }) { const [open, setOpen] = useSidebarSectionState(id, true) return (
{open ? children : null}
) } ``` ## Forms ### `useFormField` @tetherto/mdk-react-devkit/core Read-only context hook for form field children β€” returns the field's id, error state, and ARIA attributes. ```tsx ``` #### Example ```tsx // Custom component that reads field context β€” must be rendered inside / function FieldStatusDot() { const { invalid, isDirty } = useFormField() return ( ) } ``` ### `useFormReset` @tetherto/mdk-react-devkit/core Hook to handle form reset with callbacks. ```tsx ``` #### Example ```tsx type MinerFields = { name: string; ip: string } function MinerEditForm({ onSubmit }: { onSubmit: (v: MinerFields) => void }) { const form = useForm({ defaultValues: { name: '', ip: '' } }) const { resetForm, isDirty } = useFormReset({ form, onAfterReset: () => console.log('Form reset'), }) return (
) } ``` ## Filters ### `useReportTimeFrameSelectorState` @tetherto/mdk-react-devkit/foundation State hook backing the reporting time-frame selector β€” exposes the active window and setters. ```tsx ``` #### Example ```tsx function ReportDateBar() { const { start, end, presetTimeFrame, setPresetTimeFrame } = useReportTimeFrameSelectorState() return (

{start.toLocaleDateString()} – {end.toLocaleDateString()}

) } ``` ### `useTimeframeControls` @tetherto/mdk-react-devkit/foundation Core state machine for TimeframeControls β€” owns year / month / week selection and resolves the date-range output. ```tsx ``` #### Example ```tsx function YearMonthPicker({ dateRange, onRangeChange, onTimeframeTypeChange }) { const { selectedYear, selectedMonth, handleYearChange, handleMonthTreeChange, yearSelectValue, monthSelectValue, } = useTimeframeControls({ dateRange, timeframeType: null, onRangeChange, onTimeframeTypeChange, isWeekSelectVisible: false, weekTree: false, }) return (
) } ``` ## Widgets ### `useFinancialDateRange` @tetherto/mdk-react-devkit/foundation Resolves the active financial date range (start/end) used by every reporting-section query. ```tsx ``` #### Example ```tsx function ReportingToolbar({ timezone }: { timezone: string }) { const { datePicker, dateRange, onDateRangeReset } = useFinancialDateRange({ timezone }) return (
{datePicker} {dateRange && (

{new Date(dateRange.start).toLocaleDateString()} –{' '} {new Date(dateRange.end).toLocaleDateString()}

)}
) } ``` ## Tables ### `useGetAvailableDevices` @tetherto/mdk-react-devkit/foundation Transforms the host's device list into the available container and miner type sets used by device explorer. Pass `data` from your query result. ```tsx ``` #### Example ```tsx function DeviceTypeFilter({ devices }) { const { availableContainerTypes, availableMinerTypes } = useGetAvailableDevices({ data: devices }) return (
) } ``` ## Charts ### `useChartDataCheck` @tetherto/mdk-react-devkit/foundation Check if chart data is empty or unavailable. Returns `true` if empty (show empty state), `false` if data exists (show chart). ```tsx ``` Pass chart input in one of two shapes: 1. **`dataset`**: direct dataset for BarChart-style usage. 2. **`data`**: Chart.js-shaped object with `datasets` (LineChart) or a `dataset` property. Provide at least one of `dataset` or `data` for a meaningful empty check. #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `dataset` | Optional | `object \| array` | none | Direct dataset for BarChart; set `dataset` or `data` (at least one) for a meaningful check | | `data` | Optional | `object` | none | Chart.js-shaped object with `datasets` (LineChart) or `dataset` property; set `dataset` or `data` (at least one) | #### Returns | Type | Description | |------|-------------| | `boolean` | `true` if data is empty, `false` if data exists | #### Example ```tsx function HashrateChart({ dataset }) { const isEmpty = useChartDataCheck({ dataset }) if (isEmpty) { return } return } ``` ```tsx function TemperatureChart({ data }) { const isEmpty = useChartDataCheck({ data }) return isEmpty ? ( ) : ( ) } ``` #### Chart utility integration `useChartDataCheck` expects **Chart.js-shaped** `data` (`{ labels, datasets }`), not raw `{ labels, series }` from app hooks. Convert hook output with the [**`buildBarChartData` utility**](/v0-2-0/ui/react/core/components/charts/composition#chart-utilities) from `@tetherto/mdk-react-devkit/core`, then pass the result to **`useChartDataCheck`**. ```tsx function RevenueBarChart({ hookOutput }) { const chartData = buildBarChartData(hookOutput) const isEmpty = useChartDataCheck({ data: chartData }) return ( ) } ``` For the full `BarChartInput` shape, per-dataset `datalabels` merge, and all-zero empty rules, see [Hook-shaped bar data (`buildBarChartData`)](/v0-2-0/ui/react/core/components/charts/composition#hook-shaped-bar-data). ### `useEbitda` @tetherto/mdk-react-devkit/foundation Transforms an `EbitdaResponse` and date-range options into query params and a chart-ready EBITDA view-model. ```tsx ``` #### Example ```tsx // Wire your query result in; consume queryParams to drive the fetch function EbitdaSection({ ebitdaResponse, isLoading, fetchErrors }) { const { datePicker, dateRange, queryParams, errors } = useEbitda({ ebitda: ebitdaResponse, isLoading, fetchErrors, }) // Pass queryParams to your data-fetching layer whenever the date range changes // e.g. useGetEbitdaQuery(queryParams, { skip: !queryParams }) return (
{datePicker} {errors.length > 0 &&

{errors.join(', ')}

}
) } ``` ### `useEnergyBalanceViewModel` @tetherto/mdk-react-devkit/foundation Computes the full EnergyBalance view model from raw API data, managing tab selection and display-mode state. ```tsx ``` #### Example ```tsx function EnergyBalancePanel({ data, isLoading, fetchErrors, dateRange, availablePowerMW }) { const { viewModel, onTabChange, onRevenueDisplayModeChange } = useEnergyBalanceViewModel({ data, isLoading, fetchErrors, dateRange, availablePowerMW, }) return (
{viewModel.isLoading &&

Loading…

} {viewModel.errors.length > 0 &&

{viewModel.errors.join(', ')}

}
) } ``` ## Dashboards ### `usePoolConfigs` @tetherto/mdk-react-devkit/foundation Transforms raw pool-configuration rows from your API into `PoolSummary` objects for the Pool Manager UI. Fetch with TanStack Query in the host app, then pass `data`, `isLoading`, and `error` into this hook. Typical usage: fetch with TanStack Query in the host app, then pass `data`, `isLoading`, and `error` into this hook. Foundation components such as [`PoolManagerPools`](/v0-2-0/ui/react/foundation/pool-manager/pools) and [`Miner explorer`](/v0-2-0/ui/react/foundation/pool-manager/miner-explorer) expect data shaped this way. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `data` | Optional | `PoolConfigData[]` | none | Raw pool configuration rows from your API | | `isLoading` | Optional | `boolean` | `false` | When `true`, the host should show a loading state | | `error` | Optional | `unknown` | none | Error from your query; surfaced to pool-manager components | #### Returns | Member | Type | Description | |--------|------|-------------| | `pools` | `PoolSummary[]` | Normalized pool list for lists and accordions | | `poolIdMap` | `Record` | Lookup by pool `id` | | `isLoading` | `boolean` | Same as the option you passed in | | `error` | `unknown` | Same as the option you passed in | #### Example ```tsx const { data, isLoading, error } = useGetPoolConfigsQuery({}) return usePoolConfigs({ data, isLoading, error }) } ``` ```tsx function PoolsPage({ poolConfig }: { poolConfig: PoolConfigData[] }) { const { pools, isLoading, error } = usePoolConfigs({ data: poolConfig }) if (isLoading) return if (error) return Failed to load pools return (
    {pools.map((pool) => (
  • {pool.name}
  • ))}
) } ``` ### `useSiteOverviewDetailsData` @tetherto/mdk-react-devkit/foundation Composes the per-site overview view-model: pools, performance series, and recent activity. ```tsx ``` #### Example ```tsx function SiteOverviewCard({ unit, pdus, connectedMiners, isLoading }) { const { containerHashRate, isContainerRunning, minersHashmap, segregatedPduSections, } = useSiteOverviewDetailsData(unit, { pdus, connectedMiners, isLoading }) return (

Hashrate: {containerHashRate}

Status: {isContainerRunning ? 'Running' : 'Offline'}

Miners mapped: {Object.keys(minersHashmap).length}

PDU sections: {Object.keys(segregatedPduSections).join(', ')}

) } ``` ## Reporting ### `useHashrate` @tetherto/mdk-react-devkit/foundation Base hook for a single Hashrate tab in single-site mode. Normalises a grouped-hashrate query result into the `{ log, isLoading, error }` shape consumed by ``, ``, and ``. Call once per tab β€” each tab fetches independently because they use different `groupBy` axes. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `query` | Optional | `HashrateQueryState` | none | Result of fetching the v2 `/auth/metrics/hashrate?groupBy=…` endpoint. Wire your data layer (TanStack Query, RTK Query, fixtures) and pass the result here β€” this hook never fetches directly. | #### Returns | Member | Type | Description | |--------|------|-------------| | `log` | `HashrateGroupedLog \| undefined` | Normalised grouped-hashrate log; `undefined` while loading. | | `isLoading` | `boolean` | Loading state from the upstream query. | | `error` | `unknown` | Error from the upstream query, if any. | #### Example ```tsx function HashrateTab({ groupBy }) { const query = useQuery({ queryKey: ['hashrate', groupBy], queryFn: fetchHashrate }) const { log, isLoading, error } = useHashrate({ query }) return } ``` --- ### `useEnergyReportSite` @tetherto/mdk-react-devkit/foundation Merges site energy consumption data (from the v2 `/auth/metrics/consumption` endpoint) with snapshot tail-log and container list data for the Energy report site tab. Returns the combined view-model consumed by the energy report components. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `dateRange` | Required | `object` | none | **Required.** Active date range `{ start, end }` in ms epoch. | | `consumptionLog` | Required | `object` | none | **Required.** Raw consumption log from the `/auth/metrics/consumption` response. | | `consumptionLoading` | Required | `boolean` | none | **Required.** Loading state of the consumption query. | | `consumptionFetching` | Required | `boolean` | none | **Required.** Background-refetch state of the consumption query. | | `consumptionError` | Required | `unknown` | none | **Required.** Error state of the consumption query. | | `nominalPowerAvailabilityMw` | Required | `number \| undefined` | none | **Required.** Site nominal power capacity in MW from the nominal config. | | `nominalConfigLoading` | Required | `boolean` | none | **Required.** Loading state of the nominal config query. | | `tailLog` | Required | `object` | none | **Required.** Raw tail-log snapshot data. | | `tailLogLoading` | Required | `boolean` | none | **Required.** Loading state of the tail-log query. | | `containers` | Required | `object[]` | none | **Required.** Container list from the device query. | | `containersLoading` | Required | `boolean` | none | **Required.** Loading state of the container query. | #### Returns Returns a `UseEnergyReportSiteResult` object containing the merged power-consumption view-model, power-mode table rows, and combined loading/error states for each data source. Pass directly to the Energy report site-tab components. # Data hooks (/v0-2-0/reference/app-toolkit/hooks/data) @tetherto/mdk-react-adapter TanStack Query–backed hooks that fetch live data from the MDK backend and project it into view-model shapes ready for MDK foundation components. These hooks sit in the adapter layer so that tag names, aggregate field keys, and unit conversions stay out of the devkit component layer. All hooks require [``](/v0-2-0/ui/react/quickstart#wrap-your-app-in-mdkprovider) to be mounted in the tree. ## At a glance | Sub-group | Hooks | |---|---| | [Auth and token](#auth-and-token) | `useAuthToken`, `useTokenPolling` | | [Chart data](#chart-data) | `useConsumptionChartData`, `useHashrateChartData`, `usePowerModeTimelineData` | | [Dashboard](#dashboard) | `useDashboardDateRange`, `useDashboardExport`, `useDashboardTimeRange` | | [Incidents](#incidents) | `useActiveIncidents` | | [Pool data](#pool-data) | `usePoolRows`, `usePoolStats` | | [Site data](#site-data) | `useSiteConsumption`, `useSiteConsumptionChartData`, `useSiteContainerCapacity`, `useSiteEfficiency`, `useSiteHashrate`, `useSiteMinerCounts`, `useSiteMinerStats`, `useSitePowerMeter`, `useSitesOverviewData` | ## Prerequisites - [Wrap your app in ``](/v0-2-0/ui/react/quickstart#wrap-your-app-in-mdkprovider) - Complete the [@tetherto/mdk-react-devkit installation and add the dependency](/v0-2-0/ui/react/foundation#prerequisites) ## Import ```tsx useActiveIncidents, useAuthToken, useConsumptionChartData, useDashboardDateRange, useDashboardExport, useDashboardTimeRange, useHashrateChartData, usePoolRows, usePoolStats, usePowerModeTimelineData, useSiteConsumption, useSiteConsumptionChartData, useSiteContainerCapacity, useSiteEfficiency, useSiteHashrate, useSiteMinerCounts, useSiteMinerStats, useSitePowerMeter, useSitesOverviewData, useTokenPolling, } from '@tetherto/mdk-react-adapter' ``` --- ## Site data ### `useSitesOverviewData` Projects raw site-overview container rows and pool stats into a ``-ready shape. Each container receives its per-container hashrate in MH/s, an attached pool-stats row, and a `mining` / `offline` status derived from the underlying snapshot. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `units` | Required | `ContainerUnit[]` | none | **Required.** Raw container rows from the site overview query. | | `poolStats` | Required | `ContainerPoolStat[]` | none | **Required.** Per-container pool stat rows keyed by container id. | | `isLoading` | Required | `boolean` | none | **Required.** Combined loading state from the upstream queries. | | `tailLogItem` | Required | `SitesOverviewTailLogItem` | none | **Required.** Latest tail-log row from a `stat-1m` miner query; pass `_head(_head(rawResponse))`. | #### Returns | Member | Type | Description | |--------|------|-------------| | `units` | `ProcessedContainerUnit[]` | Processed containers with `hashrateMhs`, `status`, and `poolStats` attached. | | `isLoading` | `boolean` | Passes through the combined loading state from the options. | #### Example ```tsx function SiteOverview({ rawUnits, rawPoolStats, loading, latestTailLog }) { const { units, isLoading } = useSitesOverviewData({ units: rawUnits, poolStats: rawPoolStats, isLoading: loading, tailLogItem: latestTailLog, }) return } ``` --- ### `useSiteConsumption` Projects the freshest power sample from the dashboard's existing tail-log query into a scalar MW value for the header stats strip (``). Delegates fetching to [`useSiteConsumptionChartData`](#usesiteconsumptionchartdata). ```tsx ``` #### Options Accepts the same `UseConsumptionChartDataParams` object as [`useSiteConsumptionChartData`](#usesiteconsumptionchartdata). | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `timeline` | Required | `string` | none | **Required.** Stat key suffix, e.g. `'5m'`. | | `start` | Optional | `number` | none | Lower time bound (ms epoch). | | `end` | Optional | `number` | none | Upper time bound (ms epoch). | | `tag` | Optional | `string` | `'t-miner'` | Thing tag. Use `'t-powermeter'` for transformer-level readings. | | `powerAttribute` | Optional | `string` | `'power_w_sum_aggr'` | Aggregate field name to read from the tail-log row. | | `refetchInterval` | Optional | `number` | `60000` | Polling interval in ms. Pass `0` to disable. | #### Returns | Member | Type | Description | |--------|------|-------------| | `valueMw` | `number \| undefined` | Latest aggregate value in MW; `undefined` while loading or with no data. | | `valueW` | `number \| undefined` | Raw backend value in watts. | | `isLoading` | `boolean` | Loading state from the upstream query. | --- ### `useSiteConsumptionChartData` TanStack Query hook that fetches site consumption tail-log samples and returns a ``-ready `ChartCardData` payload. Applies site-powermeter defaults and converts watts to MW. ```tsx ``` #### Options Same `UseConsumptionChartDataParams` as [`useSiteConsumption`](#usesiteconsumption). #### Returns A TanStack `UseQueryResult` whose `data` field is `ChartCardData` β€” the object accepted by ``. #### Example ```tsx function ConsumptionChart() { const { data, isLoading } = useSiteConsumptionChartData({ timeline: '5m' }) return } ``` --- ### `useSiteContainerCapacity` Reads the aggregated nominal miner capacity across all site containers from the 5-minute tail-log aggregate, giving the denominator shown in the header (e.g. the "2,188" in "MOS / 2,188"). ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `refetchInterval` | Optional | `number` | `300000` | Polling interval in ms. Pass `0` to disable. | #### Returns | Member | Type | Description | |--------|------|-------------| | `value` | `number \| undefined` | Total nominal miner slots across all site containers; `undefined` while loading. | | `isLoading` | `boolean` | Loading state. | --- ### `useSiteEfficiency` Derives site efficiency in W/TH/s by dividing the latest site consumption by the latest hashrate. Both upstream hooks are called internally; pass `powerW` to override the consumption source (e.g. to use a power-meter reading instead). ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `timeline` | Required | `string` | none | **Required.** Stat key suffix forwarded to both upstream hooks. | | `start` | Optional | `number` | none | Lower time bound (ms epoch). | | `end` | Optional | `number` | none | Upper time bound (ms epoch). | | `tag` | Optional | `string` | none | Thing tag override forwarded to the consumption hook. | | `powerAttribute` | Optional | `string` | none | Aggregate field override forwarded to the consumption hook. | | `refetchInterval` | Optional | `number` | none | Polling interval in ms forwarded to both upstream hooks. | | `powerW` | Optional | `number` | none | When provided, skips the internal `useSiteConsumption` call and uses this watts value as the numerator directly. Pair with `useSitePowerMeter().valueW` for meter-level efficiency. | #### Returns | Member | Type | Description | |--------|------|-------------| | `valueWthS` | `number \| undefined` | Watts per TH/s; `undefined` while loading or when hashrate is zero. | | `isLoading` | `boolean` | `true` while either upstream hook is loading. | --- ### `useSiteHashrate` TanStack Query hook that reads the latest aggregate hashrate from the site tail-log and returns both a PH/s display value and the raw MH/s backend value. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `timeline` | Required | `string` | none | **Required.** Stat key suffix, e.g. `'5m'`. | | `start` | Optional | `number` | none | Lower time bound (ms epoch). | | `end` | Optional | `number` | none | Upper time bound (ms epoch). | | `refetchInterval` | Optional | `number` | `60000` | Polling interval in ms. Pass `0` to disable. | #### Returns | Member | Type | Description | |--------|------|-------------| | `valuePhs` | `number \| undefined` | Latest aggregate value in PH/s; `undefined` while loading or with no data. | | `valueMhs` | `number \| undefined` | Latest aggregate value in MH/s (raw backend unit). | | `isLoading` | `boolean` | Loading state. | --- ### `useSiteMinerCounts` Polls the device list for miners and aggregates them into online / offline / error counts. Uses the `last.status` field on each device row. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `refetchInterval` | Optional | `number` | none | Polling interval in ms. | #### Returns | Member | Type | Description | |--------|------|-------------| | `total` | `number` | Total miner device count. | | `online` | `number` | Miners with status `online` or `on`. | | `offline` | `number` | Miners not online and not in error. | | `error` | `number` | Miners with status `error` or `alert`. | --- ### `useSiteMinerStats` Polls the `stat-rtd` realtime aggregate for the live miner stat summary β€” the MOS total, online/offline breakdown, and active pool worker count shown in the header. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `refetchInterval` | Optional | `number` | none | Polling interval in ms. | #### Returns | Member | Type | Description | |--------|------|-------------| | `mosTotal` | `number` | Count of miners that reported hashrate in the last minute (the MOS denominator). | | `online` | `number` | Miners online or with minor errors (green column). | | `offline` | `number` | Offline or sleeping miners. | | `notMining` | `number` | Miners not currently mining. | | `isLoading` | `boolean` | Loading state. | --- ### `useSitePowerMeter` Reads the current power-meter reading in watts from the site-level device tagged `t-powermeter`. Returns a MW conversion alongside the raw watts value. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `tag` | Optional | `string` | `'t-powermeter'` | Device tag to filter by. | | `refetchInterval` | Optional | `number` | none | Polling interval in ms. | #### Returns | Member | Type | Description | |--------|------|-------------| | `valueMw` | `number \| undefined` | Latest power-meter reading in MW; `undefined` when no device or no data. | | `valueW` | `number \| undefined` | Raw value in watts. | | `isLoading` | `boolean` | Loading state. | --- ## Pool data ### `usePoolRows` TanStack Query hook that fetches per-pool stats and transforms them into `PoolRow[]` objects shaped for the pool manager table. Hashrate is normalised from raw H/s to PH/s. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `refetchInterval` | Optional | `number` | `120000` | Polling interval in ms. Pass `0` to disable. | #### Returns A TanStack `UseQueryResult` whose `data` is `PoolRow[]`. | Member | Type | Description | |--------|------|-------------| | `id` | `string` | Stable React key derived from `poolType`. | | `name` | `string` | Display name in Moria style β€” `minerpool-{poolType}-shelf-0`. | | `poolType` | `string` | Raw pool type string, e.g. `'f2pool'`, `'ocean'`. | | `revenue24hBtc` | `number \| undefined` | 24 h revenue in BTC if reported. | | `hashrateHs` | `number \| undefined` | Hashrate in raw H/s. | | `hashratePhsDisplay` | `number \| undefined` | Hashrate in PH/s for display. | | `details` | `PoolDetail[]` | Array of label/value pairs for the expanded row. | --- ### `usePoolStats` TanStack Query hook that fetches per-pool stats and aggregates them into site-level totals β€” total workers, online workers, mismatch count, and aggregate hashrate in PH/s. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `refetchInterval` | Optional | `number` | `120000` | Polling interval in ms. Pass `0` to disable. | #### Returns | Member | Type | Description | |--------|------|-------------| | `total` | `number` | Total miners reported across all configured pools. | | `online` | `number` | Pool-reported active workers. | | `mismatch` | `number` | Difference between configured and active workers. | | `hashratePhs` | `number \| undefined` | Aggregate pool hashrate in PH/s; `undefined` while loading or with no data. | | `hashrateHs` | `number \| undefined` | Aggregate pool hashrate in raw H/s. | | `isLoading` | `boolean` | Loading state. | --- ## Dashboard ### `useDashboardDateRange` Owns the single source of truth for the dashboard's date-range picker: a `start` / `end` ms-epoch pair with setters and a reset helper that restores the default 24-hour window. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `initial` | Optional | `{ start: number; end: number }` | last 24 h | Initial date range. Defaults to the last 24 hours ending now. | #### Returns | Member | Type | Description | |--------|------|-------------| | `start` | `number` | Lower bound (ms epoch). | | `end` | `number` | Upper bound (ms epoch). | | `setRange` | `function` | Replace the current range. | | `reset` | `function` | Convenience: restore the default 24-hour window ending now. | #### Example ```tsx function DashboardHeader() { const { start, end, setRange, reset } = useDashboardDateRange() return } ``` --- ### `useDashboardExport` Reads cached query data for hashrate, consumption, incidents, and pool stats from the TanStack Query client and exposes `exportCsv`, `exportJson`, and a unified `export(format)` callable. Does not trigger refetches β€” the export represents exactly what is currently displayed. ```tsx ``` #### Options Accepts a `UseDashboardExportOptions` object containing `DashboardQueryRange` fields (`timeline`, `start`, `end`, `tag`) needed to reconstruct the query keys. | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `timeline` | Required | `string` | none | **Required.** Stat key suffix used to locate the cached chart queries. | | `start` | Optional | `number` | none | Lower time bound (ms epoch). | | `end` | Optional | `number` | none | Upper time bound (ms epoch). | | `tag` | Optional | `string` | none | Thing tag override. | #### Returns | Member | Type | Description | |--------|------|-------------| | `exportCsv` | `function` | Triggers a browser file-download of the dashboard data as CSV. | | `exportJson` | `function` | Triggers a browser file-download as JSON. | | `export` | `function` | Unified callable β€” `export('csv')` or `export('json')`. | --- ### `useDashboardTimeRange` Tiny piece of scoped state for the dashboard's timeline selector. Owns the current `timeline` string and the canonical option list. Chart hooks (`useHashrateChartData`, `useSiteConsumptionChartData`, etc.) consume `timeline` as a prop. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `initial` | Optional | `string` | `'5m'` | Initial timeline value. | | `options` | Optional | `TimelineOption[]` | canonical list | Custom option list; defaults to the canonical set from `getTimelineOptions`. | #### Returns | Member | Type | Description | |--------|------|-------------| | `timeline` | `string` | Currently selected timeline string, e.g. `'5m'`. | | `options` | `TimelineOption[]` | Available options, ready for ``. | | `setTimeline` | `function` | Setter for the current timeline. | #### Example ```tsx function DashboardControls() { const { timeline, options, setTimeline } = useDashboardTimeRange() const { valuePhs } = useSiteHashrate({ timeline }) return ( <> {valuePhs?.toFixed(2)} PH/s ) } ``` --- ## Chart data ### `useConsumptionChartData` TanStack Query hook returning raw consumption tail-log samples. Most dashboards should use the higher-level [`useSiteConsumptionChartData`](#usesiteconsumptionchartdata), which wraps this hook and returns a ``-ready payload. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `timeline` | Required | `string` | none | **Required.** Stat key suffix, e.g. `'1m'`, `'5m'`. | | `start` | Optional | `number` | none | Lower time bound (ms epoch). | | `end` | Optional | `number` | none | Upper time bound (ms epoch). | | `tag` | Optional | `string` | `'t-miner'` | Thing tag. Use `'t-powermeter'` for transformer-level consumption. | | `powerAttribute` | Optional | `string` | `'power_w_sum_aggr'` | Aggregate field name to read. | | `refetchInterval` | Optional | `number` | `60000` | Polling interval in ms. Pass `0` to disable. | #### Returns A TanStack `UseQueryResult` β€” raw tail-log matrix before projection. --- ### `useHashrateChartData` TanStack Query hook combining the site tail-log hashrate series with the per-pool hashrate history into a single `ChartCardData` payload for ``. Handles unit conversion from both MH/s (tail-log) and raw H/s (pool API) into PH/s for display. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `timeline` | Required | `string` | none | **Required.** Stat key suffix, e.g. `'5m'`. | | `start` | Optional | `number` | none | Lower time bound (ms epoch). | | `end` | Optional | `number` | none | Upper time bound (ms epoch). | | `refetchInterval` | Optional | `number` | none | Polling interval in ms. | #### Returns A TanStack `UseQueryResult` whose `data` is `ChartCardData` β€” ready for ``. --- ### `usePowerModeTimelineData` TanStack Query hook returning power-mode and status samples shaped for ``. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `timeline` | Required | `string` | none | **Required.** Stat key suffix, e.g. `'1m'`. | | `start` | Optional | `number` | none | Lower time bound (ms epoch). | | `end` | Optional | `number` | none | Upper time bound (ms epoch). | | `tag` | Optional | `string` | none | Thing tag override. | | `refetchInterval` | Optional | `number` | none | Polling interval in ms. | #### Returns A TanStack `UseQueryResult`. --- ## Auth and token ### `useAuthToken` Reads `?authToken=…` from `window.location.search`, persists it into the headless `authStore`, and strips the parameter from the URL via `history.replaceState` so the token never lingers in the address bar. Router-agnostic by design. Returns the current token from the store so callers can gate navigation on authentication. ```tsx ``` #### Returns | Type | Description | |------|-------------| | `string \| null` | Current auth token from the `authStore`, or `null` when no token is present. | #### Example ```tsx function ProtectedRoute() { const token = useAuthToken() return token ? : } ``` --- ### `useTokenPolling` Polls the backend token-refresh endpoint at a fixed interval (default 250 s) and writes the new token back into the `authStore`. Fires the optional `onSessionEnded` callback on a 401 or 500, which MDK UI Shell uses to redirect back to `/signin`. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `intervalMs` | Optional | `number` | `250000` | Polling interval in ms. Backend token TTL defaults to 5 min (300 s); 250 s gives comfortable headroom. | | `enabled` | Optional | `boolean` | `true` when token present | Pass `false` to pause polling (e.g. on the sign-in page). | | `onSessionEnded` | Optional | `function` | none | Called on 401 or 500; use to navigate to the sign-in page. | #### Example ```tsx function App() { const navigate = useNavigate() useTokenPolling({ onSessionEnded: () => navigate('/signin') }) return } ``` --- ## Incidents ### `useActiveIncidents` TanStack Query hook returning the list of currently-firing alerts, shaped for ``. Queries devices that have a non-null `last.alerts` field and maps them via the `mapDevicesToIncidents` utility. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `refetchInterval` | Optional | `number` | `20000` | Polling interval in ms. Matches Moria's production cadence. Pass `0` to disable. | | `formatDate` | Optional | `function` | ISO string | Date formatter applied to the row body timestamp. | #### Returns A TanStack `UseQueryResult`. #### Example ```tsx function IncidentsFeed() { const { data: items = [], isLoading } = useActiveIncidents({ refetchInterval: 20_000 }) return } ``` # State hooks (/v0-2-0/reference/app-toolkit/hooks/state) @tetherto/mdk-react-adapter State hooks bind the framework-agnostic [`@tetherto/mdk-ui-core`](/v0-2-0/reference/app-toolkit/ui-core) Zustand stores into React. Each hook subscribes the component to its store and re-renders only when the selected slice changes. Use these when you want headless state with your own components. The [Developer entry points](/v0-2-0/concepts/architecture/app-toolkit#developer-entry-points) table compares adoption layers. ## Prerequisites - [Wrap your app in ``](/v0-2-0/ui/react/quickstart#wrap-your-app-in-mdkprovider) - Complete the [@tetherto/mdk-react-devkit installation and add the dependency](/v0-2-0/ui/react/foundation#prerequisites) ## Import ```tsx useActions, useAuth, useDevices, useNotifications, useTimezone, } from '@tetherto/mdk-react-adapter' ``` ## `useAuth` @tetherto/mdk-react-adapter React-bound view of the headless [`authStore`](/v0-2-0/reference/app-toolkit/ui-core). Exposes `token` and `permissions`; equivalent to `useStore(authStore)` with React subscription semantics. ```tsx ``` ### Example ```tsx function SessionStatus() { const { token } = useAuth() if (!token) return

Sign in to continue

return

Active session

} ``` ## `useDevices` @tetherto/mdk-react-adapter React-bound view of the headless [`devicesStore`](/v0-2-0/reference/app-toolkit/ui-core). Exposes the device list, the currently selected devices, and helpers to mutate selection. ```tsx ``` ### Example ```tsx function DeviceToolbar() { const { selectedDevices, setSelectedDevices } = useDevices() return (

Selected: {selectedDevices.length}

) } ``` ## `useTimezone` @tetherto/mdk-react-adapter React-bound view of the headless [`timezoneStore`](/v0-2-0/reference/app-toolkit/ui-core). Read or update the operator's timezone preference (IANA identifier). Use [`useTimezoneFormatter`](/v0-2-0/reference/app-toolkit/hooks/utilities#usetimezoneformatter) when you also need date-formatting helpers. ```tsx ``` ### Example ```tsx function TimezonePicker() { const { timezone, setTimezone } = useTimezone() return ( ) } ``` ## `useNotifications` @tetherto/mdk-react-adapter React-bound view of the headless [`notificationStore`](/v0-2-0/reference/app-toolkit/ui-core). Exposes the unread counter (`count`) plus `increment`, `decrement`, and `reset`. For the user-facing toast surface use [`useNotification`](/v0-2-0/reference/app-toolkit/hooks/components#usenotification) from foundation. ```tsx ``` ### Example ```tsx function UnreadBadge() { const { count } = useNotifications() return } ``` ## `useActions` @tetherto/mdk-react-adapter React-bound view of the headless [`actionsStore`](/v0-2-0/reference/app-toolkit/ui-core). Exposes the pending operator submission queue plus helpers like `setAddPendingSubmissionAction` and `removePendingSubmissionAction`. ```tsx ``` ### Example ```tsx function SubmissionTracker() { const { pendingSubmissions, setAddPendingSubmissionAction } = useActions() return (

{pendingSubmissions.length} pending submissions

) } ``` # Utility hooks (/v0-2-0/reference/app-toolkit/hooks/utilities) @tetherto/mdk-react-adapter + @tetherto/mdk-react-devkit/foundation Generic React helpers, mining-domain transforms, permission checks, device/IP utilities, and a curated set of TanStack Query re-exports. Use these alongside [State hooks](/v0-2-0/reference/app-toolkit/hooks/state) to wire app concerns (permissions, devices, viewport, formatting) without depending on MDK styled components. ## At a glance | Sub-group | Hooks | |---|---| | [Permissions](#permissions) | `useCheckPerm`, `useHasPerms`, `useIsFeatureEditingEnabled` | | [Generic React](#generic-react) | `useLocalStorage`, `useKeyDown`, `useWindowSize`, `usePlatform`, `useDeviceResolution`, `useBeepSound`, `usePagination`, `useSubtractedTime`, `useTimezoneFormatter` | | [Device and IP](#device-and-ip) | `usePduViewer` | | [Domain transforms](#domain-transforms) | `useCostSummary`, `useHashBalance`, `useSubsidyFees`, `useUpdateExistedActions` | | [TanStack Query re-exports](#tanstack-query-re-exports) | `useQuery`, `useMutation`, `useQueries`, `useInfiniteQuery`, `useIsFetching`, `useIsMutating`, `useQueryClient` | ## Prerequisites - [Wrap your app in ``](/v0-2-0/ui/react/quickstart#wrap-your-app-in-mdkprovider) - Complete the [@tetherto/mdk-react-devkit installation and add the dependency](/v0-2-0/ui/react/foundation#prerequisites) ## Import ```tsx useBeepSound, useCheckPerm, useDeviceResolution, useHasPerms, useIsFeatureEditingEnabled, useKeyDown, useLocalStorage, usePagination, usePduViewer, usePlatform, useSubtractedTime, useTimezoneFormatter, useWindowSize, } from '@tetherto/mdk-react-adapter' useCostSummary, useHashBalance, useSubsidyFees, useUpdateExistedActions, } from '@tetherto/mdk-react-devkit/foundation' ``` ## Permissions ### `useCheckPerm` @tetherto/mdk-react-adapter Check if the current user has a specific permission. Reads `permissions` from the adapter [`authStore`](/v0-2-0/reference/app-toolkit/hooks/state#useauth). Prefer this over `useHasPerms` for single-permission gates. ```tsx ``` #### Example ```tsx function EditDevicesButton() { const canEdit = useCheckPerm('devices:edit') return canEdit ? : null } ``` ### `useHasPerms` @tetherto/mdk-react-adapter Returns a callable that accepts a permission request β€” a string, a string array (first match wins), or a check object. Reads from the adapter [`authStore`](/v0-2-0/reference/app-toolkit/hooks/state#useauth). ```tsx ``` #### Example ```tsx function ContextMenu({ device }) { const hasPerms = useHasPerms() return ( {hasPerms(['devices:edit', 'devices:admin']) && Edit} {hasPerms('devices:delete') && Delete} ) } ``` ### `useIsFeatureEditingEnabled` @tetherto/mdk-react-adapter Returns whether the current user has the `features` capability to edit feature flags. ```tsx ``` #### Example ```tsx function FeatureFlagRow({ flag }) { const canEdit = useIsFeatureEditingEnabled() return } ``` ## Generic React ### `useLocalStorage` @tetherto/mdk-react-adapter Type-safe `localStorage` access with cross-tab synchronization. Returns `[value, setValue, removeValue]` and stays in sync across browser tabs via the `storage` event. ```tsx ``` #### Example ```tsx function ThemeToggle() { const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('app:theme', 'light') return } ``` ### `useKeyDown` @tetherto/mdk-react-adapter Track whether a specific keyboard key is currently pressed. Attaches global `keydown`/`keyup` listeners. Useful for modifier-key interactions like shift-click multi-select. ```tsx ``` #### Example ```tsx function MinerGrid({ miners }) { const shiftHeld = useKeyDown('Shift') return miners.map((m) => ( select(m, { range: shiftHeld })} /> )) } ``` ### `useWindowSize` @tetherto/mdk-react-adapter Track window width and height, refreshing on `resize`. Returns `{ windowWidth, windowHeight }`. Use [`useDeviceResolution`](#usedeviceresolution) when you only need a device-class branch rather than raw pixels. ```tsx ``` #### Example ```tsx function ResponsiveChart() { const { windowWidth } = useWindowSize() return } ``` ### `usePlatform` @tetherto/mdk-react-adapter Detect the host platform (iOS, Android, Mac, Windows, Linux) from the user agent. Returns the value matching the exported `OS_TYPES` constant; pair with `detectPlatform` for one-off checks outside React. ```tsx ``` #### Example ```tsx function PlatformBadge() { const platform = usePlatform() return Running on {platform} } ``` ### `useDeviceResolution` @tetherto/mdk-react-adapter Map window width to a device class (`mobile`, `tablet`, `desktop`) using the shared `BREAKPOINTS` constant. Cheaper than re-reading pixels in every render. ```tsx ``` #### Example ```tsx function Layout({ children }) { const device = useDeviceResolution() return device === 'mobile' ? {children} : {children} } ``` ### `useBeepSound` @tetherto/mdk-react-adapter Play a repeating beep when `isAllowed` is true. Configurable volume and interval (`delayMs`). The alarm is synthesised via the Web Audio API β€” no audio asset is bundled or fetched. Designed for audible critical alerts (overheating containers, equipment failure). ```tsx ``` #### Example ```tsx function CriticalAlertChime({ active }) { useBeepSound({ isAllowed: active, volume: 0.6, delayMs: 1500 }) return null } ``` ### `usePagination` @tetherto/mdk-react-adapter Manage pagination state and produce `{ limit, offset }` query arguments for API calls. Returns state shaped for the devkit `` component plus helpers to change page, page size, and total count. ```tsx ``` #### Options | Option | Status | Type | Default | Description | |--------|--------|------|---------|-------------| | `current` | Optional | `number` | `1` | Initial 1-indexed page | | `pageSize` | Optional | `number` | `20` | Initial rows per page | | `total` | Optional | `number` | none | Initial total row count | | `showSizeChanger` | Optional | `boolean` | `true` | Whether the UI exposes a page-size selector | #### Returns | Member | Type | Description | |--------|------|-------------| | `pagination` | `PaginationState` | `{ current, pageSize, showSizeChanger, total }` β€” spread onto `` | | `queryArgs` | `{ limit, offset }` | Query arguments for API calls | | `handleChange` | `function` | `(page, pageSize) => void` β€” pass to `` | | `setPagination` | `function` | Imperative pagination state update | | `reset` | `function` | Reset to initial state | | `setTotal` | `function` | Update total row count | | `hideNextPage` | `function` | Hide next page when the current page has fewer rows than `pageSize` | #### Example ```tsx function MinerList() { const { pagination, queryArgs, handleChange } = usePagination({ current: 1, pageSize: 25 }) const { data } = useQuery(['miners', queryArgs], () => fetchMiners(queryArgs)) return ( <> ) } ``` ### `useSubtractedTime` @tetherto/mdk-react-adapter Returns `Date.now() - diff`, refreshing on a fixed interval (default 5s). Useful for "synced N seconds ago" labels without forcing tree-wide re-renders. ```tsx ``` #### Example ```tsx function LastSyncedLabel({ lastSyncOffsetMs }) { const now = useSubtractedTime(lastSyncOffsetMs) return Synced {formatDistanceToNow(now)} ago } ``` ### `useTimezoneFormatter` @tetherto/mdk-react-adapter Read the app timezone from the adapter [`timezoneStore`](/v0-2-0/reference/app-toolkit/hooks/state#usetimezone) and format dates for display. Use when foundation components or app code need timestamps in the operator-selected timezone (alerts, pool manager, dashboard widgets). ```tsx ``` #### Example ```tsx function AlertTimestamp({ raisedAt }) { const { getFormattedDate } = useTimezoneFormatter() return } ``` ## Device and IP ### `usePduViewer` @tetherto/mdk-react-adapter Pan/zoom controller for the PDU floor-plan viewer. Wraps `react-zoom-pan-pinch` with viewport-aware reset logic and a debounced "back to content" indicator that appears when the user pans the layout off-screen. ```tsx ``` #### Example ```tsx function PduFloorPlan() { const { onViewerInit, showBackToContent, handleBackToContent } = usePduViewer() return ( <> {/* …diagram… */} {showBackToContent && } ) } ``` ## Domain transforms ### `useCostSummary` @tetherto/mdk-react-devkit/foundation Base hook for the cost-summary reporting page (single-site mode). ```tsx ``` #### Example ```tsx // Wire your query result in; consume queryParams to drive the fetch function CostSummaryPage({ query }) { const { datePicker, queryParams, isLoading, error } = useCostSummary({ query }) // Pass queryParams to your data-fetching layer whenever the date range changes // e.g. useGetCostSummaryQuery(queryParams, { skip: !queryParams }) return (
{datePicker} {isLoading &&

Loading…

} {error &&

Failed to load cost data

}
) } ``` ### `useHashBalance` @tetherto/mdk-react-devkit/foundation Derives hash-balance metrics and chart datasets from finance log entries for the active date range, currency, and timeframe type. Used by hash balance panels. ```tsx ``` #### Example ```tsx function HashBalancePanel({ data, log, currency, dateRange }) { const { siteHashRevenueChartData, networkHashpriceChartData, combinedCostChartData, } = useHashBalance({ data, log, currency, dateRange }) return (
{/* Pass chart datasets to your BarChart components */}
{JSON.stringify(siteHashRevenueChartData?.labels, null, 2)}
{JSON.stringify(networkHashpriceChartData?.labels, null, 2)}
{JSON.stringify(combinedCostChartData?.labels, null, 2)}
) } ``` ### `useSubsidyFees` @tetherto/mdk-react-devkit/foundation Aggregates raw subsidy-fee log entries into chart-ready datasets keyed by the active period type (day / week / month / year) and surfaces a summary for the matching reporting widgets. Used by `Subsid… ```tsx ``` #### Example ```tsx function SubsidyFeesPanel({ data, dateRange }) { const { summary, subsidyFeesChartData, averageFeesChartData, isEmpty } = useSubsidyFees({ data, dateRange, }) if (isEmpty) return

No subsidy fee data for this period.

return (

Total fees: {(summary as any)?.total ?? 'β€”'}

{/* Pass chart datasets to your BarChart components */}
{JSON.stringify(subsidyFeesChartData?.labels, null, 2)}
{JSON.stringify(averageFeesChartData?.labels, null, 2)}
) } ``` ### `useUpdateExistedActions` @tetherto/mdk-react-devkit/foundation Mutation hook that updates only the changed fields of an existing action record. ```tsx ``` #### Example ```tsx function DeviceActionBar({ actionType, pendingSubmissions, selectedDevices }) { const { updateExistedActions } = useUpdateExistedActions() const handleApply = () => { updateExistedActions({ actionType, pendingSubmissions, selectedDevices }) } return ( ) } ``` ## TanStack Query re-exports The adapter re-exports a curated set of [TanStack Query](https://tanstack.com/query/latest/docs/framework/react/overview) hooks so you can import data-fetching primitives from a single package alongside MDK helpers. The re-exports are unmodified β€” refer to the upstream TanStack Query documentation for full API details. | Hook | TanStack docs | | --- | --- | | `useQuery` | [TanStack `useQuery`](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery) | | `useMutation` | [TanStack `useMutation`](https://tanstack.com/query/latest/docs/framework/react/reference/useMutation) | | `useQueries` | [TanStack `useQueries`](https://tanstack.com/query/latest/docs/framework/react/reference/useQueries) | | `useInfiniteQuery` | [TanStack `useInfiniteQuery`](https://tanstack.com/query/latest/docs/framework/react/reference/useInfiniteQuery) | | `useIsFetching` | [TanStack `useIsFetching`](https://tanstack.com/query/latest/docs/framework/react/reference/useIsFetching) | | `useIsMutating` | [TanStack `useIsMutating`](https://tanstack.com/query/latest/docs/framework/react/reference/useIsMutating) | | `useQueryClient` | [TanStack `useQueryClient`](https://tanstack.com/query/latest/docs/framework/react/reference/useQueryClient) | # UI CLI reference (/v0-2-0/reference/app-toolkit/ui-cli) The **UI CLI** (`mdk-ui`, package `@tetherto/mdk-ui-cli`) is the command surface your AI agent uses to build with MDK. You usually never run it yourself, your agent does, after you [wire your IDE](/v0-2-0/agents). This page documents the commands for when you want to drive or inspect the tooling by hand. Every command runs locally and prints JSON by default. Add `--format table` for human-readable output. There are no network or model calls: each command is a lookup against files MDK ships. ## At a glance | Bucket | Section | What it covers | |--------|---------|----------------| | Set up | [Set up a project](#set-up-a-project) | Wire your IDE with `init` | | Discover | [Discover what to use](#discover-what-to-use) | `suggest`, `hooks`, `stores`, `find` | | Read | [Read a component contract](#read-a-component-contract) | `docs`, `example` | | Recipes | [Follow a recipe](#follow-a-recipe) | `blueprints`, `blueprint` | | Scaffold | [Scaffold and verify](#scaffold-and-verify) | `add page`, `check`, `sync` | | Inspect | [Inspect the UI CLI itself](#inspect-the-ui-cli-itself) | `--json-help` | ## All commands | Command | Summary | |---------|---------| | [`init`](#init) | Bootstrap `.mdk/context.md` and IDE rules | | [`suggest`](#suggest) | Ranked shortlist from free-text intent | | [`hooks`](#hooks) | List adapter hooks (optional `--category`) | | [`stores`](#stores) | List Zustand stores and query helpers | | [`find`](#find) | Filter components by domain and capability | | [`docs`](#docs) | Print a component's `USAGE.md` | | [`example`](#example) | Print a runnable `*.example.tsx` | | [`blueprints`](#blueprints) | List curated intent-to-component recipes | | [`blueprint`](#blueprint) | Show one recipe in detail | | [`add page`](#add-page) | Scaffold a page with chosen components | | [`check`](#check) | Type-check a file against real APIs | | [`sync`](#sync) | Refresh `.mdk/context.md` | | [`--json-help`](#json-help) | Machine-readable CLI surface | ## The agent's decision flow Given an intent, the deterministic path a session follows is: ```mermaid flowchart TD intent["Plain-language intent"] suggest["mdk-ui suggest"] state{"State or hooks needed?"} hooks["mdk-ui hooks / stores"] blueprints["mdk-ui blueprints"] match{"Matching blueprint?"} blueprint["mdk-ui blueprint"] find["mdk-ui find"] docs["mdk-ui docs / example"] add["mdk-ui add page"] check["mdk-ui check"] intent --> suggest suggest --> state state -->|yes| hooks state -->|no| blueprints hooks --> add blueprints --> match match -->|yes| blueprint match -->|no| find blueprint --> docs find --> docs docs --> add add --> check ``` ## Set up a project ### init Bootstraps the current project with an agent-context file and an IDE rule so every AI session is wired automatically: ```bash npx @tetherto/mdk-ui-cli init --ide cursor # .mdk/context.md + .cursor/rules/mdk.mdc npx @tetherto/mdk-ui-cli init --ide claude # .mdk/context.md + CLAUDE.md ``` ## Discover what to use ### suggest Turns free text into a ranked shortlist across components, hooks, blueprints, and stores: ```bash mdk-ui suggest "show hashrate for a pool" ``` ### hooks Lists every hook exported from `@tetherto/mdk-react-adapter`, grouped by category (`store`, `utility`, `permission`, `ui`, `external`): ```bash mdk-ui hooks --format table # all adapter hooks mdk-ui hooks --category store --format table # store-binding hooks only ``` ### stores Describes the Zustand stores and TanStack Query helpers from `@tetherto/mdk-ui-core`: ```bash mdk-ui stores --format table # stores and query helpers mdk-ui stores --category devices --format table ``` ### find Filters the component library by domain and capability: ```bash mdk-ui find --domain mining-operations --capability hashrate-monitoring ``` ## Read a component contract ### docs Prints a component's usage notes: ```bash mdk-ui docs LineChartCard ``` ### example Prints a runnable example: ```bash mdk-ui example LineChartCard ``` ## Follow a recipe Blueprints are curated recipes that map a high-level intent to a concrete set of components and hooks. ### blueprints Lists available recipes: ```bash mdk-ui blueprints ``` ### blueprint Shows one recipe in detail: ```bash mdk-ui blueprint device-management ``` ## Scaffold and verify ### add page Scaffolds a page with the components you name: ```bash mdk-ui add page Dashboard --component LineChartCard ``` ### check Confirms a file compiles against the real component APIs: ```bash mdk-ui check src/pages/Dashboard.tsx ``` ### sync Keeps the `.mdk/context.md` agent-context file current as MDK updates: ```bash mdk-ui sync ``` ## Inspect the UI CLI itself ### json-help `--json-help` prints the full command surface, useful for meta-tooling that wants to discover commands without running them: ```bash mdk-ui --json-help ``` ## Next steps - [Build dashboards with your AI agent](/v0-2-0/agents): the two-step flow most developers use - [UI Kit](/v0-2-0/ui): the component library these commands draw from - [MDK repositories](/v0-2-0/resources/repositories): source for the UI CLI and the agent-ready contract # UI Core (/v0-2-0/reference/app-toolkit/ui-core) @tetherto/mdk-ui-core If you are building a React app with the MDK kit, start with the [React adapter](/v0-2-0/ui/react/get-started) instead. `` and adapter hooks such as `useAuth` and `useDevices` wrap this package so most React code never imports `@tetherto/mdk-ui-core` directly. Use this reference when you need headless store access outside React (logging, websocket setup, test helpers) or when authoring a future framework adapter. `@tetherto/mdk-ui-core` is the framework-agnostic headless layer of the [MDK App Toolkit](/v0-2-0/concepts/architecture/app-toolkit). It ships Zustand vanilla stores and a TanStack Query Core `QueryClient` factory. There are **no React imports** in this package. ## Subpath exports | Subpath | Purpose | |---------|---------| | `.` | Top-level barrel | | `./store` | Zustand vanilla stores | | `./query` | `QueryClient` factory, query keys, and query/mutation factories | | `./types` | Shared type contracts | | `./stores.json` | Machine-readable store manifest (generated at build time) | ## Stores Each store is a Zustand vanilla singleton. In a React app, read and update state through the matching adapter hook instead of importing stores directly. | Store | Summary | Adapter hook | |-------|---------|--------------| | `authStore` | Session token and permission payload | [`useAuth`](/v0-2-0/reference/app-toolkit/hooks/state#useauth) | | `devicesStore` | Fleet device list and current selection | [`useDevices`](/v0-2-0/reference/app-toolkit/hooks/state#usedevices) | | `timezoneStore` | Active operator timezone | [`useTimezone`](/v0-2-0/reference/app-toolkit/hooks/state#usetimezone) | | `notificationStore` | Unread notification counter (`count`, `increment`, `decrement`, `reset`) | [`useNotifications`](/v0-2-0/reference/app-toolkit/hooks/state#usenotifications) | | `actionsStore` | Pending device and pool action submissions | [`useActions`](/v0-2-0/reference/app-toolkit/hooks/state#useactions) | Import from `@tetherto/mdk-ui-core/store` (or the top-level barrel). ## QueryClient factory `createMdkQueryClient` builds a TanStack Query Core client with environment-aware App Node base URL resolution: 1. Explicit override (typically from ``) 2. Build-time env: `VITE_MDK_API_URL` (Vite) or `MDK_API_URL` (Node) 3. Default: `http://localhost:3000` The `./query` subpath also exports query key helpers and factories (`authQuery`, `devicesQuery`, `deviceQuery`, `telemetryQuery`). See [TanStack Query](https://tanstack.com/query/latest) for general usage. ## Headless read outside React Utility code can subscribe to a store without React: ```ts const token = authStore.getState().token const unsubscribe = authStore.subscribe((state) => { console.log('token changed', state.token) }) // later: unsubscribe() ``` `` wires these singleton stores into React and creates the shared `QueryClient` for adapter hooks. ## What's not here yet Throttled telemetry subscriptions, stale detection, and history ring buffers are not yet present. They will be added alongside the consuming code that requires them. ## Next steps - [React get started](/v0-2-0/ui/react/get-started): three-package install and `` - [Wire a React app](/v0-2-0/tutorials/ui/react/tutorial): full adapter wiring walkthrough - [MDK App Toolkit architecture](/v0-2-0/concepts/architecture/app-toolkit): where UI Core fits in the frontend stack # UI Kit (/v0-2-0/reference/app-toolkit/ui-kit) The UI Kit is the frontend tools half of the [MDK App Toolkit](/v0-2-0/concepts/architecture/app-toolkit). This reference documents its constants, React hooks, TypeScript types, and helper utilities. ## Browse by topic | Topic | What's there | |-------|--------------| | [Constants](/v0-2-0/reference/app-toolkit/ui-kit/constants) | Colors, units, currency, chart configs and permissions, roles, header preferences| | [Hooks](/v0-2-0/reference/app-toolkit/ui-kit/hooks) | Discover hooks for monitoring and UI patterns; full reference at [Hooks](/v0-2-0/reference/app-toolkit/hooks) | | [Types](/v0-2-0/reference/app-toolkit/ui-kit/types) | TypeScript type exports. UI primitives and domain models like `Device`, `Alert` | | [Utilities](/v0-2-0/reference/app-toolkit/ui-kit/utilities) | Helper functions for formatting, validation, conversions and settings persistence | ## Browse by package If you're working with a specific package, use these per-package shortcuts: ### `@tetherto/mdk-react-devkit/core` - [Constants](/v0-2-0/reference/app-toolkit/ui-kit/constants#core-constants) - [Types](/v0-2-0/reference/app-toolkit/ui-kit/types#core-types) - [Utilities](/v0-2-0/reference/app-toolkit/ui-kit/utilities#core-utilities) ### `@tetherto/mdk-react-devkit/foundation` - [Constants](/v0-2-0/reference/app-toolkit/ui-kit/constants#foundation-constants) - [Types](/v0-2-0/reference/app-toolkit/ui-kit/types#foundation-types) - [Utilities](/v0-2-0/reference/app-toolkit/ui-kit/utilities#foundation-utilities) - [Hooks](/v0-2-0/reference/app-toolkit/hooks) β€” every App Toolkit hook (state, components, utilities) lives on a single overview page now. # Constants (/v0-2-0/reference/app-toolkit/ui-kit/constants) This page documents constants exported by the MDK packages. Constants live in two distinct domains: - [Core constants](#core-constants) ships UI primitives: colors, units, currency symbols, chart defaults - [Foundation constants](#foundation-constants) ships mining-domain values: permissions, roles, header preferences, error codes ## Core constants UI primitive constants exported by `@tetherto/mdk-react-devkit/core` β€” colors, units, currency symbols, and chart defaults. ### Prerequisites - Complete the [@tetherto/mdk-react-devkit installation and add the dependency](/v0-2-0/ui/react/core#prerequisites) ### Import @tetherto/mdk-react-devkit/core ```tsx COLOR, UNITS, CURRENCY, CHART_COLORS, TABLE_COLORS, HASHRATE_LABEL_DIVISOR, } from '@tetherto/mdk-react-devkit/core' ``` ### Color constants #### `COLOR` @tetherto/mdk-react-devkit/core Comprehensive color palette with 80+ named colors. ```tsx COLOR.GREEN // '#72F59E' COLOR.RED // '#EF4444' COLOR.COLD_ORANGE // '#F7931A' ``` ##### Base colors | Constant | Value | Description | |----------|-------|-------------| | `WHITE` | `#FFFFFF` | Pure white | | `BLACK` | `#17130F` | Standard black | | `DARK_BACK` | `#1A1815` | Dark background | | `EBONY` | `#0f0f0f` | Chart background | | `TRANSPARENT` | `transparent` | Transparent | ##### Status colors | Constant | Value | Description | |----------|-------|-------------| | `GREEN` | `#72F59E` | Success/online | | `RED` | `#EF4444` | Error/danger | | `YELLOW` | `#FFC107` | Warning | | `BRIGHT_YELLOW` | `#EAB308` | Bright warning | | `LIGHT_BLUE` | `#22AFFF` | Info | | `SLEEP_BLUE` | `#3B82F6` | Sleep/standby | ##### Brand colors | Constant | Value | Description | |----------|-------|-------------| | `COLD_ORANGE` | `#F7931A` | Bitcoin orange | | `ORANGE` | `#FF6A00` | Primary orange | | `EMERALD` | `#009393` | Teal accent | | `INDIGO` | `#5B5FFB` | Purple accent | #### `TABLE_COLORS` @tetherto/mdk-react-devkit/core Colors for table styling. ```tsx TABLE_COLORS.HEADER_BG TABLE_COLORS.ROW_HOVER ``` #### `HEATMAP` @tetherto/mdk-react-devkit/core `HEATMAP` color scale for temperature and intensity displays. ```tsx ``` #### `CHART_COLORS` @tetherto/mdk-react-devkit/core Default chart color palette. ```tsx ``` #### `PIE_CHART_COLORS` @tetherto/mdk-react-devkit/core Color palette for pie and doughnut charts. ```tsx ``` #### `CATEGORICAL_COLORS` @tetherto/mdk-react-devkit/core 25-color categorical palette for multi-series charts. ```tsx CATEGORICAL_COLORS[0] // First color CATEGORICAL_COLORS[24] // Last color ``` #### `TEMPERATURE_COLORS` @tetherto/mdk-react-devkit/core Color scale for temperature displays. ```tsx ``` #### `SOCKET_BORDER_COLOR` @tetherto/mdk-react-devkit/core Colors for socket status indicators. ```tsx ``` ### Unit constants #### `UNITS` @tetherto/mdk-react-devkit/core Physical and measurement units. ```tsx UNITS.POWER_W // 'W' UNITS.POWER_KW // 'kW' UNITS.ENERGY_MWH // 'MWh' UNITS.TEMPERATURE_C // 'Β°C' UNITS.HASHRATE_TH_S // 'TH/s' ``` | Constant | Value | Description | |----------|-------|-------------| | `POWER_W` | `W` | Watts | | `POWER_KW` | `kW` | Kilowatts | | `ENERGY_WH` | `Wh` | Watt-hours | | `ENERGY_KWH` | `kWh` | Kilowatt-hours | | `ENERGY_MW` | `MW` | Megawatts | | `ENERGY_MWH` | `MWh` | Megawatt-hours | | `ENERGY_GWH` | `GWh` | Gigawatt-hours | | `TEMPERATURE_C` | `Β°C` | Celsius | | `VOLTAGE_V` | `V` | Volts | | `AMPERE` | `A` | Amperes | | `PERCENT` | `%` | Percentage | | `PRESSURE_BAR` | `bar` | Pressure (bar) | | `HASHRATE_MH_S` | `MH/s` | Megahash/second | | `HASHRATE_TH_S` | `TH/s` | Terahash/second | | `HASHRATE_PH_S` | `PH/s` | Petahash/second | | `HASHRATE_EH_S` | `EH/s` | Exahash/second | | `FREQUENCY_MHZ` | `MHz` | Megahertz | | `FREQUENCY_HERTZ` | `Hz` | Hertz | | `HUMIDITY_PERCENT` | `%RH` | Relative humidity | | `EFFICIENCY_W_PER_TH` | `W/TH` | Watts per terahash | | `FLOW_M3H` | `m3/h` | Flow rate | | `SATS` | `Sats` | Satoshis | | `VBYTE` | `vByte` | Virtual bytes | #### `CURRENCY` @tetherto/mdk-react-devkit/core Currency symbols and labels. ```tsx CURRENCY.BTC // 'β‚Ώ' CURRENCY.USD // '$' CURRENCY.EUR // '€' CURRENCY.SATS // 'Sats' CURRENCY.BTC_LABEL // 'BTC' CURRENCY.USD_LABEL // 'USD' ``` #### `MAX_UNIT_VALUE` @tetherto/mdk-react-devkit/core Maximum values for certain units. ```tsx MAX_UNIT_VALUE.HUMIDITY_PERCENT // 100 MAX_UNIT_VALUE.TEMPERATURE_PERCENT // 100 ``` #### `HASHRATE_LABEL_DIVISOR` @tetherto/mdk-react-devkit/core Divisors for converting hashrate units. ```tsx HASHRATE_LABEL_DIVISOR['TH/s'] // 1e6 HASHRATE_LABEL_DIVISOR['PH/s'] // 1e9 HASHRATE_LABEL_DIVISOR['EH/s'] // 1e12 ``` ### Chart constants #### `defaultChartColors` @tetherto/mdk-react-devkit/core Default color array for chart datasets. ```tsx ``` #### `defaultChartOptions` @tetherto/mdk-react-devkit/core Default [Chart.js](https://www.chartjs.org/) options. ```tsx ``` #### `CHART_LEGEND_OPACITY` @tetherto/mdk-react-devkit/core Opacity values for chart legends. ```tsx ``` #### `CHART_PERFORMANCE` @tetherto/mdk-react-devkit/core Performance threshold constants for charts. ```tsx ``` #### `getChartAnimationConfig` @tetherto/mdk-react-devkit/core Get animation configuration based on data count. ```tsx const animConfig = getChartAnimationConfig(dataPointCount) ``` #### `getDataDecimationConfig` @tetherto/mdk-react-devkit/core Get data decimation configuration for large datasets. ```tsx const decimationConfig = getDataDecimationConfig(dataPointCount) ``` ### Type exports The constants module also exports TypeScript types: ```tsx UnitKey, UnitValue, CurrencyKey, CurrencyValue, } from '@tetherto/mdk-react-devkit/core' ``` ## Foundation constants Constants exported by `@tetherto/mdk-react-devkit/foundation`: app identity, dialog flows, header preferences, permissions, roles, and error codes. ### Prerequisites - Complete the [@tetherto/mdk-react-devkit installation and add the dependency](/v0-2-0/ui/react/foundation#prerequisites) ### Import @tetherto/mdk-react-devkit/foundation ```tsx WEBAPP_NAME, WEBAPP_SHORT_NAME, WEBAPP_DISPLAY_NAME, POSITION_CHANGE_DIALOG_FLOWS, HEADER_ITEMS, DEFAULT_HEADER_PREFERENCES, AUTH_PERMISSIONS, AUTH_LEVELS, USER_ROLE, USER_ROLES, PERM_LEVEL_LABELS, getRoleBadgeColors, } from '@tetherto/mdk-react-devkit/foundation' ``` ### App identity Brand strings the foundation UI uses to label the running web app in header rows, chart legends, settings export errors, and confirmation copy. The kit ships placeholder defaults; consumers read them via the named imports. #### `WEBAPP_NAME` @tetherto/mdk-react-devkit/foundation Inline name used in confirmations and the `parseSettingsFile` import-error message. ```tsx WEBAPP_NAME // 'Appl.' ``` #### `WEBAPP_SHORT_NAME` @tetherto/mdk-react-devkit/foundation Compact label embedded in [`HEADER_ITEMS`](#header_items) for the in-app miner and hashrate header rows. ```tsx WEBAPP_SHORT_NAME // 'APP' ``` #### `WEBAPP_DISPLAY_NAME` @tetherto/mdk-react-devkit/foundation Display name used in chart legends, including the hash-rate line chart series label. ```tsx WEBAPP_DISPLAY_NAME // 'Application' ``` ### Dialog flows #### `POSITION_CHANGE_DIALOG_FLOWS` @tetherto/mdk-react-devkit/foundation Flow keys consumed by [`PositionChangeDialog`](/v0-2-0/ui/react/foundation/operations/details-view/fleet-management#positionchangedialog) and its sibling dialogs to route between step-specific surfaces. ```tsx POSITION_CHANGE_DIALOG_FLOWS.MAINTENANCE // 'maintenance' POSITION_CHANGE_DIALOG_FLOWS.REPLACE_MINER // 'replaceMiner' ``` | Constant | Value | Description | |----------|-------|-------------| | `CONFIRM_REMOVE` | `remove` | Render the remove-miner confirmation surface | | `CHANGE_INFO` | `changeInfo` | Edit info for an existing miner | | `MAINTENANCE` | `maintenance` | Move a miner to (or back from) the maintenance container | | `REPLACE_MINER` | `replaceMiner` | Replace the miner currently occupying a socket | | `CONFIRM_CHANGE_POSITION` | `confirmChange` | Confirm a position change between sockets | | `CONTAINER_SELECTION` | `containerSelection` | Pick a destination container and socket | #### `PositionChangeDialogFlowKey` type @tetherto/mdk-react-devkit/foundation ```tsx type PositionChangeDialogFlowKey = keyof typeof POSITION_CHANGE_DIALOG_FLOWS ``` #### `PositionChangeDialogFlowValue` type @tetherto/mdk-react-devkit/foundation ```tsx type PositionChangeDialogFlowValue = (typeof POSITION_CHANGE_DIALOG_FLOWS)[PositionChangeDialogFlowKey] ``` ### Header controls #### `HEADER_ITEMS` @tetherto/mdk-react-devkit/foundation Array of header metric options for the [`HeaderControlsSettings`](/v0-2-0/ui/react/foundation/settings/header-controls) component. ```tsx ``` | Key | Label | |-----|-------| | `poolMiners` | `Pool Miners` | | `miners` | `` `${WEBAPP_SHORT_NAME} Miners` `` | | `poolHashrate` | `Pool Hashrate` | | `hashrate` | `` `${WEBAPP_SHORT_NAME} Hashrate` `` | | `consumption` | `Consumption` | | `efficiency` | `Efficiency` | The two in-app rows compose their labels from [`WEBAPP_SHORT_NAME`](#webapp_short_name); with the shipped default that renders as `APP Miners` and `APP Hashrate`. #### `DEFAULT_HEADER_PREFERENCES` @tetherto/mdk-react-devkit/foundation Default visibility state for all header items (all `true`). ```tsx DEFAULT_HEADER_PREFERENCES.poolMiners // true DEFAULT_HEADER_PREFERENCES.consumption // true ``` #### `HeaderPreferences` type @tetherto/mdk-react-devkit/foundation ```tsx type HeaderPreferences = { poolMiners: boolean miners: boolean poolHashrate: boolean hashrate: boolean consumption: boolean efficiency: boolean } ``` ### Permissions #### `AUTH_PERMISSIONS` @tetherto/mdk-react-devkit/foundation Permission resource identifiers. ```tsx AUTH_PERMISSIONS.USERS // 'users' AUTH_PERMISSIONS.SETTINGS // 'settings' AUTH_PERMISSIONS.MINER // 'miner' ``` | Constant | Value | Description | |----------|-------|-------------| | `USERS` | `users` | User management | | `SETTINGS` | `settings` | Application settings | | `MINER` | `miner` | Miner operations | | `ALERTS` | `alerts` | Alert management | | `ACTIONS` | `actions` | Action execution | | `EXPLORER` | `explorer` | Device explorer | | `INVENTORY` | `inventory` | Inventory management | | `CONTAINER` | `container` | Container management | | `PRODUCTION` | `production` | Production data | | `REPORTING` | `reporting` | Reports | #### `AUTH_LEVELS` @tetherto/mdk-react-devkit/foundation Permission access levels. ```tsx AUTH_LEVELS.READ // 'r' AUTH_LEVELS.WRITE // 'w' ``` #### `USER_ROLE` @tetherto/mdk-react-devkit/foundation User role identifiers. ```tsx USER_ROLE.ADMIN // 'admin' USER_ROLE.SITE_MANAGER // 'site_manager' USER_ROLE.READ_ONLY // 'read_only_user' ``` | Constant | Value | |----------|-------| | `ADMIN` | `admin` | | `SITE_MANAGER` | `site_manager` | | `SITE_OPERATOR` | `site_operator` | | `FIELD_OPERATOR` | `field_operator` | | `REPAIR_TECHNICIAN` | `repair_technician` | | `REPORTING_TOOL_MANAGER` | `reporting_tool_manager` | | `READ_ONLY` | `read_only_user` | ### Settings #### `USER_ROLES` @tetherto/mdk-react-devkit/foundation Array of role options for select dropdowns. ```tsx // [{ label: 'Admin', value: 'admin' }, ...] ``` #### `PERM_LEVEL_LABELS` @tetherto/mdk-react-devkit/foundation Human-readable labels for permission levels. ```tsx PERM_LEVEL_LABELS.rw // 'Read & Write' PERM_LEVEL_LABELS.r // 'Read Only' PERM_LEVEL_LABELS.none // 'No Access' ``` #### `SETTINGS_ERROR_CODES` @tetherto/mdk-react-devkit/foundation Error code to message mapping. ```tsx SETTINGS_ERROR_CODES.ERR_USER_EXISTS // 'User already exists' SETTINGS_ERROR_CODES.DEFAULT // 'An error occurred' ``` ### Role styling #### `getRoleBadgeColors` @tetherto/mdk-react-devkit/foundation Get badge colors for a role. ```tsx const { color, bgColor } = getRoleBadgeColors('admin') // { color: '#e8833a', bgColor: 'rgba(232, 131, 58, 0.1)' } ``` | Role | Color | Background | |------|-------|------------| | `admin` | `#e8833a` | Orange tint | | `site_manager` | `#52c41a` | Green tint | | `site_operator` | `#faad14` | Yellow tint | | `read_only_user` | `#8c8c8c` | Gray tint | # Hooks (/v0-2-0/reference/app-toolkit/ui-kit/hooks) Hooks shipped by `@tetherto/mdk-react-devkit`. For the full catalog β€” including adapter data hooks and state hooks from `@tetherto/mdk-react-adapter` β€” see the [Hooks reference](/v0-2-0/reference/app-toolkit/hooks). Use the groups below to browse by concern; each hook name links to its entry in that reference. ## Available hooks | Group | Hooks | |---|---| | [Monitoring](/v0-2-0/reference/app-toolkit/hooks) | [`useChartDataCheck`](/v0-2-0/reference/app-toolkit/hooks/components#usechartdatacheck), [`useBeepSound`](/v0-2-0/reference/app-toolkit/hooks/utilities#usebeepsound), [`useEnergyReportSite`](/v0-2-0/reference/app-toolkit/hooks/components#useenergyreportsite), [`useHashrate`](/v0-2-0/reference/app-toolkit/hooks/components#usehashrate) | | [UI](/v0-2-0/reference/app-toolkit/hooks) | [`useNotification`](/v0-2-0/reference/app-toolkit/hooks/components#usenotification), [`useHeaderControls`](/v0-2-0/reference/app-toolkit/hooks/components#useheadercontrols), [`useSidebarExpandedState`](/v0-2-0/reference/app-toolkit/hooks/components#usesidebarexpandedstate), [`useSidebarSectionState`](/v0-2-0/reference/app-toolkit/hooks/components#usesidebarsectionstate), [`useHasPerms`](/v0-2-0/reference/app-toolkit/hooks/utilities#usehasperms), [`useCheckPerm`](/v0-2-0/reference/app-toolkit/hooks/utilities#usecheckperm), [`useLocalStorage`](/v0-2-0/reference/app-toolkit/hooks/utilities#uselocalstorage), [`usePagination`](/v0-2-0/reference/app-toolkit/hooks/utilities#usepagination), [`useIsFeatureEditingEnabled`](/v0-2-0/reference/app-toolkit/hooks/utilities#useisfeatureeditingenabled) | # Types (/v0-2-0/reference/app-toolkit/ui-kit/types) This page documents the TypeScript types exported by the MDK packages. The two packages cover different territory: - [Core types](#core-types) ships **UI primitive types**: sizes, variants, colors, status, common API and chart shapes. These are the building blocks consumed by core components and re-used in your own component prop types. - [Foundation types](#foundation-types) ships **mining-domain models**: `Device`, `Container`, `Alert`, `MinerStats`, settings shapes. These describe the data flowing through foundation components and the API responses they consume. ## Core types UI primitive types exported by `@tetherto/mdk-react-devkit/core` β€” sizes, variants, colors, status, and common API and chart shapes. These are the building blocks consumed by core components and re-used in your own component prop types. ### Prerequisites - Complete the [@tetherto/mdk-react-devkit installation and add the dependency](/v0-2-0/ui/react/core#prerequisites) ### Import @tetherto/mdk-react-devkit/core ```tsx ComponentSize, ButtonVariant, ColorVariant, Status, ApiResponse, } from '@tetherto/mdk-react-devkit/core' ``` ### Common types #### `ComponentSize` @tetherto/mdk-react-devkit/core Standard size variants used across multiple components. ```tsx type ComponentSize = 'sm' | 'md' | 'lg' ``` Used by: `Button`, `Badge`, `Checkbox`, `Switch`, `Radio`, `Spinner`, `Indicator`, `EmptyState`, `Pagination`. #### `ButtonSize` @tetherto/mdk-react-devkit/core Extends `ComponentSize` with an icon-only variant. ```tsx type ButtonSize = ComponentSize | 'icon' ``` #### `BorderRadius` @tetherto/mdk-react-devkit/core Border radius variants for form components. ```tsx type BorderRadius = 'none' | 'small' | 'medium' | 'large' | 'full' ``` Used by: `Checkbox`, `Switch`, `Radio`. #### `ColorVariant` @tetherto/mdk-react-devkit/core Comprehensive color variants for components. ```tsx type ColorVariant = | 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' ``` #### `StatusVariant` @tetherto/mdk-react-devkit/core Status variants for state indication. ```tsx type StatusVariant = 'success' | 'processing' | 'error' | 'warning' | 'default' | 'idle' ``` Used by: badges, notifications, status indicators. #### `ComponentColor` @tetherto/mdk-react-devkit/core Color options for form components. ```tsx type ComponentColor = 'default' | 'primary' | 'success' | 'warning' | 'error' ``` Used by: `Checkbox`, `Switch`, `Radio`, `Typography`. #### `Position` @tetherto/mdk-react-devkit/core Position/side options for UI elements. ```tsx type Position = 'top' | 'right' | 'bottom' | 'left' ``` Used by: `Tooltip`, `Popover`, chart legends. #### `TextAlign` @tetherto/mdk-react-devkit/core Text alignment options. ```tsx type TextAlign = 'left' | 'center' | 'right' | 'justify' ``` #### `FlexAlign` @tetherto/mdk-react-devkit/core Flex/grid alignment options. ```tsx type FlexAlign = 'start' | 'center' | 'end' ``` ### Component types #### `ButtonVariant` @tetherto/mdk-react-devkit/core Button visual variants. ```tsx type ButtonVariant = | 'primary' | 'secondary' | 'danger' | 'tertiary' | 'link' | 'icon' | 'outline' | 'ghost' ``` #### `ButtonIconPosition` @tetherto/mdk-react-devkit/core Where icons appear in buttons. ```tsx type ButtonIconPosition = 'left' | 'right' ``` #### `NotificationVariant` @tetherto/mdk-react-devkit/core Toast/notification variants. ```tsx type NotificationVariant = 'success' | 'error' | 'warning' | 'info' ``` #### `BadgeStatus` @tetherto/mdk-react-devkit/core Badge status options. ```tsx type BadgeStatus = 'success' | 'processing' | 'error' | 'warning' | 'default' ``` #### `TypographyColor` @tetherto/mdk-react-devkit/core Typography color options including muted. ```tsx type TypographyColor = 'default' | 'primary' | 'success' | 'warning' | 'error' | 'muted' ``` ### Utility types #### `UnknownRecord` @tetherto/mdk-react-devkit/core Generic type for objects with unknown structure. ```tsx type UnknownRecord = Record ``` #### `Nullable` / `Optional` / `Maybe` @tetherto/mdk-react-devkit/core Null/undefined wrapper types. ```tsx type Nullable = T | null type Optional = T | undefined type Maybe = T | null | undefined ``` #### `Status` @tetherto/mdk-react-devkit/core Async operation status. ```tsx type Status = 'idle' | 'loading' | 'success' | 'error' ``` ### API types #### `PaginationParams` @tetherto/mdk-react-devkit/core Pagination request parameters. ```tsx type PaginationParams = { limit?: number offset?: number page?: number } ``` #### `PaginatedResponse` @tetherto/mdk-react-devkit/core Paginated response wrapper. ```tsx type PaginatedResponse = { data: T[] page: number total: number totalPages: number } ``` #### `ApiResponse` @tetherto/mdk-react-devkit/core API response wrapper. ```tsx type ApiResponse = { data: T message?: string status: number } ``` #### `ApiError` @tetherto/mdk-react-devkit/core API error response structure. ```tsx type ApiError = { error: string message: string status: number data?: { message?: string } } ``` ### Data table types @tetherto/mdk-react-devkit/core Re-exported from TanStack Table for convenience. ```tsx DataTableColumnDef, DataTableExpandedState, DataTablePaginationState, DataTableRow, DataTableRowSelectionState, DataTableSortingState, } from '@tetherto/mdk-react-devkit/core' ``` ### Value types #### `ValueUnit` @tetherto/mdk-react-devkit/core A value paired with a unit, used for display formatting. ```tsx type ValueUnit = { value: number | string | null unit: string realValue: number } ``` #### `HashrateUnit` / `CurrencyUnit` @tetherto/mdk-react-devkit/core Specialized value-unit aliases. ```tsx type HashrateUnit = ValueUnit type CurrencyUnit = ValueUnit ``` #### `UnitLabel` @tetherto/mdk-react-devkit/core SI-prefix unit labels. ```tsx type UnitLabel = 'decimal' | 'k' | 'M' | 'G' | 'T' | 'P' ``` ### Time types #### `TimeRangeFormatted` @tetherto/mdk-react-devkit/core A formatted time range. ```tsx type TimeRangeFormatted = { start: string end: string formatted: string } ``` #### `TimeInterval` @tetherto/mdk-react-devkit/core A time interval with start/end timestamps. ```tsx type TimeInterval = { start: number end: number } ``` ### Chart types #### `ChartLegendPosition` @tetherto/mdk-react-devkit/core Chart legend position options. ```tsx type ChartLegendPosition = 'top' | 'bottom' | 'left' | 'right' | 'center' | 'chartArea' ``` #### `WeightedAverageResult` @tetherto/mdk-react-devkit/core Result from weighted average calculation. ```tsx type WeightedAverageResult = { avg: number totalWeight: number weightedValue: number } ``` #### `ErrorWithTimestamp` @tetherto/mdk-react-devkit/core An error with optional timestamp. ```tsx type ErrorWithTimestamp = { msg?: string message?: string timestamp?: number | string } ``` ## Foundation types Foundation types describe the shape of devices, containers, alerts, site configuration, and settings data flowing through `@tetherto/mdk-react-devkit/foundation` components and API responses. They are organized into barrels including `alerts`, `config`, `device`, and `settings.types`. ### Prerequisites - Complete the [@tetherto/mdk-react-devkit installation and add the dependency](/v0-2-0/ui/react/foundation#prerequisites) ### Import @tetherto/mdk-react-devkit/foundation ```tsx Alert, Device, DeviceLast, DeviceInfo, ContainerSnap, ContainerStats, MinerStats, MinerConfig, SettingsUser, PermLevel, GlobalConfig, TimelineChartDataPoint, TimelineChartDataset, TimelineChartData, ChartRange, AxisTitleText, MetricsEfficiencyLogEntry, } from '@tetherto/mdk-react-devkit/foundation' ``` ### Alert types #### `Alert` @tetherto/mdk-react-devkit/foundation Raw alert record as it appears on a device's `alerts` array. ```tsx type Alert = { id?: string severity: string createdAt: number | string name: string description: string message?: string uuid?: string code?: string | number [key: string]: unknown } ``` The `severity` field uses string values like `critical`, `high`, `medium`, `low`. The open `[key: string]: unknown` index signature allows vendor-specific fields without breaking the type contract. #### `LogFormattedAlertData` @tetherto/mdk-react-devkit/foundation Alert reshaped for log display components (e.g., `AlertsLog`). ```tsx type LogFormattedAlertData = { title: string subtitle: string status: string severityLevel: number creationDate: number | string body: string id: string uuid?: string [key: string]: unknown } ``` ### Device types The `Device` family models everything that appears on the device explorer: miners, containers, power meters, temperature sensors, and cabinets. The shape is intentionally permissive (open index signatures, optional fields) because devices come from a live API that adds vendor-specific fields over time. #### `Device` @tetherto/mdk-react-devkit/foundation The root device record. Used pervasively in the [operations centre](/v0-2-0/ui/react/foundation/operations) components. ```tsx type Device = { id: string type: string tags?: string[] rack?: string last?: DeviceLast username?: string info?: DeviceInfo containerId?: string address?: string | null code?: string alerts?: Alert[] | null powerMeters?: Device[] tempSensors?: Device[] transformerTempSensor?: Device rootTempSensor?: Device [key: string]: unknown } ``` The `type` string discriminates devices by category (such as miner or container) and by vendor. The `last` field carries the latest snapshot from the device. #### `DeviceLast` @tetherto/mdk-react-devkit/foundation The latest reading wrapper that lives on `Device.last`. ```tsx type DeviceLast = { err?: string | null type?: string snap?: ContainerSnap alerts?: Alert[] | null [key: string]: unknown } ``` `err` is a connection or upstream error string when the device is unreachable. `snap` carries the actual stats and config payload. #### `DeviceInfo` @tetherto/mdk-react-devkit/foundation Identification and placement metadata that lives on `Device.info`. ```tsx type DeviceInfo = { container?: string pos?: string poolConfig?: string serialNum?: string macAddress?: string | null posHistory?: Partial [key: string]: unknown } ``` #### `PosHistoryEntry` @tetherto/mdk-react-devkit/foundation A single past placement of a miner. ```tsx type PosHistoryEntry = { container: string pos: string removedAt: number } ``` #### `DeviceData` @tetherto/mdk-react-devkit/foundation A flattened version of `Device` with a guaranteed (non-optional) `snap` field, returned by the `getDeviceData` helper. ```tsx type DeviceData = { id: string type: string tags?: string[] rack?: string snap: ContainerSnap alerts?: Alert[] username?: string info?: DeviceInfo containerId?: string address?: string err?: string [key: string]: unknown } ``` ### Container types #### `Container` @tetherto/mdk-react-devkit/foundation A `Device` specialized for containers, with container-specific `info` and `last` shapes. ```tsx type Container = { info?: Partial last?: Partial } & Device ``` #### `ContainerInfo` @tetherto/mdk-react-devkit/foundation Cooling, supply, and pressure metadata for a container. ```tsx type ContainerInfo = { container: string cooling_system: Record cdu: Record primary_supply_temp: number second_supply_temp1: number second_supply_temp2: number supply_liquid_temp: number supply_liquid_set_temp: number supply_liquid_pressure: number return_liquid_pressure: number } ``` #### `ContainerPosInfo` @tetherto/mdk-react-devkit/foundation Position descriptor for a device inside a container (PDU/socket coordinates). ```tsx type ContainerPosInfo = { containerInfo: Partial<{ container: string; type: string }> pdu: string | number socket: string | number pos: string [key: string]: unknown } ``` #### `ContainerLast` @tetherto/mdk-react-devkit/foundation Last-snapshot wrapper for a container. ```tsx type ContainerLast = { snap: { stats?: Partial } alerts: unknown[] | null err: string | null } ``` #### `ContainerSnap` @tetherto/mdk-react-devkit/foundation The `snap` payload found on `DeviceLast.snap` for both miners and containers. `stats` is what most components read. ```tsx type ContainerSnap = { stats?: Partial config?: Record } ``` #### `ContainerStats` @tetherto/mdk-react-devkit/foundation The big stats blob produced by every container snapshot. ```tsx type ContainerStats = { status: string ambient_temp_c: number humidity_percent: number power_w: number container_specific: Partial distribution_box1_power_w: number distribution_box2_power_w: number stats: Record temperature_c: Partial frequency_mhz: Partial miner_specific: Partial [key: string]: unknown } ``` `status` is one of `running`, `offline`, `stopped` (see `CONTAINER_STATUS` in [Constants](/v0-2-0/reference/app-toolkit/ui-kit/constants#foundation-constants)). #### `ContainerSpecific` @tetherto/mdk-react-devkit/foundation Container-specific stats (currently the PDU array). ```tsx type ContainerSpecific = { pdu_data: Partial[] [key: string]: unknown } ``` #### `ContainerPduData` @tetherto/mdk-react-devkit/foundation Per-PDU power and status reading. ```tsx type ContainerPduData = { power_w: number status: number } ``` #### `StatsTemperatureC` @tetherto/mdk-react-devkit/foundation Temperature stats with per-chip detail. ```tsx type StatsTemperatureC = { avg: number min: number max: number chips: TempChipData[] [key: string]: unknown } ``` #### `StatsFrequencyMhz` @tetherto/mdk-react-devkit/foundation Frequency stats with per-chip detail. ```tsx type StatsFrequencyMhz = { avg: number chips: ChipData[] [key: string]: unknown } ``` #### `ChipData` / `TempChipData` @tetherto/mdk-react-devkit/foundation Per-chip readings. ```tsx type ChipData = { index: number current: number } type TempChipData = { index: number max?: number min?: number avg?: number } ``` #### `MinerSpecificStats` @tetherto/mdk-react-devkit/foundation Miner-specific stats blob. ```tsx type MinerSpecificStats = { upfreq_speed: number [key: string]: unknown } ``` ### Miner types #### `MinerStats` @tetherto/mdk-react-devkit/foundation Per-miner stats reported on each snapshot. ```tsx type MinerStats = { status?: string uptime_ms?: number power_w?: number hashrate_mhs?: MinerHashrateMhs poolHashrate?: string temperature_c?: { max?: number } } ``` #### `MinerHashrateMhs` @tetherto/mdk-react-devkit/foundation Hashrate readings, currently just the rolling 5-minute window. ```tsx type MinerHashrateMhs = { t_5m?: number } ``` #### `MinerInfo` @tetherto/mdk-react-devkit/foundation Identifying info for a miner (used by miner record cards). ```tsx type MinerInfo = { container?: string pos?: string macAddress?: string serialNum?: string } ``` #### `MinerConfig` @tetherto/mdk-react-devkit/foundation Mutable miner configuration. ```tsx type MinerConfig = { firmware_ver?: string power_mode?: string led_status?: boolean } ``` `power_mode` values come from `MINER_POWER_MODE` (`sleep`, `low`, `normal`, `high`). #### `MinerDeviceSnapshot` @tetherto/mdk-react-devkit/foundation Lightweight snapshot wrapper holding only `MinerConfig`. ```tsx type MinerDeviceSnapshot = { last?: { snap?: { config?: MinerConfig } } } ``` #### `MinerRecord` @tetherto/mdk-react-devkit/foundation Combined miner record used by list/table views. ```tsx type MinerRecord = { id?: string shortCode?: string info?: MinerInfo address?: string type?: string alerts?: unknown[] stats?: MinerStats config?: MinerConfig device?: MinerDeviceSnapshot error?: string err?: string isPoolStatsEnabled?: boolean } ``` ### Power and cabinet types #### `PowerMeter` @tetherto/mdk-react-devkit/foundation Minimal power meter reading shape. ```tsx type PowerMeter = { last?: { snap?: { stats?: { power_w?: number } } } } ``` #### `LvCabinetRecord` @tetherto/mdk-react-devkit/foundation LV cabinet record carrying its associated power meters. ```tsx type LvCabinetRecord = { id: string powerMeters?: PowerMeter[] } ``` ### Config types #### `GlobalConfig` @tetherto/mdk-react-devkit/foundation Site-wide configuration from your API or store, including nominal targets for reporting dashboards. Load this shape in your app from your API or store and pass it into foundation reporting UI as needed. ```tsx type GlobalConfig = { nominalSiteHashrate_MHS?: number nominalAvailablePowerMWh?: number nominalPowerConsumption_MW?: number nominalWeightedAvgEfficiency_WThs?: number nominalMinerCapacity?: number isAutoSleepAllowed?: boolean siteEnergyDataThresholdMWh?: number [key: string]: unknown } ``` | Field | Type | Description | |-------|------|-------------| | `nominalSiteHashrate_MHS` | `number` | Nominal site hashrate (MH/s) | | `nominalAvailablePowerMWh` | `number` | Nominal available power (MWh on the wire) | | `nominalPowerConsumption_MW` | `number` | Nominal power consumption (MW) | | `nominalWeightedAvgEfficiency_WThs` | `number` | Nominal weighted average efficiency (W/TH) | | `nominalMinerCapacity` | `number` | Nominal miner capacity | | `isAutoSleepAllowed` | `boolean` | Whether auto-sleep is permitted for the site | | `siteEnergyDataThresholdMWh` | `number` | Energy data threshold (MWh) | | `[key: string]` | `unknown` | Additional API fields without breaking the type | ### Timeline chart types Types for [`TimelineChart`](/v0-2-0/ui/react/foundation/dashboard/charts#timelinechart) in `@tetherto/mdk-react-devkit/foundation`. Each segment uses `x: [startMs, endMs]` and `y` matching a row in `labels`. #### `TimelineChartDataPoint` @tetherto/mdk-react-devkit/foundation One horizontal segment on a timeline row. ```tsx type TimelineChartDataPoint = { x: [number, number] y: string | undefined } ``` | Field | Type | Description | |-------|------|-------------| | `x` | `[number, number]` | Segment start and end (epoch ms) | | `y` | `string \| undefined` | Row label; must match an entry in `TimelineChartData.labels` | #### `TimelineChartDataset` @tetherto/mdk-react-devkit/foundation A named group of segments (legend entry). ```tsx type TimelineChartDataset = { label: string data: TimelineChartDataPoint[] borderColor?: string[] backgroundColor?: string[] color?: string } ``` #### `TimelineChartData` @tetherto/mdk-react-devkit/foundation Full payload for `initialData` and `newData`. ```tsx type TimelineChartData = { labels: string[] datasets: TimelineChartDataset[] } ``` | Field | Type | Description | |-------|------|-------------| | `labels` | `string[]` | Row names (Y axis) | | `datasets` | `TimelineChartDataset[]` | Segment groups keyed by `label` | #### `ChartRange` @tetherto/mdk-react-devkit/foundation Visible time window for the `range` prop. ```tsx type ChartRange = { min: Date | number max: Date | number } ``` #### `AxisTitleText` @tetherto/mdk-react-devkit/foundation Axis title strings for `axisTitleText`. ```tsx type AxisTitleText = { x: string y: string } ``` ### Metrics efficiency types Types for **`/auth/metrics/efficiency`**, used by the site view tab on [`OperationsEfficiency`](/v0-2-0/ui/react/foundation/reporting/operations-efficiency). #### `MetricsEfficiencyLogEntry` @tetherto/mdk-react-devkit/foundation One point on the site efficiency time series. ```tsx type MetricsEfficiencyLogEntry = { ts: number efficiencyWThs: number } ``` | Field | Type | Description | |-------|------|-------------| | `ts` | `number` | Timestamp (epoch ms) | | `efficiencyWThs` | `number` | Site efficiency (W/TH) at `ts` | #### `MetricsEfficiencySummary` @tetherto/mdk-react-devkit/foundation Summary block returned with the efficiency metrics response. ```tsx type MetricsEfficiencySummary = { avgEfficiencyWThs: number | null } ``` #### `MetricsEfficiencyResponse` @tetherto/mdk-react-devkit/foundation Wrapper for the efficiency metrics endpoint (`log` entries plus summary), using the shared `MetricsResponse` shape from `@tetherto/mdk-react-devkit/foundation`. ### Settings types #### `SettingsUser` @tetherto/mdk-react-devkit/foundation A user record as it appears in user-management lists. ```tsx type SettingsUser = { id: string name?: string email: string role: string last_login?: string lastActive?: string [key: string]: unknown } ``` #### `RoleOption` @tetherto/mdk-react-devkit/foundation Role option for select dropdowns. Also exported as the array `USER_ROLES` in [Constants](/v0-2-0/reference/app-toolkit/ui-kit/constants#foundation-constants). ```tsx type RoleOption = { label: string value: string } ``` #### `PermLevel` @tetherto/mdk-react-devkit/foundation Permission level values. ```tsx type PermLevel = 'rw' | 'r' | false ``` #### `RolesPermissionsData` @tetherto/mdk-react-devkit/foundation The shape consumed by [`RBACControlSettings`](/v0-2-0/ui/react/foundation/settings/access-control). ```tsx type RolesPermissionsData = { permissions: Record> labels: Record } ``` #### `SettingsExportData` @tetherto/mdk-react-devkit/foundation The export envelope produced and consumed by [`ImportExportSettings`](/v0-2-0/ui/react/foundation/settings/import-export). Generic `TExtra` lets you attach app-specific extras. ```tsx type SettingsExportData = Record> = { headerControls?: Record featureFlags?: Record timestamp?: string version?: string } & TExtra ``` #### `ImportResult` @tetherto/mdk-react-devkit/foundation Result returned from settings import operations. ```tsx type ImportResult = { success: boolean applied?: string[] errors?: string[] message?: string } ``` # Utilities (/v0-2-0/reference/app-toolkit/ui-kit/utilities) This page documents helper functions exported by the MDK packages. - [Core utilities](#core-utilities) ships **15 utility modules** with functions for formatting, dates, validation, conversions, class-name merging, and more - [Foundation utilities](#foundation-utilities) currently ships a single public utility module (`settings-utils`) for parsing, validating, and exporting settings JSON ## Core utilities Helper functions exported by `@tetherto/mdk-react-devkit/core` for formatting, dates, validation, conversions, and class-name merging. ### Prerequisites - Complete the [@tetherto/mdk-react-devkit installation and add the dependency](/v0-2-0/ui/react/core#prerequisites) ### Import @tetherto/mdk-react-devkit/core ```tsx formatNumber, formatHashrate, formatDate, formatRelativeTime, cn, isEmpty, isValidEmail, } from '@tetherto/mdk-react-devkit/core' ``` ### Formatting utilities #### `formatNumber` @tetherto/mdk-react-devkit/core Format numbers with locale formatting and configurable options. ```tsx formatNumber(1234.567) // "1,234.57" formatNumber(null) // "-" formatNumber(1234, { minimumFractionDigits: 2 }) // "1,234.00" formatNumber(undefined, {}, 'N/A') // "N/A" ``` #### `formatHashrate` @tetherto/mdk-react-devkit/core Format hashrate values with rounding. ```tsx formatHashrate(150.456) // "150.46" formatHashrate(null) // "-" ``` #### `formatCurrency` @tetherto/mdk-react-devkit/core Format currency values. ```tsx formatCurrency(1234.56, 'USD') // "$1,234.56" formatCurrency(0.00012345, 'BTC') // "β‚Ώ0.00012345" ``` #### `getPercentFormattedNumber` @tetherto/mdk-react-devkit/core Format numbers as percentages. ```tsx getPercentFormattedNumber(0.75) // "75%" getPercentFormattedNumber(0.1234, 1) // "12.3%" ``` #### `formatValueUnit` @tetherto/mdk-react-devkit/core Format value-unit objects. ```tsx formatValueUnit(150, 'TH/s') // "150 TH/s" ``` ### Date utilities #### `formatDate` @tetherto/mdk-react-devkit/core Format dates with customizable patterns. ```tsx formatDate(new Date()) // "Jan 15, 2025" formatDate(1705334400000, { format: 'yyyy-MM-dd' }) // "2025-01-15" ``` #### `formatRelativeTime` @tetherto/mdk-react-devkit/core Format dates as relative time strings. ```tsx formatRelativeTime(new Date(Date.now() - 3600000)) // "1h ago" formatRelativeTime(new Date(Date.now() - 86400000)) // "1d ago" ``` #### `formatChartDate` @tetherto/mdk-react-devkit/core Format timestamps for chart display. ```tsx formatChartDate(1705334400) // "Jan 15" formatChartDate(1705334400, true) // "Jan 15, 2025" ``` #### `isValidTimestamp` @tetherto/mdk-react-devkit/core Check if a timestamp is valid. ```tsx isValidTimestamp(1705334400000) // true isValidTimestamp('invalid') // false ``` #### `parseMonthLabelToDate` @tetherto/mdk-react-devkit/core Parse month labels to `Date` objects. ```tsx parseMonthLabelToDate('01-26') // Date(2026, 0, 1) parseMonthLabelToDate('03-2025') // Date(2025, 2, 1) ``` #### `getPastDateFromDate` @tetherto/mdk-react-devkit/core Get a date in the past. ```tsx getPastDateFromDate({ dateTs: Date.now(), days: 7 }) // 7 days ago ``` ### Validation utilities #### `isEmpty` @tetherto/mdk-react-devkit/core Check if a value is empty. ```tsx isEmpty(null) // true isEmpty('') // true isEmpty([]) // true isEmpty({}) // true isEmpty('hello') // false isEmpty([1, 2, 3]) // false ``` #### `isValidEmail` @tetherto/mdk-react-devkit/core Validate email addresses. ```tsx isValidEmail('user@example.com') // true isValidEmail('invalid') // false ``` #### `isValidUrl` @tetherto/mdk-react-devkit/core Validate URLs. ```tsx isValidUrl('https://example.com') // true isValidUrl('not-a-url') // false ``` #### `isNil` @tetherto/mdk-react-devkit/core Check if value is `null` or `undefined`. ```tsx isNil(null) // true isNil(undefined) // true isNil(0) // false isNil('') // false ``` #### `isPlainObject` @tetherto/mdk-react-devkit/core Check if value is a plain object. ```tsx isPlainObject({}) // true isPlainObject({ a: 1 }) // true isPlainObject([]) // false isPlainObject(new Date()) // false ``` ### Class name utilities #### `cn` @tetherto/mdk-react-devkit/core Merge class names using [clsx](https://github.com/lukeed/clsx) and [tailwind-merge](https://github.com/dcastil/tailwind-merge). ```tsx cn('px-4', 'py-2') // "px-4 py-2" cn('text-red', isError && 'bg-red') // conditional classes cn('p-4', { 'hidden': !visible }) // object syntax ``` ### Conversion utilities #### `toMW` / `toMWh` @tetherto/mdk-react-devkit/core Convert watts to megawatts. ```tsx toMW(1000000) // 1 toMWh(1000000) // 1 ``` #### `toPHS` @tetherto/mdk-react-devkit/core Convert raw hashrate to `PH/s`. ```tsx toPHS(1000000000000000) // 1 ``` #### `convertMpaToBar` @tetherto/mdk-react-devkit/core Convert pressure units. ```tsx convertMpaToBar(0.1) // 1 ``` #### `unitToKilo` @tetherto/mdk-react-devkit/core Convert to kilo units. ```tsx unitToKilo(1000) // 1 ``` ### Number utilities #### `percentage` @tetherto/mdk-react-devkit/core Calculate percentage. ```tsx percentage(25, 100) // 25 percentage(1, 4) // 25 ``` #### `getPercentChange` @tetherto/mdk-react-devkit/core Calculate percentage change. ```tsx getPercentChange(110, 100) // 10 getPercentChange(90, 100) // -10 ``` #### `convertUnits` @tetherto/mdk-react-devkit/core Convert between SI-prefix units. ```tsx convertUnits(1, 'k', 'M') // 0.001 convertUnits(1000, 'decimal', 'k') // 1 ``` #### `safeNumber` @tetherto/mdk-react-devkit/core Safely convert to number. ```tsx safeNumber('123') // 123 safeNumber('invalid') // 0 safeNumber(null) // 0 ``` ### String utilities #### `toTitleCase` @tetherto/mdk-react-devkit/core Convert string to Title Case. ```tsx toTitleCase('hello world') // "Hello World" ``` #### `formatMacAddress` @tetherto/mdk-react-devkit/core Format MAC addresses. ```tsx formatMacAddress('aa:bb:cc:dd:ee:ff') // "AA:BB:CC:DD:EE:FF" ``` #### `safeString` @tetherto/mdk-react-devkit/core Safely convert to string. ```tsx safeString(123) // "123" safeString(null) // "" ``` ### Time utilities #### `secondsToMs` @tetherto/mdk-react-devkit/core Convert seconds to milliseconds. ```tsx secondsToMs(60) // 60000 ``` #### `breakTimeIntoIntervals` @tetherto/mdk-react-devkit/core Split time range into intervals. ```tsx breakTimeIntoIntervals(start, end, 3600000) // Array of 1-hour intervals ``` #### `timeRangeWalker` @tetherto/mdk-react-devkit/core Generator for iterating through time ranges. ```tsx for (const interval of timeRangeWalker(start, end, duration)) { // Process each interval } ``` ### Color utilities #### `hexToRgba` @tetherto/mdk-react-devkit/core Convert hex color to rgba. ```tsx hexToRgba('#72F59E', 0.5) // "rgba(114, 245, 158, 0.5)" ``` ### Array utilities #### `getNestedValue` @tetherto/mdk-react-devkit/core Get nested value by dot-path. ```tsx getNestedValue({ a: { b: 1 } }, 'a.b') // 1 ``` #### `getWeightedAverage` @tetherto/mdk-react-devkit/core Calculate weighted average. ```tsx getWeightedAverage(items, 'value', 'weight') ``` #### `circularArrayAccess` @tetherto/mdk-react-devkit/core Create infinite cycling generator. ```tsx const colors = circularArrayAccess(['red', 'green', 'blue']) colors.next().value // 'red' colors.next().value // 'green' colors.next().value // 'blue' colors.next().value // 'red' (cycles) ``` ## Foundation utilities Helpers exported by `@tetherto/mdk-react-devkit/foundation` for filtering, formatting, validating, parsing, and exporting settings data. ### Prerequisites - Complete the [@tetherto/mdk-react-devkit installation and add the dependency](/v0-2-0/ui/react/foundation#prerequisites) ### Import @tetherto/mdk-react-devkit/foundation ```tsx filterUsers, formatRoleLabel, formatLastActive, validateSettingsJson, parseSettingsFile, exportSettingsToFile, } from '@tetherto/mdk-react-devkit/foundation' ``` ### Settings utilities #### `filterUsers` @tetherto/mdk-react-devkit/foundation Filter a `SettingsUser[]` list by email substring (case-insensitive) and exact role match. Used by the user-management table search. ```tsx filterUsers({ users, email: 'alice', // partial, case-insensitive match on user.email role: 'admin', // exact match on user.role; pass null to skip }) ``` | Parameter | Type | Description | |-----------|------|-------------| | `users` | `SettingsUser[]` | Source list | | `email` | `string \| null \| undefined` | Substring filter on `email` (case-insensitive). Skipped when falsy. | | `role` | `string \| null \| undefined` | Exact role match. Skipped when falsy. | #### `formatRoleLabel` @tetherto/mdk-react-devkit/foundation Convert a snake case role identifier to a human-readable Title Case label. ```tsx formatRoleLabel('site_manager') // "Site Manager" formatRoleLabel('reporting_tool_manager') // "Reporting Tool Manager" formatRoleLabel('admin') // "Admin" ``` #### `formatLastActive` @tetherto/mdk-react-devkit/foundation Format a timestamp string as `MM/DD/YYYY - HH:MM`. Returns `'-'` when the input is missing or invalid. ```tsx formatLastActive('2025-01-15T14:30:00Z') // "01/15/2025 - 14:30" formatLastActive(undefined) // "-" formatLastActive('not-a-date') // "-" ``` #### `validateSettingsJson` @tetherto/mdk-react-devkit/foundation Type-guard that checks whether an unknown value is a valid `SettingsExportData`. Returns `true` if the value is an object that contains at least one of `headerControls`, `featureFlags`, or `timestamp`. ```tsx if (validateSettingsJson(parsed)) { // parsed is now narrowed to SettingsExportData } ``` #### `parseSettingsFile` @tetherto/mdk-react-devkit/foundation Read a `File` containing settings JSON, validate it, and resolve to `SettingsExportData`. Rejects on invalid JSON, an invalid format, or a file read error. ```tsx try { const settings = await parseSettingsFile(file) applySettings(settings) } catch (err) { notifyError('Could not import settings', err.message) } ``` | Throws | Reason | |--------|--------| | `` `Invalid settings file format. Please ensure the file is a valid ${WEBAPP_NAME} settings export.` `` | The JSON parsed but didn't match the `SettingsExportData` shape. The literal interpolates [`WEBAPP_NAME`](/v0-2-0/reference/app-toolkit/ui-kit/constants#webapp_name). | | `Failed to parse JSON file. Please ensure the file is valid JSON.` | The file contents weren't valid JSON. | | `Failed to read file.` | The browser couldn't read the file. | #### `exportSettingsToFile` @tetherto/mdk-react-devkit/foundation Serialize `SettingsExportData` to JSON, package it as a downloadable Blob, and trigger a browser download. Returns the generated filename (e.g., `mdk-settings-2025-01-15T14-30-00-000Z.json`). The filename prefix is fixed in the foundation kit and does not interpolate [`WEBAPP_NAME`](/v0-2-0/reference/app-toolkit/ui-kit/constants#webapp_name). ```tsx const filename = exportSettingsToFile({ headerControls: { poolMiners: true, consumption: false }, featureFlags: { betaCharts: true }, timestamp: new Date().toISOString(), version: '1.0.0', }) ``` # ORK reference (/v0-2-0/reference/ork) `@tetherto/mdk-ork` is the orchestration kernel of the MDK stack. This subsection holds the canonical specs for its internal modules. For the architectural narrative explaining how these modules fit together, see [Architecture](/v0-2-0/concepts/architecture/ork). ## What's documented - **[Modules](/v0-2-0/reference/ork/modules)**: per-module responsibility, interfaces, state machines, transition rules, crash-recovery procedures, and scaling characteristics. # ORK modules (/v0-2-0/reference/ork/modules) `@tetherto/mdk-ork`'s coordination splits across single-purpose modules. Each owns its own state machine, persistence boundary, and scaling characteristics. Six modules ship in v0.0.1; two more (Fault Supervisor, Concurrency Manager) are deferred to a later release. For the architectural overview that explains how these modules connect, see [Architecture](/v0-2-0/concepts/architecture/ork). This page is the per-module spec. ## How to read this page Each accordion below covers one module: - **Responsibility**: what the module owns and the example commands or events it handles. - **Interfaces**: input and output channels, plus the public function names callers depend on. - **State machine**: the canonical diagram for the module's internal lifecycle. - **Crash recovery**: behavior on a fresh start, including how state is reconstructed. - **Scalability**: where the module can be extracted, sharded, or replicated. ## Modules **Responsibility**: validates incoming commands against the generic MDK schema, checks permissions, and resolves the correct worker based on the `deviceId`. *Example*: when an App Node sends a `reboot` command for `wm001`, the Dispatcher verifies that `wm001` exists, that `reboot` is a valid capability for it, then passes the request to the State Machine. **Interfaces**: - *Input*: receives generic commands via Holepunch RPC (HRPC) and the MDK Protocol from the App Node or MCP Handler. - *Output*: hands off validated commands to the Command State Machine. - *Functions*: `dispatchCommand(deviceId, action, payload)` **State machine**: ```mermaid stateDiagram-v2 [*] --> Validating Validating --> RoutingAction : Valid Validating --> Rejected : Invalid RoutingAction --> Enqueued Enqueued --> [*] ``` **Crash recovery**: none needed. In-flight requests fail and must be retried by the client. **Scalability**: extracted easily; can run independently to offload validation. **Responsibility**: tracks the execution lifecycle of every single command in the system. Receives execution results directly via the synchronous HRPC response. If the connection drops or a response is delayed, it relies on the Scheduler to fetch the latest status via `state.pull` so commands cannot hang. *Example*: once the `reboot` command is dispatched it transitions to `EXECUTING`. If the HRPC response returns OK it transitions to `SUCCESS`. If the response hangs, the next `state.pull` fetches the true status from the worker. **Interfaces**: - *Input*: receives validated commands from the Dispatcher; status updates from HRPC responses or Scheduler ticks. - *Output*: invokes the worker HRPC execution layer; emits terminal state results to the caller. - *Functions*: `enqueue(command)`, `syncState(commandId)`, `cancel(commandId)` **State machine**: ```mermaid stateDiagram-v2 [*] --> QUEUED QUEUED --> DISPATCHED DISPATCHED --> EXECUTING : HRPC sent EXECUTING --> SUCCESS : state.pull (response) EXECUTING --> FAILED : state.pull (error) EXECUTING --> TIMEOUT TIMEOUT --> QUEUED : Retry allowed TIMEOUT --> FAILED : Max retries SUCCESS --> [*] FAILED --> [*] ``` **Crash recovery**: on startup, performs a recovery sweep of pending commands. `DISPATCHED` and `EXECUTING` commands are forced to `TIMEOUT` (and re-queued if retries are available); `QUEUED` commands are left untouched. **Scalability**: requires state sharding (for example, by device or rack). **Responsibility**: the phonebook for the entire `@tetherto/mdk-ork` ecosystem. Maps which physical device IDs belong to which connected worker channels and stores their declared capabilities. *Example*: a `whatsminer-worker` connects and declares it manages `wm001` and `wm002`. The Registry saves this topology so the Dispatcher knows exactly where to route a command for `wm001`. **Interfaces**: - *Input*: `identity.register` requests from workers. - *Output*: internal events triggering full state lifecycle binding. - *Functions*: `resolveWorkerState(target)` **State machine**: ```mermaid stateDiagram-v2 [*] --> Unregistered Unregistered --> Discovered : DHT peer detected Discovered --> IdentitySaved : identity pulled IdentitySaved --> Ready : capabilities pulled Ready --> Terminated : eviction Terminated --> [*] ``` **Crash recovery**: rebuilt from state, which serves as a baseline to detect workers that were registered but failed to reconnect. **Scalability**: read-heavy by nature; can be extracted into a read-replica architecture or partitioned by region or rack. **Responsibility**: a lightweight proxy and routing layer between the upper system (UI / AI) and the downstream workers. Rather than `@tetherto/mdk-ork` performing heavy time-series aggregations, the *worker* is responsible for storing and aggregating data for the specific devices it controls. The Collector simply provides an interface to query this data and proxies the response up to the UI (via App Node) or the AI Agent. **Worker data handling (telemetry context)**: - *Compaction*: the worker handles compaction of metrics over large time frames. - *Local storage*: workers should save telemetry data in a local Hyper DB. - *As-requested serving*: the worker serves data strictly when `@tetherto/mdk-ork` asks for it, precisely as dictated by the telemetry schemas in `mdk-contract.json`. - *Internal scheduling*: to achieve this without blocking `@tetherto/mdk-ork`, the worker may run its own internal scheduler for device polling. **Interfaces**: - *Input*: client or AI telemetry queries (for example, "fetch metrics for device wm001"). - *Output*: normalized telemetry payloads passed straight through from the Worker to the requesting layer. - *Functions*: `proxyTelemetryFetch(deviceId, queryArgs)` **State machine**: ```mermaid stateDiagram-v2 [*] --> Idle Idle --> Proxying : Request received Proxying --> RoutingToClient : Worker returns data Proxying --> Timeout : Worker unresponsive RoutingToClient --> Idle ``` **Scalability**: because the heavy lifting of data storage and aggregation is pushed down into the isolated worker processes, the Collector remains stateless and highly scalable as a pure asynchronous router. **Responsibility**: the system metronome. Triggers repetitive tasks without holding any domain-specific logic itself. *Example*: emits an internal `tick` event every 5 seconds that the Health Monitor listens to, prompting it to ping all workers. **Interfaces**: - *Input*: system clock and configured task intervals. - *Output*: injects intents (for example, `telemetry.pull`, `health.ping`) into the Dispatcher or Collector. - *Functions*: `addJob(interval, intent)`, `removeJob(jobId)` **State machine**: ```mermaid stateDiagram-v2 [*] --> Waiting Waiting --> Triggered : Interval elapsed Triggered --> Waiting ``` **Crash recovery**: timers re-initialize from zero on startup. Tasks are strictly idempotent. **Scalability**: scales trivially. Requires basic distributed locking to avoid duplicate ticks in multi-process `@tetherto/mdk-ork` deployments. **Responsibility**: continuously evaluates the liveness and readiness of every registered worker to prevent routing messages to dead nodes. *Example*: if `health.ping` to a worker fails three times in a row, the Health Monitor marks the worker's status as `SICK` and tells the Registry to halt routing new commands there. **Interfaces**: - *Input*: executes `health.ping` sequentially based on Scheduler ticks. - *Output*: pushes status updates to the Registry. - *Functions*: `pingWorker(workerId)`, `getHealth(workerId)` **State machine**: ```mermaid stateDiagram-v2 [*] --> UNKNOWN UNKNOWN --> HEALTHY : Ping success HEALTHY --> SICK : Ping failed (1) SICK --> DEAD : Ping failed (threshold) SICK --> HEALTHY : Ping success DEAD --> HEALTHY : Reconnected ``` **Crash recovery**: blank slate on startup; re-evaluates all known workers immediately via ping. **Scalability**: operates locally per `@tetherto/mdk-ork` kernel, or via independent lightweight ping agents. **Idea**: implements circuit-breaker patterns to protect the overall system from cascading failures caused by bad hardware or software bugs (for example, rejecting commands during a cooling period after repeated errors). **Status**: deferred for the first cut to keep the core orchestrator simple. If the use case arises (such as complex retry backoffs or cluster destabilization), it will be reintroduced. **Idea**: provides guaranteed lock management and queue limits to ensure mutually exclusive commands do not overlap on physical devices. **Status**: deferred for the first cut. The system relies solely on the basic command queue. If explicit global locks or backpressure limits become necessary, this module will be built out. # Protocol reference (/v0-2-0/reference/protocol) The MDK Protocol is the contract that crosses every layer of the stack: Workers, `@tetherto/mdk-ork`, and the App Node all exchange the same envelope. This subsection holds the canonical specs. For the architectural narrative explaining how the protocol fits together, see [Architecture](/v0-2-0/concepts/architecture#the-mdk-protocol). ## What's documented - **[Messages](/v0-2-0/reference/protocol/messages)**: envelope schema, request/response examples, the full action catalogue, and the base command set. # Protocol messages (/v0-2-0/reference/protocol/messages) Every MDK Protocol message uses the same envelope regardless of which layers are talking. This page is the canonical spec for that envelope, the full set of protocol actions, and the base command set every worker must support. For the architectural narrative explaining when each action fires, see [Architecture](/v0-2-0/concepts/architecture#the-mdk-protocol). ## Envelope ```json { "id": "uuid-v4", "version": "0.1.0", "type": "request | response | event", "action": "", "sender": "", "target": " | null", "deviceId": "string | null", "timestamp": 1711640000000, "payload": {} } ``` External consumers (UI or AI agents) only provide `deviceId`; the `target` worker identity is internally resolved by `@tetherto/mdk-ork`. A concrete request and response pair, end to end: ```json // request: App Node asks @tetherto/mdk-ork to reboot device wm001 { "id": "8d1c-e3a4", "version": "0.1.0", "type": "request", "action": "command.request", "sender": "appNode:fleet-api:1", "target": null, "deviceId": "wm001", "timestamp": 1711640000000, "payload": { "command": "reboot" } } // response: @tetherto/mdk-ork relays the worker's terminal result { "id": "1f9b-77c2", "version": "0.1.0", "type": "response", "action": "command.result", "sender": "ork:kernel:tx-1", "target": "appNode:fleet-api:1", "deviceId": "wm001", "timestamp": 1711640002145, "payload": { "status": "SUCCESS", "elapsedMs": 2145 } } ``` ## Actions | Action | Type | Direction | Purpose | |---|---|---|---| | *(DHT presence)* | passive | Worker to DHT topic | Worker joins a known Hyperswarm topic; `@tetherto/mdk-ork` detects its peer connection automatically | | `identity.request` | request | `@tetherto/mdk-ork` to Worker | Requests the worker's identity and managed devices | | `capability.request` | request | `@tetherto/mdk-ork` to Worker | Asks the worker to declare its full capability schema | | `state.pull` | request | `@tetherto/mdk-ork` to Worker | Worker returns a snapshot of state-machine status (low cadence tick, e.g., 60s) | | `telemetry.pull` | request | `@tetherto/mdk-ork` to Worker | Worker returns device metrics plus history (medium cadence tick, e.g., 10s) | | `command.request` | request | `@tetherto/mdk-ork` to Worker | Resolves the worker by `deviceId` and dispatches the command for execution | | `health.ping` | request | `@tetherto/mdk-ork` to Worker | Liveness probe (high cadence tick, e.g., 5s) | ## Base command set The protocol standardizes a Base Command Set supported by every worker: - `getConfig`: retrieve current device configuration - `setConfig`: update device configuration parameters - `health`: fetch detailed diagnostic health status from the device # Supported hardware (/v0-2-0/reference/supported-hardware) ## Overview MDK integrates field hardware through workers. Each worker declares what it supports in its `mdk-contract.json`, and that contract is the single source of truth for coverage. Use this page to discover what workers are supported. ## What MDK supports - **Miners**: For example, Bitmain Antminer, MicroBT Whatsminer - **Containers**: For example, Bitmain Antspace, MicroBT - **Power meters**: For example, ABB, Satec - **Sensors**: For example, Seneca - **Mining pools**: Protocol integrations such as Ocean, F2Pool For the exact model lists, worker packages, and per-worker docs, see the generated catalogue: - [Full supported-hardware catalogue](https://github.com/tetherto/mdk/blob/main/backend/workers/docs/supported-hardware.md) β€” generated from every `backend/workers/**/mdk-contract.json` ## Next steps - New to the moving parts? Read [terminology](/v0-2-0/concepts/terminology) (ORK, worker, manager, thing, mock) - Decide how to run the worker service β€” [Deployment topologies](/v0-2-0/concepts/deployment-topologies) - Run a miner worker β€” [Run a miner worker](/v0-2-0/how-to/miners) # MDK Repositories (/v0-2-0/resources/repositories) The MDK monorepo makes the frontend and backend components publicly accessible: - [https://github.com/tetherto/mdk](https://github.com/tetherto/mdk) *MDK is developed by Tether and released under the [Apache 2.0 license](/v0-2-0/community/contributing#licensing).* # Roadmap (/v0-2-0/resources/roadmap) MDK follows a **two-week release cadence** to keep progress visible, collect feedback early, and progressively harden the platform β€” from a rudimentary end-to-end foundation toward a production-ready release. ## Release principles - **Release every two weeks** to keep momentum and feedback loops short - **Use four maturity phases** to mark clear readiness jumps - **Start with a working end-to-end developer experience**, then harden progressively - **Reach production readiness progressively**, not by a single large release - **Follow standard [semantic versioning](https://semver.org)** β€” the `0.x` line means MDK is still in development and not intended for production ## Versioning and naming strategy MDK uses standard [semantic versioning](https://semver.org) (`MAJOR.MINOR.PATCH`): - A **new version ships every two weeks**, bumping the **minor**: `0.2.0` β†’ `0.3.0` β†’ `0.4.0` β†’ … - **Patch** releases (`0.x.1`) go out only when a fix is needed between the scheduled releases, e.g. 0.2.1 - While MDK is in the **`0.x` line**, interfaces may change and the platform is **not intended for production** β€” this is the standard semver signal, and we use it deliberately so developers can read it literally - **`1.0.0`** marks the first **production-ready** release Maturity is tracked by **phase**, not by version number β€” each phase spans several bi-weekly releases. The four phases describe how ready MDK is at each stage: | Phase | Production-readiness | |---|---| | **Foundation** | Experimental. End-to-end but rudimentary. Not stable. | | **Lab testing** | Complete enough to build against end-to-end, in a lab. Not stable, not for production. | | **On site testing** | Stable enough to test at real sites under real operating conditions, closely monitored. Not yet production-ready. | | **Production Ready** | Ready for production deployment, with stable interfaces and compatibility guarantees. | ## 2026 2026 moves MDK through **four phases**: - **May:** [Foundation](#foundation) - **July:** [Lab testing](#lab-testing) - **October:** [On site testing](#on-site-testing) - **December:** [Production Ready](#production-ready) Between phases, MDK ships a new release **every two weeks**, starting from the Foundation release at the end of May. The diagram below shows only the phase milestones; the bi-weekly releases land in the windows between them. ```mermaid graph TB subgraph foundation [Foundation] p1([May 2026
Foundation
Public, end-to-end but rudimentary]) it1[[Jun–Jul 2026
New release every 2 weeks]] end subgraph lab [Lab testing] p2([Jul 2026
Lab testing
Complete end-to-end, lab only]) it2[[Aug–Oct 2026
New release every 2 weeks]] end subgraph onsite [On site testing] p3([Oct 2026
On site testing
Testing at real sites]) it3[[Nov–Dec 2026
New release every 2 weeks]] end subgraph prod [Production Ready] p4([Dec 2026
Production Ready
Production-ready baseline]) end p1 --> it1 --> p2 --> it2 --> p3 --> it3 --> p4 style foundation fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A style lab fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A style onsite fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A style prod fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A ``` ## Release plan Phase descriptions below focus on the **level of maturity and production-readiness** at each stage. The exact feature scope of each two-week release is decided as we go and is not fixed by this roadmap. ### Foundation *Released end of May 2026.* The first public release is about **visibility, direction, and feedback**. It is intentionally rudimentary: developers can clone it, run an end-to-end example, and get a concrete feel for how MDK fits together. Interfaces are expected to change. The goal is to show where MDK is heading and invite feedback from developers, partners, and early contributors β€” not to support real workloads. ### Lab testing *Target: end of July 2026.* At this stage MDK is **complete enough to build against end to end**. Developers can run a full workflow and experiment with MDK in their own labs. It is **not stable** β€” breaking changes are still expected between releases β€” and it is **not ready for production**. The goal is to validate the end-to-end developer workflow and surface integration gaps before MDK is exposed to real operational conditions. ### On site testing *Target: end of October 2026.* This is the first stage intended for **testing at real sites**. MDK should be stable enough to run on site under real operating conditions, while still being monitored closely. The goal is to validate performance, robustness, deployment workflows, and operational fit in real scenarios. This is not yet a stability commitment β€” interfaces may still change before production readiness. ### Production Ready *Target: end of December 2026.* This is the first **production-ready** release (`1.0.0`). By this point MDK offers a solid baseline for production deployment, with stable core interfaces, validated workflows, and documentation that supports adoption by operators, integrators, and developers building on top of the platform. From here, standard semver compatibility guarantees apply. # Backend stack tutorials (/v0-2-0/tutorials/backend-stack) If ORK, worker, manager, or thing are unfamiliar, read [`terminology.md`](/v0-2-0/concepts/terminology) first. ## Overview Get started with MDK in three short tutorials that build on each other. Each rung adds one layer of capability: first you watch the stack run, then you drive it, then you run a browser demo on top. | Rung | You'll | You'll end with | Mock hardware | Time | |---|---|---|---|---| | [1. Run the stack](/v0-2-0/tutorials/backend-stack/run) | **Observe** β€” run one command and watch a stack come up | ORK plus one registered device, IDs printed | Antminer S19XP | `< 3 min` | | [2. Control devices from the CLI](/v0-2-0/tutorials/backend-stack/cli) | **Interact** β€” drive a running stack from a REPL | Live telemetry and commands over an IPC socket | Whatsminer M56S | `< 3 min` | | [3. Run the dashboard demo](/v0-2-0/tutorials/full-stack/dashboard) | **Run** β€” launch a browser dashboard on the stack | A React dashboard with live charts at `:3030` | Whatsminer M56S | `< 15 min` after a one-time UI build | **New to MDK? Start with [1. Run the stack](/v0-2-0/tutorials/backend-stack/run).** Each rung links to the next, so you can climb straight through. Rungs uses different mock hardware on purpose β€” Antminer on rung 1, Whatsminer on rungs 2 and 3. Notice, the MDK API stays identical: every rung calls the same `getOrk()`, `startWorker()`, and `registerThing()` shape. Only the worker class and the mock device change. That sameness is the point β€” one interface, any hardware. Each rung is self-contained and repeats the clone-and-install step, so you can start at whichever one you need. # 2. Control from CLI (/v0-2-0/tutorials/backend-stack/cli) *Get started Β· 2 of 3 Β· Control devices from the CLI* If ORK, worker, manager, or thing are unfamiliar, read [`terminology.md`](/v0-2-0/concepts/terminology) first. ## Overview This is rung 2 of the [Get started](/v0-2-0/tutorials/backend-stack) ladder: **interact**. It walks the shortest path from a fresh clone to a fully wired MDK stack you can drive interactively from a CLI. Everything runs in one Node process, no real hardware required. What you'll have at the end: - A mock Whatsminer M56S serving telemetry on `127.0.0.1:14028` - An ORK with one worker registered and one device discovered - An interactive `client.js` REPL talking to ORK over its IPC socket β€” pull metrics, list workers, send commands like `reboot` and `setpower` - (Optional) An App Node HTTP API on `:3000` so non-Node consumers (browsers, AI agents over MCP) can hit the same stack over REST Same shape as [rung 1](/v0-2-0/tutorials/backend-stack/run): the stack still boots with `getOrk()`, `startWorker()`, and `registerThing()`. Only the worker class (`WM_M56S`) and the mock hardware (Whatsminer instead of Antminer) change. What's new here is a second process β€” `client.js` β€” that connects over IPC and drives the running stack. The example lives in [`backend/core/examples/mdk-e2e/`](https://github.com/tetherto/mdk/blob/main/backend/core/examples/README.md#end-to-end-mdk-e2e) and contains six runnable scripts. This tutorial uses three of them: `run.js` for a smoke test, `server.js` for the long-running stack, and `client.js` for interactive control. ## Prerequisites - Node.js >=24 (LTS) - npm >=11 The stack starts an ORK whose control plane is peer-to-peer over a Hyperswarm DHT, so it needs outbound network access. Without it the stack stalls at startup while the ORK tries to reach DHT bootstrap nodes. See [how workers connect](/v0-2-0/concepts/worker-discovery) for the ORK/DHT mechanics. ### Clone and install #### 1.1 Clone the repo ```bash git clone git@github.com:tetherto/mdk.git cd mdk ``` #### 1.2 Install dependencies The monorepo has two workspaces with their own dependency trees. Install both: ```bash backend/core/install-packages.sh ci backend/workers/install-packages.sh ci ``` Each script walks every `package.json` under its workspace and runs `npm ci`. The `examples/mdk-e2e/` package is included automatically β€” no extra install step needed.
(1.3 Optional) Smoke test the stack Before going interactive, prove the wiring works. `run.js` starts a mock Whatsminer + worker + ORK in one process, exercises a few queries, prints the results, and exits cleanly: ```bash node backend/core/examples/mdk-e2e/run.js ``` Expected output (the UUID and metric values vary): ``` Devices: [ '8f3e9a2b-7c1d-4e5a-9f8b-6c2d1e3f4a5b [miner-wm-m56s]' ] Telemetry: ONLINE hashrate=170000 power=3500W Commands: reboot, setPowerMode, setLED, setPowerPct, setupPools, saveComment ``` If you see those three lines, every layer is working: the mock is responding, the worker registered the device, ORK discovered the worker over the local DHT topic, and IPC routing is delivering envelopes both ways. The script tears itself down and exits with code 0. If the smoke test fails with `EADDRINUSE` on port 14028, a previous run left a Node process alive. Kill stragglers with `pkill -f mdk-e2e` and retry.
### Run the interactive demo #### 3.1 Start the stack In your terminal: ```bash node backend/core/examples/mdk-e2e/server.js ``` `server.js` starts the same mock + worker + ORK as `run.js`, but stays running and prints the IDs you'll need: ``` ORK key: 7a4c8b...e3f0 Device: 8f3e9a2b-7c1d-4e5a-9f8b-6c2d1e3f4a5b hp-rpc-cli -s 7a4c8b...e3f0 -m mdk -d '{...}' hp-rpc-cli -s 7a4c8b...e3f0 -m mdk -d '{...}' Ctrl+C to stop. ``` The `ORK key` is a 64-char hex public key. `Device` is a UUIDv4 generated at registration time. Both vary per run β€” note the device UUID for the next step. The two `hp-rpc-cli` lines are paste-ready commands for inspecting ORK over HRPC from another machine. You don't need them for this tutorial β€” they're there if you have [`hp-rpc-cli`](https://www.npmjs.com/package/hyperswarm-rpc-cli) installed and want to go off-script. #### 3.2 Connect the interactive client Open a second terminal in the same `mdk` directory: ```bash node backend/core/examples/mdk-e2e/client.js ``` `client.js` connects to ORK's default IPC socket and gives you an MDK REPL: ``` MDK Client β€” connected to IPC: /tmp/mdk/ork.sock Type "help" for commands, "quit" to exit. mdk> ``` #### 3.3 Drive the stack 3.3.1 Discover the telemetry of your (mock) device by entering commands at the `mdk>` prompt, substituting `` with the UUID printed in Step 3.1: ``` mdk> metrics ``` Each command builds an MDK Protocol envelope, writes it to ORK's IPC socket, and prints the JSON response. 3.3.2 Try changing the power mode and observing the effect: ``` mdk> setpower low mdk> metrics ``` After `setpower ... low` the second `metrics` call should reflect the power mode change. {/* sync with backend/core/examples/mdk-e2e/client.js help block */}
Full command reference **Reads** ``` workers β€” list workers list [deviceId] β€” list devices count [deviceId] β€” device count metrics β€” live telemetry from hardware logs β€” recent logs settings [deviceId] β€” worker settings stats [deviceId] β€” fleet stats config β€” device config (pools, etc.) capabilities β€” mdk-contract capabilities state [deviceId] β€” worker state snapshot ``` **Commands** ``` reboot β€” reboot miner setpower β€” set power mode (normal/low/high) setled [on|off] β€” toggle LED ``` ``` quit / exit β€” exit client ```
#### 3.4 Tear down When you're done, exit the client and stop the stack: ``` mdk> quit ``` Then `Ctrl+C` in Terminal 1.
### 4 (Optional) enable App Node for HTTP access In **Terminal 1**, `Ctrl+C` the running stack, then restart with `--app-node`: ```bash node backend/core/examples/mdk-e2e/server.js --app-node ``` You'll see an extra line in the startup banner: ``` App-node: http://localhost:3000 (noAuth mode) ``` Confirm it's alive β€” App Node has no index page, so hit `/auth/site` directly: ```bash curl http://localhost:3000/auth/site ``` You should see something like `{"site":"Site_Name"}`. In `noAuth` mode most data endpoints (e.g. `/auth/list-things`, `/auth/miners`) are unavailable β€” they require the cache and auth config that the full App Node service sets up. `--app-node` in `server.js` is a development shortcut; for full REST access to device telemetry, run App Node as a full service. See [`backend/core/app-node/`](https://github.com/tetherto/mdk/blob/main/backend/core/app-node/README.md) for setup. `--app-node` runs in `noAuth` mode for development convenience. Do not expose port 3000 outside localhost.
## What just happened Your stack wired up, in order: 1. **Mock device**:`server.js` calls `wmMock.createServer({ port: 14028, ... })` β€” an HTTP server speaking the Whatsminer protocol with canned telemetry. 2. **ORK**: `getOrk()` boots the kernel, generates a random DHT topic, and opens an IPC socket at the default path (`os.tmpdir()/mdk/ork.sock`). 3. **Worker**: `startWorker(WM_M56S, { ork })` instantiates the Whatsminer manager, mounts its protocol adapter, and registers with ORK directly β€” no DHT round-trip because they share the process. 4. **Thing registration**: `manager.registerThing({ info, opts })` tells the worker about the device at `127.0.0.1:14028`. The worker stores the registration and starts polling. 5. **Client**: `client.js` opens the IPC socket from a second process and sends MDK Protocol envelopes (`worker.list`, `telemetry.pull`, `command.request`, ...). ORK routes them to the worker, the worker hits the mock, and the response flows back over IPC. No App Node here? Right. App Node is the translator that lets non-Node consumers β€” browser UIs, AI agents over MCP β€” speak MDK Protocol to ORK. `client.js` already speaks MDK Protocol over IPC, so it talks to ORK directly. App Node becomes mandatory only when the consumer can't open a UNIX socket. See [`architecture.md#app-node`](/v0-2-0/concepts/architecture#app-node). ## Cleanup `Ctrl+C` in Terminal 1 stops the worker, ORK, and mock cleanly. `run.js` deletes its own state directory on exit. `server.js` leaves data under `os.tmpdir()/mdk/` β€” safe to ignore, or remove with: ```bash rm -rf "$TMPDIR/mdk" /tmp/mdk ``` ## Continue Next: [3. Run the dashboard demo](/v0-2-0/tutorials/full-stack/dashboard) β€” put a browser dashboard on a running stack with live charts. ## Go deeper - Walk the simpler single-script Antminer path β€” [1. Run the stack](/v0-2-0/tutorials/backend-stack/run) - Run a full site (5 workers, 26 devices) β€” [`backend/core/examples/mdk-site/site.js`](https://github.com/tetherto/mdk/blob/main/backend/core/examples/mdk-site/site.js) - Understand the install pattern for any worker β€” [`backend/workers/docs/install-pattern.md`](https://github.com/tetherto/mdk/blob/main/backend/workers/docs/install-pattern.md) - Read all runnable examples in one place β€” [`backend/core/examples/README.md`](https://github.com/tetherto/mdk/blob/main/backend/core/examples/README.md) # 1. Run the stack (/v0-2-0/tutorials/backend-stack/run) *Get started Β· 1 of 3 Β· Run the stack* If ORK, worker, manager, or thing are unfamiliar, read [`terminology.md`](/v0-2-0/concepts/terminology) first. ## Overview This is rung 1 of the [Get started](/v0-2-0/tutorials/backend-stack) ladder: **observe**. It walks the shortest path from a fresh clone to a running ORK with one mock Antminer registered, no hardware required. Everything runs in one Node process. What you'll have at the end: - A mock Antminer S19XP serving Bitmain CGI endpoints on `127.0.0.1:14021` - An ORK started and aware of one registered device - The ORK HRPC key and device ID printed to the terminal β€” ready for further inspection ## Prerequisites - Node.js >=24 (LTS) - npm >=11 The stack starts an ORK whose control plane is peer-to-peer over a Hyperswarm DHT, so it needs outbound network access. Without it the stack stalls at startup while the ORK tries to reach DHT bootstrap nodes. See [how workers connect](/v0-2-0/concepts/worker-discovery) for the ORK/DHT mechanics. ### Clone and install #### 1.1 Clone the repo ```bash git clone git@github.com:tetherto/mdk.git cd mdk ``` #### 1.2 Install dependencies The monorepo has two workspaces with their own dependency trees. Install both: ```bash backend/core/install-packages.sh ci backend/workers/install-packages.sh ci ``` Each script walks every `package.json` under its workspace and runs `npm ci`. ### Run the example ```bash node backend/workers/miners/antminer/examples/run-s19xp.js ``` Expected output (the hex key varies): ``` ORK HRPC key: 7a4c8b...e3f0 Device: t-miner-am-s19xp-127-0-0-1 Ctrl+C to stop. ``` If you see those two lines, the whole stack is up: mock device responding, worker registered, ORK started and aware of the device. If the example fails with `EADDRINUSE`, a previous run left port 14021 bound. Kill stale Node processes with `pkill -f run-s19xp` and retry. ## What just happened Here is what [`run-s19xp.js`](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/antminer/examples/run-s19xp.js) does, line by line β€” the minimum runnable shape for an MDK stack: ```js const { getOrk, startWorker } = require('@tetherto/mdk') const { AM_S19XP } = require('@tetherto/miner-antminer') const amMock = require('@tetherto/miner-antminer/mock/server') // Start a mock Antminer S19XP on port 14021 to emit telemetry amMock.createServer({ port: 14021, host: '127.0.0.1', type: 's19xp', serial: 'AM-001', password: 'root' }) // Start ORK β€” the orchestration kernel that manages all workers const ork = await getOrk() // Start the Antminer worker β€” it joins the same DHT topic as ORK, allowing ORK to discover and pull the worker's identity and capabilities const { manager } = await startWorker(AM_S19XP, { ork }) // Register the mock Antminer device with the worker so it starts polling await manager.registerThing({ info: { container: 'site-1', serialNum: 'AM-001' }, // logical identity opts: { address: '127.0.0.1', port: 14021, username: 'root', password: 'root' } // connection details }) ``` Five steps, in order: 1. **Starts a mock miner.** `amMock.createServer({ port: 14021, type: 's19xp', serial: 'AM-001', password: 'root' })` binds port 14021 with a Bitmain-compatible HTTP API serving canned telemetry. It exposes Bitmain CGI paths only β€” there is no root route (so `curl http://127.0.0.1:14021/` would return 404). To verify the mock directly, use a CGI path with Digest auth: `curl --digest -u root:root http://127.0.0.1:14021/cgi-bin/miner_type.cgi` 2. **Starts ORK.** `getOrk()` brings up the kernel and joins a freshly generated DHT topic. 3. **Starts a worker.** `startWorker(AM_S19XP, { ork })` instantiates the `AM_S19XP` manager class, mounts the protocol adapter, and joins the same DHT topic β€” ORK detects the new peer and pulls the worker's identity and capabilities. 4. **Registers a thing.** `manager.registerThing({ info, opts })` tells the worker about one device at `127.0.0.1:14021`. The worker stores the registration and begins polling the mock. 5. **Prints the IDs and waits.** The script logs ORK's public HRPC key and the device ID, then sits idle. Ctrl+C tears everything down. No App Node here? Right. App Node is the translator that lets non-Node consumers β€” browser UIs, AI agents over MCP β€” speak MDK Protocol to ORK. This script already speaks MDK Protocol over IPC, so it talks to ORK directly. See [`architecture.md#app-node`](/v0-2-0/concepts/architecture#app-node) for when App Node is mandatory. ## Cleanup `Ctrl+C` stops the mock, worker, and ORK cleanly. The script uses the default ORK root at `os.tmpdir()/mdk/` β€” safe to ignore, or remove with: ```bash rm -rf "$TMPDIR/mdk" /tmp/mdk ``` ## Continue Next: [2. Control devices from the CLI](/v0-2-0/tutorials/backend-stack/cli) β€” keep a stack running and drive it interactively (Whatsminer plus an MDK REPL). ## Next steps - For the four supported Antminer models (`AM_S19XP`, `AM_S19XPH`, `AM_S21`, `AM_S21PRO`) and how to swap them, see [`backend/workers/miners/antminer/USAGE.md`](https://github.com/tetherto/mdk/blob/main/backend/workers/miners/antminer/USAGE.md) - Run a full site (5 workers, 26 devices) β€” [`backend/core/examples/mdk-site/site.js`](https://github.com/tetherto/mdk/blob/main/backend/core/examples/mdk-site/site.js) - See ORK and a worker as separate OS processes β€” [`dht-worker.js`](https://github.com/tetherto/mdk/blob/main/backend/core/examples/mdk-e2e/dht-worker.js) + [`dht-ork.js`](https://github.com/tetherto/mdk/blob/main/backend/core/examples/mdk-e2e/dht-ork.js) + [`client.js`](https://github.com/tetherto/mdk/blob/main/backend/core/examples/mdk-e2e/client.js) - Understand the install pattern for any worker β€” [`backend/workers/docs/install-pattern.md`](https://github.com/tetherto/mdk/blob/main/backend/workers/docs/install-pattern.md) - Use ORK directly without `getOrk()` β€” [`backend/workers/docs/orchestrator.md`](https://github.com/tetherto/mdk/blob/main/backend/workers/docs/orchestrator.md) - Read all runnable examples in one place β€” [`backend/core/examples/README.md`](https://github.com/tetherto/mdk/blob/main/backend/core/examples/README.md) # Run a demo from stack to dashboard (/v0-2-0/tutorials/full-stack/dashboard) *Get started Β· 3 of 3 Β· Run the dashboard demo* If ORK, worker, manager, or thing are unfamiliar, read [`terminology.md`](/v0-2-0/concepts/terminology) first. ## Overview This is rung 3 of the [Get started](/v0-2-0/tutorials/backend-stack) ladder: **run**. It puts a real browser dashboard on top of the stack you already know, using the [`mdk-site-monitor`](https://github.com/tetherto/mdk/tree/main/examples/e2e) example. What you'll have at the end: - The same mock Whatsminer M56S stack from [rung 2](/v0-2-0/tutorials/backend-stack/cli), fronted by an App Node REST API on `:3000` - A React dashboard on `:3030` β€” total hashrate, total power, and active device count as metric cards, a live hashrate chart, and a per-device breakdown - The dashboard polling App Node every 5 seconds, built entirely from MDK UI components (`MetricCard`, `HashRateLineChart`, `Typography`, ...) Same shape as rungs 1 and 2: underneath, the stack still boots with `getOrk()`, `startWorker()`, and `registerThing()`, and the device is the same Whatsminer M56S mock as [rung 2](/v0-2-0/tutorials/backend-stack/cli). What's new is two layers on top β€” App Node translating MDK Protocol to REST, and a Vite React UI consuming that REST. ## Prerequisites - Node.js >=24 or newer (LTS) - npm >=11 The stack starts an ORK whose control plane is peer-to-peer over a Hyperswarm DHT, so it needs outbound network access. Without it the stack stalls at startup while the ORK tries to reach DHT bootstrap nodes. See [how workers connect](/v0-2-0/concepts/worker-discovery) for the ORK/DHT mechanics. ### Clone and install the stack #### 1.1 Clone the repo ```bash git clone git@github.com:tetherto/mdk.git cd mdk ``` #### 1.2 Install dependencies ```bash backend/core/install-packages.sh ci backend/workers/install-packages.sh ci ``` ### Build the UI packages (one time) The dashboard imports pre-built MDK UI packages from `ui`. Build them once: ```bash cd ui npm install npm run build cd - ``` This is the slow step β€” it compiles the UI workspace. You only do it once; re-running the dashboard later skips straight to the next step. ### Install the dashboard's dependencies ```bash cd examples/e2e/ui npm install cd - ``` ### Start everything `start.js` is an interactive launcher. From the repo root: ```bash node examples/e2e/start.js ``` It prints the available services and a prompt. Type `start all` to launch the backend and UI together: ``` mdk> start all ``` This starts: 1. **App Node** (`server.js --app-node`) β€” ORK + mock Whatsminer + a REST API on `http://localhost:3000`, in `noAuth` mode 2. **UI** β€” a Vite dev server on `http://localhost:3030`, launched with `VITE_NO_AUTH=true` so it skips the login screen Wait a few seconds for both to come up, then open: ``` http://localhost:3030 ``` You'll see the dashboard populate within ~5 seconds (its first poll), then update live. Other launcher commands: ``` status β€” show running services and their URLs stop [ork|app-node|ui|all] β€” stop a service (default: all) start [ork|app-node|ui|all] β€” start a service (default: all) help β€” show usage exit β€” stop everything and quit ``` `start all` runs App Node in `noAuth` mode for development convenience. Do not expose port 3000 outside localhost. ## What you'll see ```mermaid flowchart TD mock["Mock Whatsminer M56S (TCP :14028)"] --> worker["MDK worker"] worker --> ork["ORK (IPC socket)"] ork --> appnode["App Node (HTTP :3000)"] appnode -->|"GET /site-monitor/hashrate every 5s"| ui["Dashboard (Vite :3030)"] ``` The page renders three metric cards (Total Hashrate in TH/s, Total Power in W, Active Devices), a live hashrate line chart that grows as new data points arrive, and a per-device table. All of it comes from `@tetherto/mdk-react-devkit` components driven by the `@tetherto/mdk-react-adapter` data hooks β€” no hand-rolled UI. See [`SiteHashratePage.tsx`](https://github.com/tetherto/mdk/blob/main/examples/e2e/ui/src/SiteHashratePage.tsx) for the page source. ## Cleanup At the launcher prompt: ``` mdk> exit ``` `exit` stops App Node, the mock, ORK, and the UI dev server. `server.js` leaves data under `os.tmpdir()/mdk/` β€” safe to ignore, or remove with: ```bash rm -rf "$TMPDIR/mdk" /tmp/mdk ```
Add real auth (Google OAuth) The `start all` shortcut above runs in `noAuth` mode so you can see the dashboard immediately. To exercise the real authentication flow β€” a Google sign-in that issues a bearer token the dashboard sends on every request β€” configure OAuth and start the services without the `noAuth` shortcut. **1. Create a Google OAuth 2.0 client** 1. Go to [Google Cloud Console API credentials](https://console.cloud.google.com/apis/credentials) 2. Create an **OAuth 2.0 Client ID** (type: *Web application*) 3. Add to **Authorized redirect URIs**: `http://localhost:3000/oauth/google/callback` 4. Add to **Authorized JavaScript origins**: `http://localhost:3030` 5. Copy the **Client ID** and **Client Secret** **2. Configure App Node** Edit `backend/core/app-node/config/facs/httpd-oauth2.config.json` (copy from `.example` if it doesn't exist) and fill in your client ID and secret: ```json { "h0": { "method": "google", "credentials": { "client": { "id": "", "secret": "" } }, "startRedirectPath": "/oauth/google", "callbackUri": "http://localhost:3000/oauth/google/callback", "callbackUriUI": "http://localhost:3000", "users": [] } } ``` **3. Set yourself as super-admin** In `backend/core/app-node/config/facs/auth.config.json`, set `superAdmin` to your Google account email: ```json { "a0": { "superAdmin": "you@example.com" } } ``` **4. Run with auth** Start App Node and the UI without the `VITE_NO_AUTH` shortcut (run them directly rather than via `start all`), open `http://localhost:3030`, click **Sign in with Google**, and authorise with the super-admin email. The dashboard then shows live data behind the token. For the full setup, see the example's [`README.md`](https://github.com/tetherto/mdk/tree/main/examples/e2e).
## Continue Previous: [2. Control devices from the CLI](/v0-2-0/tutorials/backend-stack/cli) You've climbed the whole ladder: observed a stack, driven it from a CLI, and built a live UI on it. ## Go deeper - The full example, including the OAuth setup and how to add a new data panel β€” [`examples/e2e/README.md`](https://github.com/tetherto/mdk/tree/main/examples/e2e) - The MDK UI toolkit (components, hooks, theming) β€” [`ui/README.md`](https://github.com/tetherto/mdk/blob/main/ui/README.md) - Run a full site (5 workers, 26 devices) β€” [`backend/core/examples/mdk-site/site.js`](https://github.com/tetherto/mdk/blob/main/backend/core/examples/mdk-site/site.js) - Read all runnable examples in one place β€” [`backend/core/examples/README.md`](https://github.com/tetherto/mdk/blob/main/backend/core/examples/README.md) # Your first component (/v0-2-0/tutorials/ui/react/first-component) This tutorial assists first-time users to understand how to render a single MDK component in a React app as fast as possible. This path skips `` which works for presentational `/core` and `/foundation` imports. For adapter hooks or connected foundation components, continue with [Wire a React app](/v0-2-0/tutorials/ui/react/tutorial). ## Prerequisites - **Node.js** >=24 - **npm** >=11 - **React** 19+ and **react-dom** 19+ {/* npm packages are not yet published to the registry; clone the MDK UI monorepo and use workspace dependencies until publish. */} ### Clone and build The MDK UI (`mdk/ui`) is an npm workspace. Clone the repository, install at the workspace root, and build all packages before adding an app or running the demo. #### 1.1 Clone the repository ```bash git clone https://github.com/tetherto/mdk.git cd mdk/ui ``` ```bash # Requires an SSH key configured for your GitHub account git clone git@github.com:tetherto/mdk.git cd mdk/ui ``` #### 1.2 Install and build ```bash npm install npm run build ``` This builds `@tetherto/mdk-react-devkit` and the other packages in the `ui/` workspace. ### Create your app #### 2.1 Scaffold a Vite React app Create a new React app in the `apps/` folder: ```bash cd apps npm create vite@latest my-app -- --template react-ts cd my-app ``` Ignore the CLI "Now run" instructions β€” follow the MDK-specific steps below. #### 2.2 Add MDK to `package.json` Open `package.json` and add the workspace dependency: ```jsonc "@tetherto/mdk-react-devkit": "*", ``` ```jsonc { "name": "my-app", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview" }, "dependencies": { "@tetherto/mdk-react-devkit": "*", // [!code ++] "react": "^19.2.6", // [!code highlight] "react-dom": "^19.2.6" // [!code highlight] }, } ``` Vite also adds a `devDependencies` block (`eslint`, `typescript`, `vite`, `@types/*`); leave that block as is. #### 2.3 Install from the workspace root ```bash cd ../.. # from apps/my-app back up to the workspace root mdk/ui npm install ``` ### Import styles #### 3.1 Import the MDK stylesheet Import the MDK stylesheet once in your app's entry point (typically `src/main.tsx`): ```tsx ``` ```tsx createRoot(document.getElementById('root')!).render( , ) ``` ### Render your first component > This example replaces `App.tsx` with a single mining metric tile that toggles between a normal and an alarmed state. #### 4.1 Replace `App.tsx` with the demo This pulls a button from `@tetherto/mdk-react-devkit/core` and a domain component (`SingleStatCard`) from `@tetherto/mdk-react-devkit/foundation`: ```tsx function App() { const [overheating, setOverheating] = useState(false) return (

Miner status

) } ``` #### 4.2 Run your app From the workspace root mdk/ui, start the dev server for your app. The `-w` name must match the `name` field in `apps/my-app/package.json` (`my-app` from step 2.1): ```bash npm -w my-app run dev ``` Alternatively, from the app folder: ```bash cd apps/my-app npm run dev ``` You should see a card showing `Inlet temperature 28 Β°C` with a **Simulate overheat** button below it. Click the button and the card flips into an alarm state: red border, red text, value jumps to `78 Β°C`, and the whole card pulses. Click **Cool down** to reset. That is `@tetherto/mdk-react-devkit/core` (the `Button`) and `@tetherto/mdk-react-devkit/foundation` (the `SingleStatCard`) working together: generic primitives plus mining-domain components, in one app.
## Next steps - To browse the full kit in one app first, see [Explore the demo](/v0-2-0/ui/react/explore-the-demo) - [Wire a React app](/v0-2-0/tutorials/ui/react/tutorial): add `` and adapter hooks for connected foundation components - [Quickstart](/v0-2-0/ui/react/quickstart): minimum integration reference (install, provider, stores, theming) - [Core](/v0-2-0/ui/react/core): primitives reference (`@tetherto/mdk-react-devkit/core`) - [Foundation](/v0-2-0/ui/react/foundation): mining-domain components (`@tetherto/mdk-react-devkit/foundation`) # Wire a React app (/v0-2-0/tutorials/ui/react/tutorial) This tutorial walks you through the full React stack: three workspace packages, ``, and adapter hooks. For a faster path that renders one presentational component without the provider, see [Your first component](/v0-2-0/tutorials/ui/react/first-component). ## Prerequisites - **Node.js** >=24 - **npm** >=11 - **React** 19+ and **react-dom** 19+ {/* npm packages are not yet published to the registry; clone the MDK UI monorepo and use workspace dependencies until publish. */} ### Clone and build The `mdk/ui` directory is the npm workspace root. Clone the repository, install at the workspace root, and build all packages before adding an app or running the demo. #### 1.1 Clone the repository ```bash git clone https://github.com/tetherto/mdk.git cd mdk/ui ``` ```bash # Requires an SSH key configured for your GitHub account git clone git@github.com:tetherto/mdk.git cd mdk/ui ``` #### 1.2 Install and build ```bash npm install npm run build ``` This builds `@tetherto/mdk-react-devkit` and the other packages in the `ui/` workspace. ### Create your app #### 2.1 Scaffold a Vite React app Create a new React app in the `apps/` folder: ```bash cd apps npm create vite@latest my-app -- --template react-ts cd my-app ``` Ignore the CLI "Now run" instructions β€” follow the MDK-specific steps below. #### 2.2 Add MDK to `package.json` Open `package.json` and add all three workspace packages: ```jsonc "@tetherto/mdk-react-devkit": "*", "@tetherto/mdk-react-adapter": "*", "@tetherto/mdk-ui-core": "*", ``` ```jsonc { "name": "my-app", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview" }, "dependencies": { "@tetherto/mdk-react-devkit": "*", // [!code ++] "@tetherto/mdk-react-adapter": "*", // [!code ++] "@tetherto/mdk-ui-core": "*", // [!code ++] "react": "^19.2.6", // [!code highlight] "react-dom": "^19.2.6" // [!code highlight] }, } ``` Vite also adds a `devDependencies` block (`eslint`, `typescript`, `vite`, `@types/*`); leave that block as is. #### 2.3 Install from the workspace root ```bash cd ../.. # from apps/my-app back up to the workspace root mdk/ui npm install ``` ### Wrap your app in MdkProvider `` wires the headless stores from [`@tetherto/mdk-ui-core`](/v0-2-0/reference/app-toolkit/ui-core) into React and sets up the TanStack `QueryClient`. It is required for adapter hooks and connected foundation components. See also [Quickstart β€” Wrap your app in MdkProvider](/v0-2-0/ui/react/quickstart#wrap-your-app-in-mdkprovider). #### 3.1 Update `main.tsx` ```tsx ``` Wrap ``: ```tsx ``` ```tsx createRoot(document.getElementById('root')!).render( {/* [!code ++] */} {/* [!code ++] */} , ) ``` ### Use adapter hooks and render a component Adapter hooks subscribe to Zustand stores and re-render when the selected slice changes. This example uses `useAuth` and `useDevices` from the adapter, then renders a presentational `SingleStatCard` from foundation. #### 4.1 Replace `App.tsx` ```tsx function App() { const { permissions } = useAuth() const { selectedDevices } = useDevices() return (

Operator dashboard

Selected devices: {selectedDevices?.length ?? 0}

Permissions loaded: {permissions ? 'yes' : 'no'}

) } ``` Connected foundation components (device explorers, pool manager, settings dashboards) expect `` and often read multiple stores. Start with presentational imports while you wire your API; swap in connected components as your backend comes online.
### Run your app From the workspace root `mdk/ui`: ```bash npm -w my-app run dev ``` Or from the app folder: ```bash cd apps/my-app npm run dev ``` You should see the operator dashboard with device and permission readouts plus a temperature card. The adapter hooks confirm `` is wired β€” without it, they would throw.
## Next steps - [Quickstart](/v0-2-0/ui/react/quickstart): install reference, store hooks, and theming - [Core](/v0-2-0/ui/react/core): primitives reference (`@tetherto/mdk-react-devkit/core`) - [Foundation](/v0-2-0/ui/react/foundation): mining-domain components (`@tetherto/mdk-react-devkit/foundation`) ### Run the demo app If you are already at the workspace root `mdk/ui`: ```bash npm run dev:catalog ``` If your shell is still in the app folder: ```bash cd ../.. # apps/my-app β†’ apps β†’ workspace root mdk/ui npm run dev:catalog ``` Open [http://localhost:5173/mdk](http://localhost:5173/mdk) to browse examples. # UI Kit (/v0-2-0/ui) ## TL;Dr Today the published React path is complete; other frameworks are on the [roadmap](/v0-2-0/resources/roadmap) and will reuse the same headless core. The MDK React UI Kit is a [three-layer stack](#how-the-layers-fit-together): - [`@tetherto/mdk-ui-core`](/v0-2-0/reference/app-toolkit/ui-core): headless Zustand stores and TanStack Query client factory (framework-agnostic) - [`@tetherto/mdk-react-adapter`](/v0-2-0/ui/react/quickstart#wrap-your-app-in-mdkprovider): `` and store hooks; binds the core into your UI runtime (React today) - [`@tetherto/mdk-react-devkit`](/v0-2-0/ui/react/get-started): [`/core`](/v0-2-0/ui/react/core) primitives and [`/foundation`](/v0-2-0/ui/react/foundation) mining-domain components ## Framework quickstarts } title={React} href="/v0-2-0/ui/react/get-started" description={ Get started with the React MDK UI Kit } /> Vue} href="/v0-2-0/resources/roadmap" description={ <> Reactive hooks for Vue via @tetherto/mdk-vue Learn about the release schedule β†’ } /> Svelte} href="/v0-2-0/resources/roadmap" description={ <> Reactive hooks for Svelte via @tetherto/mdk-svelte Learn about the release schedule β†’ } /> Web Components} href="/v0-2-0/resources/roadmap" description={ <> Framework-agnostic Web Components via @tetherto/wc Learn about the release schedule β†’ } /> ## How the layers fit together The MDK React UI Kit and subsequent frameworks will share a similar architecture: ```mermaid graph TB subgraph headless [Layer 1: Headless] uiCore([mdk-ui-core
Zustand stores Β· Query client factory
Framework-agnostic]) end subgraph adapter [Layer 2: Framework adapter] reactAdapter([mdk-react-adapter
MdkProvider Β· store hooks
React today]) end subgraph uiLib [Layer 3: Framework UI library] reactDevkit([mdk-react-devkit
/core + /foundation
Primitives and mining-domain UI]) end uiCore --> reactAdapter reactAdapter --> reactDevkit style headless fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A style adapter fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A style uiLib fill:#F7931A,stroke:#1A1A1A,color:#1A1A1A ``` ## Next steps - [Quickstart](/v0-2-0/ui/react/quickstart): integrate the three packages into your React app - [Explore the demo](/v0-2-0/ui/react/explore-the-demo): run the demo app in your browser # @tetherto/mdk-react-devkit (/v0-2-0/ui/react/core) [The core entrypoint](/v0-2-0/resources/repositories) (`@tetherto/mdk-react-devkit/core`) provides the foundational **React** UI components, utilities, and theme system for the MDK React UI Kit. This is the `/core` subpath of the devkit package, not the framework-agnostic [`@tetherto/mdk-ui-core`](/v0-2-0/reference/app-toolkit/ui-core) headless package. Mining-domain components, hooks, and settings live on the [`/foundation`](/v0-2-0/ui/react/foundation) entrypoint. Both subpaths are exported from `@tetherto/mdk-react-devkit`. ## Prerequisites - Complete the installation ```bash # Clone the MDK UI monorepo (adjust the URL to your fork if needed) git clone https://github.com/tetherto/mdk.git cd mdk/ui # Install dependencies and build packages (npm workspaces) npm install npm run build ``` - Add the dependency to your app's `package.json` ```json { "dependencies": { "@tetherto/mdk-react-devkit": "*", "@tetherto/mdk-react-adapter": "*", "@tetherto/mdk-ui-core": "*" } } ``` > **Coming soon** β€” npm packages are not yet published. Use the monorepo setup for now. ```bash npm install \ @tetherto/mdk-react-devkit \ @tetherto/mdk-react-adapter \ @tetherto/mdk-ui-core ``` Run `npm install` from the `mdk/ui` workspace root after your app is under `apps/` so npm links workspace packages. ## What's included ### Components Production-ready React components organized by category: | Category | Description | |----------|-------------| | [Forms](/v0-2-0/ui/react/core/components/forms) | Input and form control components | | [Overlays](/v0-2-0/ui/react/core/components/overlays) | Dialogs, popovers, tooltips, and toasts | | [Data display](/v0-2-0/ui/react/core/components/data-display) | Cards, tables, tags, and data presentation | | [Charts](/v0-2-0/ui/react/core/components/charts) | Data visualization components | | [Navigation](/v0-2-0/ui/react/core/components/navigation) | Sidebar, tabs, and breadcrumbs | | [Loading](/v0-2-0/ui/react/core/components/loading) | Spinners, loaders, and progress indicators | | [Errors](/v0-2-0/ui/react/core/components/errors) | Error boundaries, error cards, and alerts | | [Logs](/v0-2-0/ui/react/core/components/logs) | Log display components | See the [Components reference](/v0-2-0/ui/react/core/components) for the full list with demo links. ### Icons 70+ purpose-built icons for Bitcoin mining applications: - Navigation icons (Dashboard, Farms, Inventory, etc.) - Status icons (Mining, Offline, Error, etc.) - Weather icons (Sunny, Cloudy, Rainy, etc.) - Alarm icons (Temperature, Pressure, Fluid, etc.) ### Utilities Helper functions for common operations: - [`formatNumber`](/v0-2-0/reference/app-toolkit/ui-kit/utilities#formatnumber), [`formatHashrate`](/v0-2-0/reference/app-toolkit/ui-kit/utilities#formathashrate), [`formatCurrency`](/v0-2-0/reference/app-toolkit/ui-kit/utilities#formatcurrency): Number formatting - [`formatDate`](/v0-2-0/reference/app-toolkit/ui-kit/utilities#formatdate), [`formatRelativeTime`](/v0-2-0/reference/app-toolkit/ui-kit/utilities#formatrelativetime): Date formatting - [`cn`](/v0-2-0/reference/app-toolkit/ui-kit/utilities#cn): Class name merging ([clsx](https://github.com/lukeed/clsx) wrapper) - Conversion utilities for energy, hashrate, and units - Validation utilities for email, URL, and empty checks ### Theme system CSS custom properties and utilities for consistent styling: - Color tokens (primary, gray scales) - Spacing scale - Border radius scale - Font size scale - Light/dark mode support ### Types TypeScript types for type-safe development: - [`ComponentSize`](/v0-2-0/reference/app-toolkit/ui-kit/types#componentsize), [`ButtonVariant`](/v0-2-0/reference/app-toolkit/ui-kit/types#buttonvariant), [`ColorVariant`](/v0-2-0/reference/app-toolkit/ui-kit/types#colorvariant) - [`Status`](/v0-2-0/reference/app-toolkit/ui-kit/types#status), [`PaginationParams`](/v0-2-0/reference/app-toolkit/ui-kit/types#paginationparams), [`ApiResponse`](/v0-2-0/reference/app-toolkit/ui-kit/types#apiresponse) - Chart types, table types, and utility types ## Reference Detailed reference material lives in the unified [Reference section](/v0-2-0/reference). Core (`/core`) slices are: - [Constants](/v0-2-0/reference/app-toolkit/ui-kit/constants#core-constants): colors, units, currency, chart configs - [Types](/v0-2-0/reference/app-toolkit/ui-kit/types#core-types): UI primitives, API shapes, value-unit types - [Utilities](/v0-2-0/reference/app-toolkit/ui-kit/utilities#core-utilities): formatting, validation, conversions, `cn` and friends ## Import examples ```tsx // Import components // Import utilities // Import types // Import icons // Import styles (required for component styling) ``` ## Troubleshooting - **`npm install` warns about `engines.npm`, or `npm -v` is 10.x on Node 22** β€” [Upgrade npm](/v0-2-0/how-to/ui/react/upgrade-npm), then rerun `npm install` and `npm run build` from the `mdk/ui` workspace root. # Components (/v0-2-0/ui/react/core/components) The `@tetherto/mdk-react-devkit/core` package provides production-ready React components. All components are built with accessibility in mind and support both light and dark themes. ## Import ### Prerequisites - Complete the [installation](/v0-2-0/ui/react/quickstart) ```bash # Clone the MDK UI monorepo (adjust the URL to your fork if needed) git clone https://github.com/tetherto/mdk.git cd mdk/ui # Install dependencies and build packages (npm workspaces) npm install npm run build ``` - Import the core styles in your app's entry point: ```tsx ``` ### Import named components Declare the components you require: ```tsx ``` ## Supported components ## Styling Components use BEM-style CSS classes (e.g., `.mdk-button`, `.mdk-card__header`) for styling consistency and easy customization. ## Component design principles - **Composable**: Components are designed to work together - **Accessible**: Built with ARIA attributes and keyboard navigation - **Themeable**: Support light/dark modes via CSS custom properties - **Type-safe**: Full TypeScript support with exported prop types # Chart components (/v0-2-0/ui/react/core/components/charts) Chart components for visualizing time-series data, metrics, and statistics. Built on [Chart.js](https://www.chartjs.org/) and [Lightweight Charts](https://tradingview.github.io/lightweight-charts/), supporting: - Chart types: components that render datasets (`AreaChart`, `BarChart`, `DoughnutChart`, `GaugeChart`, `LineChart`) - [Composition elements](/v0-2-0/ui/react/core/components/charts/composition): layout chrome, legends, stats rows, and chart utilities (`ChartContainer`, `ChartStatsFooter`, `DetailLegend`, helpers) ## Prerequisites - Complete the [@tetherto/mdk-react-devkit installation and add the dependency](/v0-2-0/ui/react/core#prerequisites) ## All chart type components ## `AreaChart` Presentational Chart.js area chart (line series with fill). Data and options follow the same Chart.js model as [`LineChart`](#linechart), without a separate MDK data wrapper type. ### Import ```tsx ``` ### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `data` | Required | `ChartData` | none | [Chart.js](https://www.chartjs.org/) line chart `data` (datasets typically use `fill: true` for an area look) | | `options` | Optional | `ChartOptions` | none | [Chart.js](https://www.chartjs.org/) options (merged with defaults) | | `tooltip` | Optional | `ChartTooltipConfig` | none | Custom HTML tooltip config; when set, replaces the default Chart.js tooltip | | `height` | Optional | `number` | `300` | Chart height in pixels | | `className` | Optional | `string` | none | Root class name from the host app | ### Basic usage ```tsx const data = { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], datasets: [ { label: 'Revenue', data: [100, 120, 115, 134, 168], fill: true, backgroundColor: 'rgba(114, 245, 158, 0.2)', borderColor: '#72F59E', }, ], } ``` ## `BarChart` The `BarChart` component renders Chart.js bar or column series. It manages: 1. **Data** (`data`): labels and datasets passed to Chart.js. 2. **Chart options** (`options`): merged with MDK defaults. 3. **Formatting** (`formatYLabel`, `formatDataLabel`, `tooltip`): axis labels, data labels, and HTML tooltips. 4. **Presentation** (`isStacked`, `isHorizontal`, `showLegend`, `legendPosition`, `legendAlign`, `showDataLabels`): layout and legend. 5. **Size** (`height`): chart height in pixels. ### Import ```tsx ``` ### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `data` | Required | `ChartData` | none | [Chart.js](https://www.chartjs.org/) data object | | `options` | Optional | `ChartOptions` | none | [Chart.js](https://www.chartjs.org/) options (merged with defaults) | | `formatYLabel` | Optional | `function` | none | Format Y-axis tick labels | | `formatDataLabel` | Optional | `function` | none | Format data label values | | `tooltip` | Optional | `ChartTooltipConfig` | none | Custom HTML tooltip config | | `isStacked` | Optional | `boolean` | `false` | Stack bars on top of each other | | `isHorizontal` | Optional | `boolean` | `false` | Render bars horizontally | | `showLegend` | Optional | `boolean` | `true` | Show [Chart.js](https://www.chartjs.org/) legend | | `legendPosition` | Optional | `'top' \| 'right' \| 'bottom' \| 'left'` | `'top'` | Legend position | | `legendAlign` | Optional | `'start' \| 'center' \| 'end'` | `'start'` | Legend alignment | | `showDataLabels` | Optional | `boolean` | `false` | Show values above bars | | `height` | Optional | `number` | `300` | Chart height in pixels | ### Basic usage ```tsx const data = { labels: ['Jan', 'Feb', 'Mar', 'Apr'], datasets: [ { label: 'Hashrate (TH/s)', data: [120, 150, 180, 200], backgroundColor: '#72F59E', }, ], } ``` ### Data from hooks Hand-built Chart.js `data` (above) is valid. When app hooks return `{ labels, series }` declarative input, convert with [`buildBarChartData`](/v0-2-0/ui/react/core/components/charts/composition#hook-shaped-bar-data) in [chart utilities](/v0-2-0/ui/react/core/components/charts/composition#chart-utilities), optionally merge per-series `datalabels`, then pass the result to `data`. ### Stacked bar chart ```tsx const data = { labels: ['Site A', 'Site B', 'Site C'], datasets: [ { label: 'Online', data: [100, 80, 120], backgroundColor: '#72F59E' }, { label: 'Offline', data: [10, 20, 5], backgroundColor: '#FF6B6B' }, ], } ``` ### Horizontal bar chart ```tsx `${value} TH/s`} /> ``` ### With data labels ```tsx `${value.toFixed(1)}%`} /> ``` ## `DoughnutChart` ### Import ```tsx ``` ### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `data` | Required | `DoughnutChartDataset[]` | none | Array of `{ label, value, color? }` slices (see [data shape](#data-shape-for-doughnut-charts)) | | `unit` | Optional | `string` | `''` | Unit suffix shown in the default tooltip | | `options` | Optional | `ChartOptions` | none | [Chart.js](https://www.chartjs.org/) doughnut options (merged with defaults) | | `cutout` | Optional | `string` | `'75%'` | Inner radius cutout (doughnut ring thickness) | | `borderWidth` | Optional | `number` | `4` | Border width between segments | | `height` | Optional | `number` | `260` | Chart height in pixels | | `legendPosition` | Optional | `string` | `'top'` | Where to place the custom legend relative to the chart | | `tooltip` | Optional | `ChartTooltipConfig` | none | Custom HTML tooltip; when set, replaces the default doughnut tooltip | | `className` | Optional | `string` | none | Root class name from the host app | #### Data shape for doughnut charts Pass `data` as an array of `{ label, value, color? }`. The chart maps this into Chart.js internally (`labels`, single dataset, segment colors). Omit `color` to use the default palette rotation. ### Basic usage ```tsx const data = [ { label: 'Online', value: 85, color: '#72F59E' }, { label: 'Offline', value: 10, color: '#FF6B6B' }, { label: 'Maintenance', value: 5, color: '#FFD700' }, ] ``` ## `GaugeChart` Speedometer-style presentational arc gauge for displaying a single normalized value, providing: 1. **Percent** (`percent`): fill level from `0` to `1` (values outside that range are clamped). 2. **Arc appearance** (`colors`, `arcWidth`, `nrOfLevels`): segment colors, relative thickness, and segment count. 3. **Center label** (`hideText`): percentage text in the center of the gauge (hidden when `hideText` is `true`). 4. **Accessibility** (`id`): stable id wired into SVG labels (defaults to `mdk-gauge-chart`). 5. **Layout** (`height`, `maxWidth`, `className`): container height, max width, and host root class. The `GaugeChart` is SVG-based; strokes and labels may paint past the host element’s layout bounds. For a hard edge, wrap the gauge in an element with **`overflow: hidden`**, or add vertical spacing. [`ChartContainer`](/v0-2-0/ui/react/core/components/charts/composition#chartcontainer) does **not** set overflow clipping on the chart slotβ€”it is for title, loading/empty, and footer chrome like other charts (see [Composition](/v0-2-0/ui/react/core/components/charts/composition)); it may add spacing that makes overlap less visible, but it is not a substitute for clipping when you need it. ### Import ```tsx ``` The gauge is driven by a **normalized** `percent` in the range **0–1** (for example `0.75` is 75%). It is not a `value` / `max` API, and it does not accept `label` or `unit` props (see [`ChartContainer`](/v0-2-0/ui/react/core/components/charts/composition#chartcontainer) for layout notes). ### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `percent` | Required | `number` | none | Fill level from `0` to `1` (clamped) | | `colors` | Optional | `string[]` | green β†’ soft green β†’ red (theme) | Arc segment colors as HEX strings | | `arcWidth` | Optional | `number` | `0.2` | Arc thickness as a fraction of the gauge radius (`0`–`1`) | | `nrOfLevels` | Optional | `number` | `3` | Number of colored segments on the arc | | `hideText` | Optional | `boolean` | `false` | Hide the percentage text in the center | | `id` | Optional | `string` | `'mdk-gauge-chart'` | Stable id for SVG accessibility labels | | `height` | Optional | `number` \| `string` | `200` | Container height (pixels or CSS length, e.g. `'50%'`) | | `maxWidth` | Optional | `number` | `500` | Maximum width in pixels | | `className` | Optional | `string` | none | Root class name from the host app | ### Basic usage ```tsx ``` ### In a chart card (same shell as other charts) [`ChartContainer`](/v0-2-0/ui/react/core/components/charts/composition#chartcontainer) adds title, loading, and empty chrome; it does **not** apply `overflow: hidden` to the chart body. When you still see bleed next to siblings or footers, wrap `GaugeChart` in an extra element with **`overflow: hidden`**. ```tsx
``` ### Custom colors and arc ```tsx ``` ## `LineChart` The `LineChart` component renders time-series lines. It manages: 1. **Series data** (`data`): `LineChartData` with millisecond `x` values (see [data shape](#data-shape-for-line-charts)). 2. **Context** (`timeline`, `unit`): timeline identifier and unit label for tooltips. 3. **Formatting** (`yTicksFormatter`, `priceFormatter`, `customLabel`): tick and tooltip text. 4. **Presentation** (`backgroundColor`, `beginAtZero`, `showPointMarkers`, `showDateInTooltip`, `uniformDistribution`): axis and marker behavior. 5. **Size** (`height`): chart height in pixels. ### Import ```tsx ``` ### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `data` | Required | `LineChartData` | none | Object with a `datasets` array (see [data shape](#data-shape-for-line-charts)) | | `timeline` | Optional | `string` | none | Timeline identifier | | `yTicksFormatter` | Optional | `function` | none | Format Y-axis ticks | | `priceFormatter` | Optional | `function` | none | Format tooltip values | | `customLabel` | Optional | `string` | none | Custom tooltip label | | `unit` | Optional | `string` | `''` | Unit label for values | | `backgroundColor` | Optional | `string` | none | Chart background color | | `beginAtZero` | Optional | `boolean` | `false` | Start Y-axis at zero | | `showPointMarkers` | Optional | `boolean` | `false` | Show data point markers | | `showDateInTooltip` | Optional | `boolean` | `false` | Show date in tooltip | | `uniformDistribution` | Optional | `boolean` | `false` | Uniform time distribution | | `height` | Optional | `number` | `240` | Chart height in pixels | #### Data shape for line charts `LineChartData` is `{ datasets: LineDataset[] }`. Each `LineDataset` has `label`, `borderColor`, optional `visible` / `borderWidth` / `extraTooltipData`, and `data: { x, y }[]` where `x` is a time value in **milliseconds** (for example from `Date.prototype.valueOf()`) and `y` is the series value (number, or `null` / `undefined` for gaps). ### Basic usage ```tsx const data = { datasets: [ { label: 'Hashrate', borderColor: '#72F59E', data: [ { x: 1704067200000, y: 150 }, { x: 1704153600000, y: 155 }, { x: 1704240000000, y: 160 }, ], }, ], } ``` ### Multiple lines ```tsx const hashrateData = [ { x: 1704067200000, y: 150 }, { x: 1704153600000, y: 155 }, ] const targetData = [ { x: 1704067200000, y: 140 }, { x: 1704153600000, y: 145 }, ] const data = { datasets: [ { label: 'Hashrate', borderColor: '#72F59E', data: hashrateData, }, { label: 'Target', borderColor: '#FFD700', data: targetData, }, ], } ``` # Chart composition (/v0-2-0/ui/react/core/components/charts/composition) Compose chart types from [Chart components](/v0-2-0/ui/react/core/components) with wrappers and helpers. `ChartContainer` adds title, loading, and empty states; `ChartStatsFooter` and `DetailLegend` add summary rows and rich legends (for example [`LineChartCard`](/v0-2-0/ui/react/foundation/dashboard/charts#linechartcard)). ## Prerequisites - Complete the [@tetherto/mdk-react-devkit installation and add the dependency](/v0-2-0/ui/react/core#prerequisites) ## Components ## `ChartContainer` ### Import ```tsx ``` ### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `children` | Required | `ReactNode` | none | Chart body (for example a `BarChart` or `LineChart`) | | `title` | Optional | `string` | none | Title text | | `header` | Optional | `ReactNode` | none | Custom header node | | `legendData` | Optional | `LegendItem[]` | none | Legend entries rendered in the container chrome | | `highlightedValue` | Optional | `object` | none | Highlighted metric (`value`, optional `unit`, `className`, `style`); highlight chrome renders only when this prop is set and the chart body is visible (not `loading` / not `empty`) | | `rangeSelector` | Optional | `object` | none | Range selector props (`options`, `value`, `onChange`, optional `className`, `style`, `buttonClassName`) | | `loading` | Optional | `boolean` | none | When `true`, shows a centered `Loader` overlay over the chart area | | `empty` | Optional | `boolean` | none | When `true`, shows empty state | | `emptyMessage` | Optional | `string` | `'No data available'` | Copy shown in the empty overlay when `empty` is `true` and `loading` is not `true` | | `minMaxAvg` | Optional | `object` | none | Min / max / avg strings for the summary row | | `timeRange` | Optional | `string` | none | Time range label | | `footer` | Optional | `ReactNode` | none | Footer slot | | `footerClassName` | Optional | `string` | none | Class name on the footer wrapper | | `onToggleDataset` | Optional | `function` | none | Called with dataset index when legend toggles visibility | | `className` | Optional | `string` | none | Root class name from the host app | ### Basic usage ```tsx ``` ## `ChartStatsFooter` ### Import ```tsx ``` ### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `minMaxAvg` | Optional | `object` | none | Min, max, and average strings shown in the primary row when provided | | `stats` | Optional | `ChartStatsFooterItem[]` | none | Additional stat rows (`label`, `value`) in a columnar grid | | `statsPerColumn` | Optional | `number` | `1` | Number of stat items per column when `stats` is set | | `secondaryLabel` | Optional | `object` | none | Secondary block with `title` and `value` when provided | | `className` | Optional | `string` | none | Root class name from the host app | The component renders **nothing** when `minMaxAvg`, `stats`, and `secondaryLabel` are all absent or empty. ### Basic usage ```tsx ``` ## `DetailLegend` ### Import ```tsx ``` ### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `items` | Required | `DetailLegendItem[]` | none | Legend rows (`label`, `color`, optional `icon`, `currentValue`, `percentChange`, `hidden`) | | `onToggle` | Optional | `function` | none | Called with `label` and index when a row is toggled | | `className` | Optional | `string` | none | Root class name from the host app | ### Basic usage ```tsx ``` ## Chart utilities Pure functions for building Chart.js data and options. Import from the package root alongside components. ```tsx defaultChartOptions, defaultChartColors, buildBarChartData, buildBarChartOptions, buildChartTooltip, computeStats, } from '@tetherto/mdk-react-devkit/core' ``` | Export | Role | |--------|------| | `defaultChartOptions` | Shared Chart.js defaults used by MDK chart components | | `defaultChartColors` | Default dataset color palette | | `buildBarChartData` | Map MDK bar input into Chart.js `data` | | `buildBarChartOptions` | Build bar chart options (stacking, axes, formatters) | | `buildChartTooltip` | HTML tooltip config for Chart.js | | `computeStats` | Min, max, and average for a numeric array | Types `BarChartInput`, `BarChartSeries`, `BarChartLine`, and `BarChartConstant` are exported from the same package for hook-shaped bar data. ### Hook-shaped bar data App and reporting hooks often return declarative bar input instead of Chart.js `data`. `buildBarChartData` converts that shape into `{ labels, datasets }` for [`BarChart`](/v0-2-0/ui/react/core/components/charts#barchart). The pipeline: 1. **Input** (`BarChartInput`): optional `labels`, required `series`, optional `lines` and `constants` for mixed bar/line overlays. 2. **Build** (`buildBarChartData`): returns Chart.js `data` with MDK gradient styling and layout defaults (`barWidth`, `categoryPercentage`, `barPercentage` are optional on the input). 3. **Data labels** (optional): per-series overrides on the input (`formatter`, `anchor`, `align`, `offset`, `font`, `padding`, `display`, `clamp`, `clip`) map to each built dataset’s Chart.js `datalabels` by series index. 4. **Render** (``): pass the built object to the component; pair with `buildBarChartOptions` when you need stacking, axes, or formatters. `BarChartInput` shape | Field | Type | Description | |-------|------|-------------| | `labels` | `string[]` | Optional category labels; omitted labels are derived from series `values` keys or indices | | `series` | `BarChartSeries[]` | Bar datasets (`label`, `values`, optional `color`, `stack`, `gradient`, bar layout props) | | `lines` | `BarChartLine[]` | Optional line overlays on the same chart | | `constants` | `BarChartConstant[]` | Optional horizontal reference lines | Each `BarChartSeries` uses `values` as either `number[]` (positional) or `Record` (keyed by category label). #### Example Hook output to `BarChart` example: ```tsx BarChart, buildBarChartData, ChartContainer, } from '@tetherto/mdk-react-devkit/core' // Typical shape returned by app/reporting data hooks const hookOutput = { labels: ['Q1', 'Q2', 'Q3'], series: [ { label: 'Revenue', values: [4.2, 3.8, 5.1], color: '#72F59E', dataLabels: { formatter: (v: number) => `${v.toFixed(1)}M`, anchor: 'end', align: 'top', }, }, { label: 'OpEx', values: [1.8, 2.0, 1.6], color: '#FFD700', }, ], } const cleanSeries = hookOutput.series.map(({ dataLabels: _dl, ...s }) => s) const base = buildBarChartData({ labels: hookOutput.labels, series: cleanSeries }) const chartData = { labels: base.labels, datasets: base.datasets.map((dataset, i) => { const overrides = hookOutput.series[i]?.dataLabels return overrides ? { ...dataset, datalabels: overrides } : dataset }), } const isEmpty = !hookOutput.series.length || hookOutput.series.every((s) => { const vals = Array.isArray(s.values) ? s.values : Object.values(s.values) return !vals.length || vals.every((v) => v === 0) }) ``` #### Empty and all-zero series Treat bar data as empty when any of the following is true: - `series` is missing or has length `0` - Every series has empty `values` (array or record) - Every numeric value across all series is `0` Prefer `ChartContainer` `empty` or a placeholder instead of rendering a flat chart. After building Chart.js `data`, you can also use [`useChartDataCheck`](/v0-2-0/reference/app-toolkit/hooks/components#usechartdatacheck) from `@tetherto/mdk-react-devkit/foundation` with `{ data: chartData }`. # Data display (/v0-2-0/ui/react/core/components/data-display) Components for displaying data in cards, tables, badges, tags, and other visual formats. ## Prerequisites - Complete the [@tetherto/mdk-react-devkit installation and add the dependency](/v0-2-0/ui/react/core#prerequisites) ## `DataTable` ### Import ```tsx // Types for column definitions DataTableColumnDef, DataTableSortingState, DataTablePaginationState, DataTableRowSelectionState, } from '@tetherto/mdk-react-devkit/core' ``` ### Basic usage ```tsx type Miner = { id: string name: string hashrate: number status: string } const columns: DataTableColumnDef[] = [ { accessorKey: 'name', header: 'Name', }, { accessorKey: 'hashrate', header: 'Hashrate', cell: ({ row }) => `${row.original.hashrate} TH/s`, }, { accessorKey: 'status', header: 'Status', }, ] function MinersTable() { return } ``` ### With sorting and pagination ```tsx const [sorting, setSorting] = useState([]) const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10, }) ``` ### With row selection ```tsx const [rowSelection, setRowSelection] = useState({}) ``` ### Styling - `.mdk-data-table`: Root container - `.mdk-data-table__header`: Header row - `.mdk-data-table__body`: Table body - `.mdk-data-table__row`: Data row - `.mdk-data-table__cell`: Table cell ## `Card` ### Import ```tsx ``` ### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `className` | Optional | `string` | none | Additional CSS class | | `onClick` | Optional | `function` | none | Click handler (adds clickable styling) | | `children` | Optional | `ReactNode` | none | Card content | ### Basic usage ```tsx Title Content goes here Actions ``` Default children are automatically wrapped in the body slot: ```tsx Title This content goes to the body automatically ``` ### Clickable card ```tsx navigate('/details')}> Click me Navigates on click ``` ### Styling - `.mdk-card`: Root container - `.mdk-card--clickable`: Applied when `onClick` is provided - `.mdk-card__header`: Header slot - `.mdk-card__body`: Body slot - `.mdk-card__footer`: Footer slot ## `Accordion` ### Import ```tsx Accordion, AccordionItem, AccordionTrigger, AccordionContent, } from '@tetherto/mdk-react-devkit/core' ``` ### `Accordion` props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `title` | Optional | `string` | `''` | Accordion header title | | `isOpened` | Optional | `boolean` | `false` | Initially expanded | | `isRow` | Optional | `boolean` | `false` | Row layout for content | | `unpadded` | Optional | `boolean` | `false` | Remove content padding | | `noBorder` | Optional | `boolean` | `false` | Remove trigger border | | `solidBackground` | Optional | `boolean` | `false` | Solid background color | | `showToggleIcon` | Optional | `boolean` | `true` | Show expand/collapse icon | | `toggleIconPosition` | Optional | `'left' \| 'right'` | `'left'` | Icon position | | `customLabel` | Optional | `ReactNode` | none | Custom label next to title | | `onValueChange` | Optional | `function` | none | Callback when state changes | ### Basic usage ```tsx

This content can be expanded or collapsed.

``` ### With custom label ```tsx Active} showToggleIcon={false} >

All systems operational.

``` ### Multiple items ```tsx Section 1 Content 1 Section 2 Content 2 ``` ### Styling - `.mdk-accordion`: Root container - `.mdk-accordion--solid-background`: Solid background variant - `.mdk-accordion__item`: Individual item - `.mdk-accordion__trigger`: Clickable header - `.mdk-accordion__content`: Collapsible content area ## `Badge` ### Import ```tsx ``` ### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `count` | Optional | `number` | `0` | Number to display | | `overflowCount` | Optional | `number` | `99` | Max count before showing "99+" | | `showZero` | Optional | `boolean` | `false` | Show badge when count is 0 | | `dot` | Optional | `boolean` | `false` | Show as dot instead of number | | `text` | Optional | `string` | none | Custom text content | | `color` | Optional | [`ColorVariant`](/v0-2-0/reference/app-toolkit/ui-kit/types#colorvariant) | `'primary'` | Badge color | | `size` | Optional | `'sm' \| 'md' \| 'lg'` | `'md'` | Badge size | | `status` | Optional | `'success' \| 'processing' \| 'error' \| 'warning' \| 'default'` | none | Status variant | | `square` | Optional | `boolean` | `false` | Square badge (no border-radius) | | `offset` | Optional | `[number, number]` | `[0, 0]` | Position offset [x, y] | ### Basic usage ```tsx // Number badge on content // Overflow // Shows "99+" ``` ### Dot badge ```tsx ``` ### Status badge ```tsx ``` ### Standalone badge ```tsx ``` ### Styling - `.mdk-badge`: Badge element - `.mdk-badge--{color}`: Color variant - `.mdk-badge--{size}`: Size variant - `.mdk-badge--dot`: Dot variant - `.mdk-badge--status`: Status variant - `.mdk-badge-wrapper`: Wrapper when wrapping content ## `Pagination` ### Import ```tsx ``` ### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `current` | Optional | `number` | `1` | Current page number | | `total` | Optional | `number` | `0` | Total number of items | | `pageSize` | Optional | `number` | `20` | Items per page | | `pageSizeOptions` | Optional | `number[]` | `[10, 20, 50, 100]` | Page size options | | `showSizeChanger` | Optional | `boolean` | `true` | Show page size dropdown | | `showTotal` | Optional | `boolean` | `false` | Show total count text | | `disabled` | Optional | `boolean` | `false` | Disable pagination | | `size` | Optional | `'sm' \| 'md' \| 'lg'` | `'sm'` | Size variant | | `onChange` | Optional | `function` | none | Page/size change callback | ### Basic usage ```tsx const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(20) { setPage(newPage) setPageSize(newSize) }} /> ``` ### With total display ```tsx // Shows "1-20 of 100" ``` ### Styling - `.mdk-pagination`: Root container - `.mdk-pagination__pages`: Page buttons container - `.mdk-pagination__button`: Navigation button - `.mdk-pagination__button--active`: Active page - `.mdk-pagination__total`: Total count text - `.mdk-pagination__size-changer`: Page size select ## `Tag` ### Import ```tsx ``` ### Basic usage ```tsx Default Tag Primary Success handleRemove()}>Removable ``` ## `Avatar` ### Import ```tsx ``` ### Basic usage ```tsx JD ``` ## `SkeletonBlock` ### Import ```tsx ``` ### Basic usage ```tsx // Text skeleton // Circle skeleton // Card skeleton ``` ## `EmptyState` ### Import ```tsx ``` ### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `description` | Optional | `ReactNode` | none | **Required.** Description text | | `image` | Optional | `'default' \| 'simple' \| ReactNode` | `'default'` | Image/icon to display | | `size` | Optional | `'sm' \| 'md' \| 'lg'` | `'md'` | Size variant | ### Basic usage ```tsx ``` ### Custom image ```tsx } /> ``` ### Styling - `.mdk-empty-state`: Root container - `.mdk-empty-state--{size}`: Size variant - `.mdk-empty-state__image`: Image container - `.mdk-empty-state__description`: Description text ## `Typography` ### Import ```tsx ``` ### Basic usage ```tsx Dashboard Miner status Primary site operations are running within expected parameters. Updated 2 minutes ago ``` ## `Indicator` ### Import ```tsx ``` ### Basic usage ```tsx Miner online ``` ## `CurrencyToggler` ### Import ```tsx ``` ### Basic usage ```tsx ``` ## `ListViewFilter` ### Import ```tsx ``` ### Basic usage ```tsx ``` ## `Mosaic` ### Import ```tsx ``` ### Basic usage ```tsx Hashrate Active miners Power usage ``` # Errors (/v0-2-0/ui/react/core/components/errors) Components for displaying errors, alerts, and handling error states. ## Prerequisites - Complete the [@tetherto/mdk-react-devkit installation and add the dependency](/v0-2-0/ui/react/core#prerequisites) ## `CoreAlert` ### Import ```tsx ``` The component is authored as `Alert` in `@tetherto/mdk-react-devkit/core` but re-exported as `CoreAlert` at the package barrel to avoid collisions with other `Alert` primitives (for example Radix `AlertDialog`). ### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `type` | Optional | `'success' \| 'info' \| 'warning' \| 'error'` | `'info'` | Alert type | | `title` | Optional | `ReactNode` | none | Main message | | `description` | Optional | `ReactNode` | none | Additional description | | `showIcon` | Optional | `boolean` | `false` | Show type icon | | `icon` | Optional | `ReactNode` | none | Custom icon | | `closable` | Optional | `boolean` | `false` | Show close button | | `onClose` | Optional | `function` | none | Close callback | | `banner` | Optional | `boolean` | `false` | Full-width banner style | | `action` | Optional | `ReactNode` | none | Action element | ### Basic usage ```tsx ``` ### With action ```tsx View Details} /> ``` ### Banner style ```tsx ``` ### Styling - `.mdk-alert`: Root container - `.mdk-alert--{type}`: Type variant - `.mdk-alert--banner`: Banner variant - `.mdk-alert__icon`: Icon container - `.mdk-alert__content`: Content wrapper - `.mdk-alert__title`: Title text - `.mdk-alert__description`: Description text - `.mdk-alert__action`: Action container - `.mdk-alert__close`: Close button ## `ErrorBoundary` ### Import ```tsx ``` ### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `fallback` | Optional | `ReactNode` | none | Custom fallback UI | | `componentName` | Optional | `string` | none | Component name for error display | | `onError` | Optional | `function` | none | Error callback | | `children` | Required | `ReactNode` | none | Children to wrap | ### Basic usage ```tsx Something went wrong}> ``` ### With component name ```tsx console.error(error)} > ``` ### Higher-order component ```tsx const SafeChart = withErrorBoundary( Chart, 'Chart', (error) => logError(error) ) ``` #### Styling - `.mdk-error-boundary`: Root container - `.mdk-error-boundary__title`: Error title - `.mdk-error-boundary__message`: Error message - `.mdk-error-boundary__details`: Expandable details - `.mdk-error-boundary__stack`: Stack trace ## `ErrorCard` ### Import ```tsx ``` ### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `title` | Optional | `string` | none | Error title | | `message` | Required | `string` | none | Error message | | `retry` | Optional | `function` | none | Retry callback (shows retry button) | ### Basic usage ```tsx refetch()} /> ``` ### Styling - `.mdk-error-card`: Root container - `.mdk-error-card__title`: Title text - `.mdk-error-card__message`: Message text - `.mdk-error-card__retry`: Retry button ## `NotFoundPage` ### Import ```tsx ``` ### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `title` | Optional | `string` | `'Page not found'` | Page title | | `message` | Optional | `string` | none | Custom message | | `backUrl` | Optional | `string` | `'/'` | Back button URL | ### Basic usage ```tsx ``` ### Styling - `.mdk-not-found`: Root container - `.mdk-not-found__title`: Title text - `.mdk-not-found__message`: Message text - `.mdk-not-found__back`: Back button # Form components (/v0-2-0/ui/react/core/components/forms) Form components handle user input and form submission. All components are built with accessibility in mind and support keyboard navigation. The category splits into three groups: - [Controls](#controls): raw inputs you can drop in standalone or compose inside `
` - [Composition](/v0-2-0/ui/react/core/components/forms/composition): the `` provider and the low-level building blocks (`FormField`, `FormItem`, `FormLabel`, `FormControl`, `FormDescription`, `FormMessage`) - [Prebuilt fields](/v0-2-0/ui/react/core/components/forms/fields): one-tag wrappers that bind a control to React hook form ## Prerequisites Before using these components, complete the [@tetherto/mdk-react-devkit installation](/v0-2-0/ui/react/core#prerequisites). ## Controls Standalone form controls. Manage `value` and `onChange`, or compose them inside [``](/v0-2-0/ui/react/core/components/forms/composition#form) with [`FormField`](/v0-2-0/ui/react/core/components/forms/composition#formfield). ### `ActionButton` Triggers an action after **confirmation** in either an inline **popover** (default) or a modal **dialog**. Set **variant** so the trigger [`Button`](#button) matches severity (for example `danger` for destructive flows). After the user confirms, run your side effects there. Consider showing feedback with [`Toast`](/v0-2-0/ui/react/core/components/overlays#toast) and **`Toaster`** from the same Toast setup as in [`overlays`](/v0-2-0/ui/react/core/components/overlays). This is the pattern the [demo app(../quickstart) applies. #### Import ```tsx ``` #### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `label` | Optional | `string` | none | Trigger button label | | `variant` | Optional | `'primary' \| 'secondary' \| 'danger'` | `'secondary'` | Visual variant of the trigger [`Button`](#button) | | `loading` | Optional | `boolean` | `false` | Loading state on the trigger | | `disabled` | Optional | `boolean` | `false` | Disable the trigger | | `className` | Optional | `string` | none | Class on the trigger | | `mode` | Optional | `'popover' \| 'dialog'` | `'popover'` | **`popover`**: confirmation UI in an anchored popover. **`dialog`**: confirmation UI in a modal [`Dialog`](/v0-2-0/ui/react/core/components/overlays#dialog). | | `confirmation` | Required | `ActionButtonConfirmation` | none | title, body copy, and confirm/cancel handlers. See [Confirmation object](#confirmation-object). | #### Confirmation object Nested inside `confirmation`: | Field | Type | Description | |-------|------|-------------| | `title` | `string` | Heading shown in the confirmation UI | | `description` | `ReactNode` | Optional body (text or JSX) | | `onConfirm` | `() => void` | Called when the user confirms | | `onCancel` | `() => void` | Called when the user cancels | | `confirmLabel` | `string` | Optional confirm button label (defaults vary by `mode`) | | `cancelLabel` | `string` | Optional cancel button label (defaults to **Cancel**) | | `icon` | `ReactNode` | Optional icon in **popover** header (popover mode only; dialog layout uses title and body) | #### Basic usage (popover) Default `mode` is **`popover`**: compact confirmation next to the trigger. ```tsx { void restartService() }, }} /> ``` #### Dialog mode Set **`mode="dialog"`** when you want a full modal confirmation (for example irreversible or high-impact actions). ```tsx { void factoryReset() }, }} /> ``` ### `Button` #### Import ```tsx ``` #### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `variant` | Required | `'primary' \| 'secondary' \| 'danger' \| 'tertiary' \| 'link' \| 'icon' \| 'outline' \| 'ghost'` | `'secondary'` | Visual variant | | `size` | Required | `'sm' \| 'md' \| 'lg'` | none | Button size | | `loading` | Required | `boolean` | `false` | Show loading spinner | | `fullWidth` | Required | `boolean` | `false` | Expand to container width | | `icon` | Required | `ReactNode` | none | Icon element | | `iconPosition` | Required | `'left' \| 'right'` | `'left'` | Icon placement | | `disabled` | Optional | `boolean` | `false` | Disable interaction | #### Basic usage ```tsx ``` #### With icon ```tsx ``` #### Loading state ```tsx ``` #### Styling - `.mdk-button`: Root element - `.mdk-button--variant-{variant}`: Variant modifier - `.mdk-button--size-{size}`: Size modifier - `.mdk-button--full-width`: Full width modifier - `.mdk-button--loading`: Loading state ### `Cascader` #### Import ```tsx ``` #### Basic usage ```tsx ``` ### `Checkbox` #### Import ```tsx ``` #### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `size` | Optional | `'xs' \| 'sm' \| 'md' \| 'lg'` | `'md'` | Checkbox size | | `color` | Optional | `'default' \| 'primary' \| 'success' \| 'warning' \| 'error'` | `'primary'` | Color when checked | | `radius` | Optional | `'none' \| 'small' \| 'medium' \| 'large' \| 'full'` | `'none'` | Border radius | | `checked` | Optional | `boolean \| 'indeterminate'` | none | Checked state (controlled) | | `onCheckedChange` | Optional | `function` | none | Change callback | #### Basic usage ```tsx ``` #### Sizes and colors ```tsx ``` #### Styling - `.mdk-checkbox`: Root element - `.mdk-checkbox--{size}`: Size modifier - `.mdk-checkbox--{color}`: Color modifier - `.mdk-checkbox__indicator`: Check mark container ### `DatePicker` #### Import ```tsx ``` #### Basic usage ```tsx ``` ### `DateRangePicker` #### Import ```tsx ``` #### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `selected` | Optional | `DateRange` | none | Selected `{ from, to }` range (controlled) | | `onSelect` | Optional | `(range: DateRange \| undefined) => void` | none | Called when the user applies a range | | `placeholder` | Optional | `string` | `'Pick a date range'` | Trigger text when no range is selected | | `dateFormat` | Optional | `string` | `'MM/dd/yyyy'` | `date-fns` format used in the trigger label | | `disabled` | Optional | `boolean` | `false` | Disable interaction | | `showPresets` | Optional | `boolean` | `true` | Show the default preset buttons for the last 7, 14, 30, and 90 days | | `presets` | Optional | `PresetItem[]` | none | Override the default preset list | | `allowFutureDates` | Optional | `boolean` | `false` | When `false`, dates after today are disabled | | `triggerClassName` | Optional | `string` | none | Extra classes for the trigger button | | `calendarClassName` | Optional | `string` | none | Extra classes for the calendar | | `modalClassName` | Optional | `string` | none | Extra classes for the popover modal | Any other [`react-day-picker` props](https://daypicker.dev/) (excluding `mode` and `selected`) are forwarded to the underlying calendar. #### Basic usage ```tsx const [range, setRange] = useState() ``` #### Custom presets ```tsx const presets: PresetItem[] = [ { label: 'This week', value: { from: startOfWeek(new Date()), to: new Date() } }, { label: 'This month', value: { from: startOfMonth(new Date()), to: new Date() } }, ] ``` #### Allowing future dates ```tsx ``` #### Styling - `.mdk-date-picker__trigger`: Trigger button - `.mdk-date-picker__modal`: Popover modal container - `.mdk-date-picker__calendar`: Calendar grid - `.mdk-date-picker__summary`: Selected-range summary block - `.mdk-date-picker__presets`: Preset button row ### `Input` #### Import ```tsx ``` #### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `label` | Optional | `string` | none | Visible label for the field | | `variant` | Optional | `'default' \| 'search'` | `'default'` | Input variant | | `size` | Optional | `'default' \| 'medium'` | `'default'` | Input size | | `error` | Optional | `string` | none | Error message (shows red border) | | `prefix` | Optional | `ReactNode` | none | Element before input | | `suffix` | Optional | `ReactNode` | none | Element after input | | `wrapperClassName` | Optional | `string` | none | Wrapper element class | #### Basic usage ```tsx ``` #### With prefix/suffix ```tsx } placeholder="Username" /> ``` #### Error state ```tsx setMac(e.target.value)} /> ``` #### Styling - `.mdk-input`: Input element - `.mdk-input__wrapper`: Wrapper container - `.mdk-input__wrapper--error`: Error state - `.mdk-input__label`: Label element - `.mdk-input__prefix`: Prefix element - `.mdk-input__suffix`: Suffix element - `.mdk-input__error`: Error message ### `Label` #### Import ```tsx ``` #### Basic usage ```tsx ``` ### `Radio` #### Radio composition parts | Part | Role | |------|------| | `RadioGroup` | Root that holds the selected value and calls `onValueChange` when it changes. | | `Radio` | Default circular radio item; use `label` or children for visible text. | | `RadioCard` | Card-styled option for horizontal or compact layouts; uses the same item primitive as `Radio`. | #### Import ```tsx ``` #### Basic usage ```tsx Small Medium Large ``` **RadioCard** suits card-style, horizontal groups: ```tsx ``` ### `Select` #### Import ```tsx Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGroup, SelectLabel, } from '@tetherto/mdk-react-devkit/core' ``` #### `SelectTrigger` props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `size` | Optional | `'sm' \| 'md' \| 'lg'` | `'lg'` | Trigger size | | `variant` | Optional | `'default' \| 'colored'` | `'default'` | Visual variant | | `color` | Optional | `string` | none | Custom color for colored variant (hex) | #### Basic usage ```tsx ``` #### With groups ```tsx ``` #### Colored variant ```tsx ``` #### Styling - `.mdk-select__trigger`: Trigger button - `.mdk-select__trigger--{size}`: Size modifier - `.mdk-select__trigger--colored`: Colored variant - `.mdk-select__content`: Dropdown content - `.mdk-select__item`: Option item ### `Switch` #### Import ```tsx ``` #### Props | Prop | Status | Type | Default | Description | |------|--------|------|---------|-------------| | `size` | Optional | `'sm' \| 'md' \| 'lg'` | `'md'` | Switch size | | `color` | Optional | `'default' \| 'primary' \| 'success' \| 'warning' \| 'error'` | `'default'` | Color when checked | | `radius` | Optional | `'none' \| 'small' \| 'medium' \| 'large' \| 'full'` | `'none'` | Border radius | | `checked` | Required | `boolean` | none | Checked state (controlled) | | `onCheckedChange` | Required | `function` | none | Change callback | #### Basic usage ```tsx ``` #### Sizes and colors ```tsx ``` #### Styling - `.mdk-switch`: Root element - `.mdk-switch--{size}`: Size modifier - `.mdk-switch--{color}`: Color modifier - `.mdk-switch__thumb`: Toggle thumb ### `TagInput` #### Import ```tsx ``` #### Basic usage ```tsx ``` ### `TextArea` #### Import ```tsx ``` #### Basic usage ```tsx