Splitting a game between the chain and the server: signatures, a watermark and plugins
When we designed Blockchain Cuties there was nothing to copy. The blockchain games of the day, like CryptoKitties, were simple and lived entirely on-chain. We wanted much more: items to equip, adventures to go on. It was obvious at once that doing all that on-chain would be too expensive and too slow — and wouldn’t survive growth.
So the question was where to cut. I sat with the modules on paper and drew the lines: the cuties themselves are NFTs on the chain; items and adventures live in our database. Then the hard part — how do the two halves talk to each other?
One-time signatures. Some items had to change on-chain state: a potion, for example, slightly changes a cutie’s genome. So the server signs the action, and the contract checks the signature before applying it. To stop a signature from being used twice, each one carries a unique ID. Today this is a standard pattern; in 2018 we were working it out ourselves.
A watermark instead of a growing list. The naive way is to store every used ID in the contract — and pay for that storage forever. Instead, the server periodically told the contract the current minimum sign ID: anything below it is rejected outright. That solved two problems at once:
- contract storage didn’t grow with every action;
- items stopped getting stuck. When a player starts using a potion, it’s frozen in our database until we see the transaction in the chain logs. If the player changes their mind or has no money for gas, the potion would stay frozen forever. Once the watermark moves past its ID, we know the contract will never accept that signature — so the item can be safely unfrozen.
Plugins instead of upgrades. On top of this I built a plugin system: small contracts attached to the main one, each speaking the same signature protocol. The item market was a plugin; so were the different potions; so was a daily vote, where players sent a tiny transaction and a random voter won a reward. New features could change a little of a cutie’s genome or stats without touching the main contract — no proxies, no migrations.
What I’d tell a team starting a Web3 product today: put on-chain only what players must own, prove everything else with signatures, and design how state gets released — not only how it gets locked.