CLI tooling for Polkadot Playground. Installed as the dot command.
curl -fsSL https://raw.githubusercontent.com/paritytech/playground-cli/main/install.sh | bashTo install a specific version:
curl -fsSL https://raw.githubusercontent.com/paritytech/playground-cli/main/install.sh | VERSION=v0.2.0 bashThe installer drops the binary into ~/.polkadot/bin/, symlinks it at ~/.local/bin/dot, appends the path to your shell rc, and then runs dot init so you can finish setup without a second command.
End-to-end first-run setup. Login and toolchain install run concurrently; account setup runs once both have completed successfully.
- Login via the Polkadot mobile app — a QR code is printed to the terminal. Scan it with the app. If you already have a session persisted in
~/.polkadot-apps/, this step is skipped. - Toolchain install —
rustup, nightly,rust-src,cdm, IPFS, andgh. Existing installs are detected and skipped. - Account setup (only if a session is available) — in order:
- Fund — if your balance on Paseo Asset Hub is below 1 PAS, Alice sends 10 PAS (testnet).
- Map —
Revive.map_accountis signed by you on the mobile app so an H160 is associated with your SS58 address. - Allow — Alice grants you 1000 transactions / 100 MB of Bulletin storage.
Flags:
-y, --yes— skip the QR login entirely. Dependencies still install, account setup is skipped (no session).
Self-update from the latest GitHub release. Detects your OS/arch, downloads the corresponding dot-<os>-<arch> asset, verifies HOME is set, and atomically replaces the running binary (write-to-staging-then-rename so the running process is never served a half-written file).
Auto-detects the project's package manager (pnpm / yarn / bun / npm from the lockfile) and runs the build npm script. If no build script is defined, falls back to a framework invocation (vite build, next build, tsc) based on what's installed.
Flags:
--dir <path>— project directory (defaults to the current working directory).
Builds the project, uploads the output to Bulletin, registers a .dot domain via DotNS, and optionally publishes the app to the Playground registry (so it shows up in the user's "my apps" list).
Flags:
--signer <mode>—dev(fast, uses shared dev keys for upload + DotNS — 0 or 1 phone approval) orphone(signs DotNS + publish with your logged-in account — 3 or 4 phone approvals). Interactive prompt if omitted.--domain <name>— DotNS label (with or without the.dotsuffix). Interactive prompt if omitted.--buildDir <path>— directory holding the built artifacts (defaultdist/). Interactive prompt if omitted.--playground— publish to the playground registry so the app appears under "my apps". Interactive prompt (default: no) if omitted.--suri <suri>— override signer with a dev secret URI (e.g.//Alice). Useful for CI.--env <env>—testnet(default) ormainnet(not yet supported).
Passing all four of --signer, --domain, --buildDir, and --playground runs in fully non-interactive mode. Any absent flag is filled in by the TUI prompt.
Requirement: the ipfs CLI (Kubo) must be on PATH. dot init installs it; if you skipped init you can install it manually (brew install ipfs or follow docs.ipfs.tech/install). This is a temporary requirement while bulletin-deploy's pure-JS merkleizer has a bug that makes the browser fallback unusable.
The publish step is always signed by the user so the registry contract records their address as the app owner — this is what drives the Playground "my apps" view.
Planned. No behaviour yet.
pnpm install
pnpm buildCompile and install the dot binary to ~/.polkadot/bin/:
pnpm cli:installpnpm test # one-shot
pnpm test:watch # rerun on change
npx tsc --noEmit # type checkTests live alongside the code as *.test.ts. They avoid mocking so deeply that they just re-implement the code under test — real polkadot-api primitives (Enum) stay real so a variant name change is caught.
Every PR automatically publishes a dev release tagged with the branch name. Others can try it with:
curl -fsSL https://raw.githubusercontent.com/paritytech/playground-cli/main/install.sh | VERSION=dev/my-branch bashReleases are triggered by changesets. To cut a release:
- Create a changeset:
pnpm changeset - Commit the generated
.changeset/*.mdfile with your PR - On merge to
main, CI consumes the changeset, bumps the version, compiles binaries, and creates a GitHub release
Uses Biome. Checked in CI on every PR.
pnpm format # fix
pnpm format:check # check only@polkadot-apps/*are pinned tolatestintentionally — they are our own packages and we want the lockfile to track head.@polkadot-api/sdk-inkis pinned to^0.6.2andpolkadot-apito^1.23.3becausechain-clientcurrently embeds an internalPolkadotClientshape that breaks with newer versions. Bump together withchain-clientonly.bulletin-deployis pinned to an explicit version — notlatest. Currently0.6.9. Previouslylatestpointed at 0.6.8 which had a WebSocket heartbeat bug (40s default < 60s chunk timeout) that tore chunk uploads down asWS halt (3); keeping the pin explicit avoids ever sliding back onto that. When bumping, check the release notes for any changes todeploy()/DotNSAPIs we rely on.
- Single config module (
src/config.ts) — all chain URLs, contract addresses, dapp identifiers and thetestnet/mainnetswitch live here. Nothing else in the tree should hard-code an endpoint or address. - Signer shim (
src/utils/session-signer-patch.ts) — the default session signer from@polkadot-apps/terminalusessignRaw, which the Polkadot mobile app wraps with<Bytes>…</Bytes>(producing aBadProofon-chain). We delegate togetPolkadotSignerFromPjsfrompolkadot-api/pjs-signer, which formats the payload as polkadot.jsSignerPayloadJSON— exactly what the mobile'sSignPayloadJsonInteractorconsumes. This file can be removed once@polkadot-apps/terminaldefaults tosignPayload. - Unified signer resolution (
src/utils/signer.ts) — oneresolveSigner({ suri? })call returns aResolvedSignerwhether the user is authenticated via QR session or a dev//Alice-style URI. Every command threads the result through to its operations instead of branching on source. - Connection singleton (
src/utils/connection.ts) — stores the promise (not the resolved client) so concurrent callers share a single WebSocket. Has a 30s timeout and preserves the underlying error viaError.causefor debugging. - Session lifecycle (
src/utils/auth.ts) —getSessionSigner()returns an explicitdestroy()handle. Callers MUST call it (typically from auseEffectcleanup) — the host-papp adapter keeps the Node event loop alive. - Deploy SDK / CLI split (
src/utils/deploy/+src/commands/deploy/) — the CLI command is a thin Commander + Ink wrapper around a purerunDeploy()orchestrator. The orchestrator avoids React/Ink so WebContainer consumers (e.g. RevX) can drive their own UI off the same event stream. - Signer-mode isolation (
src/utils/deploy/signerMode.ts) — decides which signer each deploy phase uses (pool mnemonic vs user's phone) in one place so the mainnet rewrite can be a single-file swap. - Bulletin delegation — all storage-side hardening (pool management, chunk retry, nonce fallback, DAG-PB verification, DotNS commit-reveal) stays inside
bulletin-deploy. We calldeploy(..., { jsMerkle: true })so the flow stays binary-free and runs unchanged in a WebContainer. - Signing proxy (
src/utils/deploy/signingProxy.ts) — wraps the user'sPolkadotSignerto emitsign-request/-complete/-errorlifecycle events. The TUI renders these as "📱 Check your phone" panels with live step counts. - Playground publish is ours (
src/utils/deploy/playground.ts) — we deliberately do NOT usebulletin-deploy's--playgroundflag. We call the registry contract fromsrc/utils/registry.tswith the user's signer so the contract records theirenv::caller()as the owner — required for the Playground app's "my apps" view.