A desktop menu-bar companion wallet for Starknet. It stores a seed safely, derives multiple accounts from it, and exposes a local service that other programs on the same machine — AI agents, desktop apps — can call to request wallet operations. The wallet never returns private keys; it returns signed payloads (and can broadcast on request). Every signing or state-changing request pops a menu-bar confirmation, and all requests are logged.
It speaks the standard Starknet
wallet_rpc.json
API plus a small companion_* extension namespace, and delegates cryptography to
krusty-kms.
It also bundles an on-device proving companion (the prover crate, merged
from dinner): hand it an
already-signed payload via companion_prove and it generates a SNIP-36 proof
locally — the secret never leaves the machine. companion_signAndProve goes one
step further, signing the private virtual transaction and proving it in a single
call. The prover holds no keys and sits strictly downstream of signing.
⚠️ Experimental. Not for real funds.krusty-kmsis flagged experimental by its authors and the crypto path is unaudited. Use throwaway test seeds only — never a real mnemonic or a seed controlling funds. See the security notes in the spec §14.
All three layers are built. The crypto core (wallet-core) and the loopback
service (wallet-rpc) are green — 92 tests, clippy clean — covering
derivation, signing, the vault, the full wallet_* + companion_* handlers,
auth, the approval broker, permission grants, and the request log. The Tauri
menu-bar app (desktop/) compiles and its frontend builds, but the GUI is not
yet run-verified (tray, dialogs, notifications, auto-lock, Dock icon need a
human launch). Broadcasting + fee estimation work against a node configured in
the Settings tab; Sepolia reads/estimates are live-verified, while the broadcast
hop still needs a funded-account submit.
The prover crate is built and tested (the success path via a test-only stub
Prover, since there is no mock backend — both real backends fail honestly
when unconfigured), with companion_prove* covered end-to-end in the wallet-rpc
dispatch tests. The native SNIP-36 backend is live-verified on Sepolia
(2026-07-02, prover pin v1.2.2): it generated a proof-carrying invoke
on-device that verified on-chain. Next up: bramble convergence — see
docs/project/bramble-convergence.md.
Privacy (strk20*) is parked, not pending.
For the authoritative, always-current state, see
docs/project/status.md.
The headless core (wallet-core + wallet-rpc):
cargo build
cargo test # core + service test suite
cargo clippy --all-targets # lintThe desktop menu-bar app (Tauri 2 + React + TypeScript + Vite), under
desktop/:
cd desktop
npm install
npm run tauri dev # dev (hot reload; child processes — for development)
npm run tauri build # build an installable bundle (recommended for use)
# → src-tauri/target/release/bundle/macos/strkd.app (drag to /Applications)Install the built strkd.app and launch it like any app — it shows in the Dock
and lives in the menu bar (the tray); closing the window only hides it, and
clicking the Dock icon re-shows it. Quit cleanly via the tray → Quit. Set the
Starknet RPC endpoint in the app's Settings tab to enable broadcasting + fee
estimation.
Requires a Rust toolchain (+ Node for the desktop app). The first core build
fetches krusty-kms from git (pinned commit), so it needs network access. The
desktop crate is its own Cargo crate, excluded from the root workspace, and
reuses the core crates by path.
The headless core is covered by automated tests. The desktop GUI is built but not run-verified — confirming the tray/onboarding/approval flows means running the app on a machine with a display.
strkd (crate strkd, under crates/wallet-cli/) is a thin command-line
client for the running menu-bar app. It holds no keys — it discovers the app's loopback service via
port.lock, pairs once, and speaks the same wallet_* / companion_* API that
agents use. Signing and approvals still happen in the app.
strkd pair --name my-cli # one-time; approve the prompt in the menu bar
strkd status # lock state, network, grant
strkd accounts # accounts this client may use
strkd usage # the wallet's self-describing API doc
strkd sign --account 0x… --data @typed_data.json
strkd send --account 0x… --to 0x… --function transfer --calldata 0x…,0x…
strkd send … --submit # broadcast (IRREVERSIBLE); default is sign-onlyAdd --json for a clean machine contract (only JSON on stdout; errors as
{"error": …} on stderr) and -q/--quiet to drop progress text — both work
before or after the subcommand, so strkd accounts --json | jq pipes cleanly.
From this repo (needs a Rust toolchain):
cargo install --path crates/wallet-cli # installs the `strkd` binaryPrebuilt binaries are distributed with cargo-dist. Once a release is cut, end users install without a toolchain via the shell installer, or by downloading a tarball from the release page:
curl -LsSf https://github.com/starknet-innovation/strkd/releases/latest/download/strkd-installer.sh | shIt installs to ~/.cargo/bin. Per-target archives (strkd-aarch64-apple-darwin.tar.xz
and friends, for Apple silicon + Intel macOS and x86_64 + arm64 Linux) are
attached to the same release, each with a .sha256, plus a signed build
provenance attestation. To verify a download before running it:
gh attestation verify strkd-aarch64-apple-darwin.tar.xz -R starknet-innovation/strkdHomebrew is not wired up yet: it needs the starknet-innovation/homebrew-tap
repo to exist and a HOMEBREW_TAP_TOKEN secret. The root Cargo.toml dist block
documents exactly what to re-add.
Two independent tracks, each triggered by its own tag shape, each publishing a draft GitHub Release for a human to review and publish:
| Tag | Workflow | Ships |
|---|---|---|
v0.3.1 |
release.yml (generated by dist) |
strkd CLI: shell installer + per-target tarballs + checksums |
desktop-v0.3.1 |
desktop-release.yml |
Menu-bar app with the prover bundled: .dmg, .app.tar.gz, .deb, .rpm, .AppImage |
git tag v0.3.1 && git push origin v0.3.1 # CLI
git tag desktop-v0.3.1 && git push origin desktop-v0.3.1 # desktop appThe desktop build stages the pinned prover from prebuilt release artifacts
(desktop/scripts/prover-pin.env is the single source of truth for the version)
and gates on snip36 doctor before building the app, so a broken proving stack
fails the release rather than shipping. Every staged prover file is also checked
against SHA-256 hashes pinned in that file, independent of the prover release's
own checksums.
What every release guarantees:
- Drafts only. Nothing is public until a maintainer publishes it, and
releases/latest/download/…keeps serving the previous published release until then. - Verifiable downloads. Checksums (CLI: per-file
.sha256+sha256.sum; desktop:SHA256SUMS) and a signed provenance attestation for every file. - Pinned toolchain. Every GitHub Action is pinned to a commit SHA, and
cargo-dist is installed from a hash-pinned tarball, never
curl | sh. - Reproducible dependencies.
Cargo.lock(root anddesktop/src-tauri) is committed, and CI and both release workflows fail on a stale lockfile. - Least privilege. Workflow tokens are read-only except in the one job that
creates the release. Manual desktop releases run from
mainonly. - Unsigned desktop apps, and the release notes say so. The
.dmg/.appare not code-signed or notarized yet: macOS users must right-click → Open, and should verify the download first.
Before publishing a draft, check the attached files and notes, then press Publish on the release page.
Publishing: only the CLI release may be "Latest". GitHub marks one release
per repo as Latest, by default the one published most recently. The CLI install
command fetches releases/latest/download/strkd-installer.sh, which exists only
in the CLI release. If a desktop release becomes Latest, that command breaks for
everyone. So publish the desktop release with Set as the latest release
unticked, and the CLI release with it ticked:
gh release edit desktop-vX.Y.Z --draft=false --latest=false # desktop: never Latest
gh release edit vX.Y.Z --draft=false --latest # CLI: Latest
curl -sI https://github.com/starknet-innovation/strkd/releases/latest | grep -i location
# must end in /tag/vX.Y.ZMaintainers. CLI dist policy lives in [workspace.metadata.dist] (root
Cargo.toml). release.yml was generated from it and carries one deliberate
local edits, listed in its banner: the release-safety changes above, and a
narrowed tag glob, so desktop-v* tags don't trigger the CLI release. Because allow-dirty = ["ci"] is set, dist generate leaves that file
alone rather than reverting the edit, which also means upstream dist CI changes
are not picked up automatically; the banner at the top of the workflow explains
how to resync deliberately.
cargo install cargo-dist # provides the `dist` binary
dist plan --tag v0.3.1 # dry run: exactly what a release would contain
dist build --artifacts=local --target aarch64-apple-darwin --tag v0.3.1
# build the real artifacts for one target, locallyWhile strkd is alpha (test seeds only), prefer cargo install for internal
dogfooding and keep the public channels quiet.
All documentation is indexed in docs/index.md — that is
the single entry point; start there if you're unsure where to look. The docs are
organized into three areas by the question they answer:
| Area | Location | Answers | Start with |
|---|---|---|---|
| Design | spec/ |
Why and what we're building | spec/wallet-companion-spec.md |
| Code | docs/code/ |
How the implementation works | docs/code/architecture.md (then wallet-core.md, wallet-rpc.md, desktop.md) |
| Project | docs/project/ |
Where we are and how we work | docs/project/status.md |
- "What is this and how is it designed?" → the spec, starting at
spec/wallet-companion-spec.md. - "How does the code work / where is X implemented?" →
docs/code/architecture.mdfor the map, then the per-crate reference (e.g.docs/code/wallet-core.md). - "What's done, what's left, what should I do next?" →
docs/project/status.md(the Resume here section at the top). - "Why is the code in its current state?" →
docs/project/progress-log.md.
How development is tracked and handed off — what's been done, what remains, how
to log progress, and how to pick up where the last session stopped — is governed
by docs/project/workflow.md. Read it before
doing project work; it is prescriptive. In short:
status.mdis the mutable snapshot of now.progress-log.mdis the append-only history.workflow.mdis the process (and the security gates).
Keep docs navigable by following these rules. They are not optional — drift here makes the whole tree untrustworthy.
- Three homes, by purpose. Design →
spec/. Code →docs/code/. Project management →docs/project/. Put a doc where its question belongs, not where it's convenient. - One topic per file; one fact in one place. Don't restate the spec in a code doc or vice versa — link to the source of truth. Duplication is the main thing we're preventing.
- Index everything. Every doc file must be listed in
docs/index.md. Adding a doc without indexing it is an incomplete change. When you add a whole new area, add a row to the Documentation table above too. - One crate → one reference. When a new crate is built, add
docs/code/<crate>.mdand link it fromdocs/code/architecture.mdand the index. - Naming & links. Files are
kebab-case.md. Use relative links between docs so they work on disk and in any viewer. Use absoluteYYYY-MM-DDdates. - Docs ship with the change. Code changes update the relevant code doc in
the same change; project state changes update
status.mdand append toprogress-log.md(perworkflow.md). - Don't break the boundary in prose. When documenting anything that touches keys, reinforce the security boundary; never include real seed/key material in an example.
If you're unsure where something goes, it almost always belongs in exactly one of the three areas above — pick by the question it answers, then link it from the index.