Building a Zcash-gated game with Zakura

Building a Zcash-gated game with Zakura

Building a Zcash-gated game

October 26, 2026· 4 min read
0 score

Making games with Zcash is easy as pie with Zcash Payment Hooks.

I wanted to learn how Zcash Payments Hooks work and I figured that there'd be no funner way than by vibecoding one of those addictive match-3 games. The results was Six Seeds. My kid is obsessed with Percy Jackson so I with a Greek Mythology genre for the game. You play the goddess Persephone, escaping the underworld one chamber at a time. The first chamber is free. To cross into each of the other five, you pay Charon (the mythical ferryman who carries souls across the river Styx) a toll of 0.001 ZEC from any shielded wallet, and the next chamber opens once the payment confirms.

I wanted to see if it is possible and practical for a small game to accept private payments with no payment processor and no app store in the middle? The answer is yes! All it takes is a small server, a lightweight Zakura node, and the receiving daemon free2z published.

The game

Six Seeds is a Svelte app wrapped in Tauri, which turns a web app into a native desktop program. The macOS build is a 3 MB download. The same code runs in a browser, and on an iPhone it installs to the home screen.

It is not in the App Store, on purpose. Apple's guideline 3.1.1 requires in-app purchase for unlocking game levels and explicitly rules out unlocking them with cryptocurrency. The web and the desktop are where a Zcash toll can live.

One server, four parts

Six Seeds (browser, iPhone, Mac)   │ 1. ask for an invoice              4. poll until unlocked   ▼                                        ▲Unlock service ◄── 3. payment events ── zcash-paymentd   │                                        ▲  viewing key, private journal   │ 2. ZIP-321 link: address,              │  full blocks over RPC   │    amount, memo "unlock:<token>"       │   ▼                                        │Player's wallet ──► Zcash mainnet ──► Zakura 1.7.0, pruned
Part What it does
Zakura 1.7.0, pruned Follows and validates the chain. Bootstrapped from the official 11 GiB pruned snapshot.
zcash-paymentd Recognizes payments to the game's address with a viewing key. It holds no spending key.
Unlock service and hook Issues invoices, records payment events, and decides which chamber a payment opens. Standard-library Python and SQLite.
Caddy Serves the game and its API over HTTPS.

All of it runs on one virtual machine with 2 vCPUs, 8 GB of RAM and a 100 GB disk. The game never talks to the node directly. It only asks the unlock service for an invoice, then asks again whether its chamber is open.

How a crossing works

  1. The game asks for an invoice and shows a ZIP-321 link and QR code: my address, the toll, and a one-time memo like unlock:p8sckj3b6cwdsx.
  2. The player pays from any shielded wallet.
  3. When the payment lands in a mined block, zcash-paymentd emits payment.observed, and the game says the oar has dipped.
  4. After three confirmations it emits payment.confirmed. The hook records the unlock against that player, and the game says you have crossed.

The hook follows the delivery contract from Part 2 of the free2z series. It deduplicates by event id, ignores stale revisions, re-locks a chamber if a reorganization revokes the payment, and records underpaid or memo-less payments without ever unlocking anything.

The first crossing

On October 12, Chamber 2 opened for a real mainnet payment of 0.001 ZEC (100,000 zatoshis) that I sent from my phone:

Step Time (UTC)
Invoice issued 22:51:38
Payment seen in a mined block 22:56:07
Three confirmations, chamber unlocked 22:57:22

The hook received each event exactly once, with no failures or retries.
Screenshot 2026-10-26 at 19.45.37

What I learned

  • On iPhone, iOS picks the wallet, not you. Any installed app can claim zcash: links, and iOS chooses among them with no menu. Mine opened an exchange app instead of the Zcash wallet I wanted. The toll now has separate copy buttons for the address, amount and memo, so any wallet works.
  • The memo is the product. No memo, no unlock. The game says so next to the copy buttons.
  • Charge only for levels people can beat. I simulated 500 attempts per chamber on the real game engine. The last two chambers were winnable only about 20% of the time, which is a bad thing to charge for. I retuned them to about 60% and 50%, and a test now fails if a paid chamber drifts out of range.
  • A payment is not DRM. Level data ships inside the game, so the server knows who paid but cannot stop a modified client from playing anyway. Content that lives on the server, such as video, can be truly withheld until the payment confirms.
  • Nodes have expiry dates. Every Zakura release stops at a fixed block height. Zakura 1.6.0 would have stopped at block 3,536,467, so I upgraded to 1.7.0, which runs until early December. Running your own node means upgrading on the chain's schedule, not yours.

Caveats

This is a demo on real mainnet ZEC. zcash-paymentd is early operator software without a security audit. The node is pruned and was bootstrapped from a snapshot, which means trusting that snapshot's state, as Part 1 of the free2z series explains.

If a small game can take private payments with a node, a daemon and two short scripts, your project can too. Try it at 34-24-229-200.sslip.io, and bring 0.001 ZEC for the ferryman.

Related Articles