This weekend i have implement a basic library/feature folder and vibed a few demos for @Gzuuus state machine spec to:
- wrap my head around it
- see what can be done with it
- see if my instincts are right that this can be used as a basic smart contract vehicle
- what is the potential if we combine it with nostr-scrolls or just wasm snippets in general and where they belong
- have autistic discussions about it
All demos have a event/timeline inspector so we can see what's happening under the hood.
Check it out at: https://schlaustronics.com/nsm/
Source:
Here is a short description for each demo:
#counter — Counter
The smallest possible Nostr state machine: two players, one shared counter, race to 5. Every TICK is a signed kind:7501; a 3rd-party custodian validates it against the SM definition and publishes a signed kind:30078 snapshot. Click TICK out of turn and watch the custodian reject it with NO_TRANSITION — no off-chain coordination, no shared backend.
#rps — Rock-Paper-Scissors
Commit-reveal RPS over Nostr. Both players publish a hash of their move first; only after both commitments land do they reveal the cleartext, so no one can peek and play accordingly. The SM enforces the order: REVEAL inputs are rejected before both commits exist.
#badges — Mint + Transfer
Mint a badge from any image — the asset id is sha256(image_bytes), content-addressable by construction. Transfers run through a shared "Ownership" state machine that requires the validator to cosign before the transfer settles, blocking double-spends. Open the "ownership proof" panel on a transferred badge to see the cryptographic chain you can verify offline.
#scroll-counter — Counter, but the action is a WASM scroll
Same counter, but the increment isn't hardcoded — it's a WebAssembly module published as a kind:1227 event ("a scroll"). Pick plus-one or times-two, the custodian fetches the scroll off the relay and runs it on every TICK. Actions are now content-addressed bytecode anyone can author, publish, and audit.
#bet — Time-driven betting with oracle + payout scrolls
Two parties stake, the match has wall-clock start/end times, and the SM advances itself: the custodian schedules START_MATCH / CANCEL_BET / TIMEOUT inputs from a schedule table. Resolution is two scrolls — an oracle scroll that names the winner, a payout scroll that computes splits. Optionally, the custodian delegates scroll execution to a remote CVM scroll-executor over MCP-over-Nostr (per CEP-15).
#vote — Deadline-driven voting
Open a question with a deadline; voters cast kind:7501 YES/NO/ABSTAIN inputs. The custodian schedules a synthetic FINALIZE input at the deadline, tallies votes against a configurable threshold, and snapshots the result — no one needs to "close the poll" manually. Watch the countdown tick, then watch the SM transition itself.
#negotiator — Two parties agree on a SM, a jukebox spawns it
Alice and Bob exchange kind:7500 proposals over Nostr (machine definition + scroll source) until both sign the same one. The agreed bundle is then handed to a "jukebox" — a CVM/MCP server that spawns + custodians the machine on demand. The whole negotiation is on-relay; the jukebox tool call rides MCP-over-Nostr per CEP-15, encrypted via NIP-44 gift-wraps.
#nested — Parent SM invokes child SMs
A tournament parent SM uses XState's invoke to spawn two child match SMs (counter-style, race to 4). Alice TICKs match A, bob TICKs match B; each child's onDone flips a flag in the parent, and an always transition fires FINISHED once both children complete. The interesting bit: each child's nested state (count, value, etc.) round-trips through kind:30078 verbatim — getPersistedSnapshot().children is what makes it work.
View quoted note →
GitHub
GitHub - zeSchlausKwab/nsm: nostr state machines demos
nostr state machines demos. Contribute to zeSchlausKwab/nsm development by creating an account on GitHub.
