🛡️ Be aware: Foundation will NEVER ask for your seed words, device PIN, or call you unexpectedly. If anyone does, it's a scam.



🚧 The Passport Prime SDK is in public beta, currently 1.0.0-beta.1. Got an idea, or want a dev unit to play with? We'd love to hear from you, hello@foundation.xyz .

What is an app on KeyOS?

An app is a Rust binary crate that runs as an isolated process under the Xous microkernel. Each app gets its own address space, its own storage scope, and its own declared permissions, all enforced by the kernel. There is no runtime escalation path.

Apps compile to the armv7a-unknown-xous-elf target, are signed with cosign2, and are installed under apps/<app-name>/ on the device.

Project layout

my-app/
├── .foundation-sdk/             # symlink to the active SDK, see below
├── app-config.toml              # app identity, publisher, permissions, theme
├── Cargo.toml
├── build.rs                     # Slint code generation (router, exports, i18n)
├── src/
│   ├── main.rs                  # entry point
│   └── theme.rs                 # applies your theme, follows the system light/dark setting
├── ui/
│   ├── app.slint                # top-level UI component
│   ├── callbacks.slint          # callbacks shared between Rust and Slint
│   └── pages/                   # per-page components (multi-page-app template)
├── resources/
│   ├── icon.svg                 # launcher icon
│   ├── theme.json               # your app theme
│   ├── images/                  # app images, reached from Slint via Images.image("name")
│   └── fonts/
└── i18n/
    └── en.json                  # translation strings

foundation new my-app scaffolds this layout for you. Three templates ship with the SDK:

  • default-app, single-page starter
  • multi-page-app, router-based multi-screen starter
  • kitchen-sink, a gallery of every @ui component in each variant, state, and size. The fastest way to see what a theme change actually looks like

Two things about this layout are worth calling out:

  • .foundation-sdk/ is a symlink to the active SDK, and it is how your Cargo.toml reaches the SDK crates. Because it is a symlink rather than a hardcoded version path, your project keeps building after an SDK update. It is generated, so it stays out of version control.
  • resources/ must be committed. Slint reaches images through it at build time, so an app whose resources/ is missing from the repo will not build for anyone else.

app-config.toml

This is the source of truth for your app's identity, publisher metadata, required permissions, and signing configuration. Every field maps one-to-one onto an internal AppConfig struct, no hidden defaults.

app-name = "my-app"
friendly-app-name = "My App"
launcher-app-name = "My App"
description = "Example application"
icon = "resources/icon.svg"
app-id = "0x00112233445566778899aabbccddeeff"
version = "0.1.0"
min-keyos-version = "1.0.0"
theme = "resources/theme.json"

[publisher]
name = "Example Company"
contact-email = "support@example.com"
support-url = "https://example.com/support"

[permissions]
"os/settings" = ["GetDeviceName"]

Field reference

FieldRequiredNotes
app-nameCargo package name and build output directory name
friendly-app-nameDisplay name
launcher-app-name,Falls back to friendly-app-name
descriptionOne-line description
iconPath relative to the project root; validated at build time
app-id0x-prefixed even-length hex; must be unique on the device
versionSemver
min-keyos-versionMinimum KeyOS version your app requires
themePath to your app theme JSON, resources/theme.json in new projects
signing-identity,Selects an identity under ~/.foundation/signing/<name>/. See the note below before committing this
cosign2-config,Explicit path to a cosign2.toml; overrides identity resolution. See the note below before committing this
[publisher]name, contact-email, support-url
[permissions]Per-service entries: "os/<service>" = ["MethodA", "MethodB"]

Leave signing-identity and cosign2-config out of a repo you publish. Both point at signing material on one particular machine, so committing them leaks local detail and, more practically, anyone else who clones your project cannot build it: they do not have your key, which is the whole point. With neither key set, the build resolves an identity itself and prompts you to choose when a machine has more than one, which is the behaviour you want for a public repo.

The UI: Slint with a curated component library

KeyOS apps use Slint for UI. The SDK exposes a stable @ui/... import surface, a curated library of Foundation-designed components, theme tokens, fonts, and icons. Your app imports from @ui and inherits a polished, on-brand starting point that you're free to fully restyle.

A minimal ui/app.slint:

import { BaseWindow } from "@ui/widgets.slint";
import { Button } from "@ui/widgets.slint";

export component AppWindow inherits BaseWindow {
    Button {
        text: "Hello, Passport Prime";
        clicked => { debug("button pressed"); }
    }
}

Components available from @ui/...:

  • Form controls, Input, Checkbox, RadioButton, Slider, Dropdown, Switch, Pagination, SegmentedSelector
  • Navigation, Drawer, Dialog, ModalWindow, PopupMenu
  • Display, Card, Chip, Badge, Button, IconButton, Progress, QR code renderer
  • Specialized, AuthWidget, CryptoFiatField, SeedWords, PinEntry, SlideToButton, FileList
  • Theming, ColorPicker, CircularProgress, Shimmer, full design tokens

Live-preview any .slint file with foundation preview ui/app.slint.

A minimal src/main.rs

The app_ui2! macro from slint_keyos_platform wires your top-level AppWindow into the KeyOS runtime. Your root component must be export component AppWindow inherits Window:

// SPDX-License-Identifier: Apache-2.0

use slint_keyos_platform::app_ui2;

app_ui2!("My App");

fn app_main(cx: AppContext, ui: AppWindow) {
    log_server::init_wait(env!("CARGO_CRATE_NAME")).unwrap();
    log::set_max_level(log::LevelFilter::Info);
    log::info!("My App starting");

    // The Slint event loop runs automatically inside app_ui2!()
    // Wire up UI callbacks, spawn tasks, subscribe to IPC events here.
}

AppContext gives you gui, fs, router, config, and set_input_handler. The macro also binds the Images global for you, so images under resources/images/ are reachable from Slint as Images.image("name").

Theming

Your app ships a theme, declared in app-config.toml as theme = "resources/theme.json". The theme file itself is small: an id, a name, and the base theme it inherits from.

{
  "id": "app_theme",
  "name": "My App",
  "parent": "base_theme"
}

Add a tokens section to override colours, spacing, typography, and control sizing, for light and dark independently. A components section can override individual components per variant, state, and size.

The generated src/theme.rs applies it and follows the system light/dark setting:

foundation_themes::apply_theme!(ui, app_theme::theme(), scheme);
// App-specific overrides go after this line

You can edit the JSON by hand, or open the visual editor:

foundation theme

foundation build and foundation sim compile the theme for you. For a brand palette shared across several apps, create a named theme with foundation themes new <brand> --from base_theme, fill in its tokens, and point your app's parent at it.

An unrecognised token key is dropped silently rather than reported, so a typo simply does nothing. If a change does not show up, check the key name before assuming the theme did not apply.

The launcher icon

Your icon lives at resources/icon.svg and has two hard requirements:

  • Exactly 110×110 px. The build refuses anything else.
  • A transparent-background glyph. The launcher draws the ring and disc behind your icon itself, so any background of your own, including a rounded rectangle, will show as a second shape on top of it. The build does not check for this, so it is worth looking at before you ship.

To supply a different icon for dark mode, add a -dark sibling next to it, for example resources/icon-dark.svg, following the same rules.

Build, simulate, preview

From inside an SDK shell (foundation develop):

foundation new my-app              # scaffold from template
cd my-app
foundation preview ui/app.slint    # live Slint preview, UI only
foundation sim                     # hosted simulator (debug)
foundation build                   # release-quality signed hardware build
foundation sideload                # build + upload to Passport Prime + launch
foundation pack                    # single .app file to hand to other people

That ladder runs from the fastest feedback to the most complete: preview for UI work, the simulator for behaviour, hardware when it matters, then pack when you want to give the app to someone else.

Run foundation preview after a first build or sim, so it picks up your compiled theme rather than the SDK default.

After any SDK update, run foundation clean && foundation build in each app, then foundation doctor. Also restart any shell or editor session you started from inside foundation develop, or it will keep pointing at the previous SDK and quietly build against it.

See the CLI Reference for every flag.

Signing

Every app on Passport Prime is signed. foundation build signs app.elf in place with cosign2 using a secp256k1 key.

One-time identity setup:

foundation cert gen "My Company"

This writes four files to ~/.foundation/signing/My Company/:

  • private.pem, secp256k1 private key (keep secret)
  • public.pub, compressed public key hex
  • My Company.crt, self-signed X.509 code-signing certificate, named after the identity so several certificates are easy to tell apart side by side
  • cosign2.toml, the config foundation build consumes

Publish the certificate's fingerprint (foundation cert fingerprint) on your official website or GitHub, so users can verify a certificate really is yours before they allow it on their device.

Identity resolution order at build time:

  1. cosign2-config in app-config.toml (explicit path)
  2. signing-identity in app-config.toml (name under ~/.foundation/signing/)
  3. An identity whose name matches [publisher].name
  4. The only configured identity, if exactly one exists
  5. Interactive prompt (errors in non-interactive contexts)

Passport Prime's app launcher verifies the signature against the embedded certificate chain before starting your app. Tampered or unsigned binaries are refused.

Registering your developer certificate on Passport Prime

Passport Prime only runs apps signed by a publisher it has been told to allow. Registering your certificate is what makes your own builds runnable on your device.

foundation cert install "My Company"

The name is optional and defaults to the publisher of the app you are in. The command:

  1. Resolves the identity and reads its certificate
  2. Prints the certificate fingerprint, along with a warning that Foundation has not verified this publisher
  3. Asks you to confirm, so allowing a publisher is always a deliberate act
  4. Connects to the device over the USB debug interface and installs the certificate

Passport Prime must be unlocked, connected over USB, with Developer Mode enabled, and no other process may be using the USB debug interface.

Allowing a publisher means every app signed by that publisher will run on your Passport Prime. Only allow a certificate you generated yourself, or one whose fingerprint you have checked against the publisher's official website or GitHub. Foundation does not vet third-party publishers.

Because trust is tied to the publisher key rather than to an individual build, keep your signing identity stable across releases. Re-signing an app under a different key makes the device treat it as a different publisher, and the user has to remove the installed app first, which takes its stored data and granted permissions with it.

Toolchain

ComponentDetail
Rust toolchainNightly, pinned by the SDK
Target triplearmv7a-unknown-xous-elf
Build flagsRUSTFLAGS="--cfg keyos -C relocation-model=pic -C link-arg=-pie" for hardware, --cfg keyos for simulator
Signingcosign2 (secp256k1 + X.509)
Build orchestrationcargo + foundation CLI (cargo xtask for SDK maintainers)
EnvironmentNix flake (foundation develop enters it)

You don't install Rust or manage the cross-compiler manually, foundation develop hands you a shell with everything already in place.

Next