Frictionless Wallets, Part 1: Device-Specific Key-Pairs
This is part one of a three-part series on making on-chain wallets feel like normal apps. Part one is the foundation: a device-specific key-pair that a player's wallet delegates signing to, so an app can submit actions on their behalf without a pop-up every time. Part two adds social login with a 2-of-3 secret, and part three adds biometric unlock - both build on the key-pair described here.
This is a Cardano capability first: it works with the standard Cardano browser wallets (Lace, Eternl, Nami, NuFi), and the same code works across the other chains EffectStream supports.
See it in action
The demo below runs the whole flow: a Cardano wallet (Lace) signs a message with a pop-up, the device key signs the same kind of message instantly, and then three production games run pop-up-free because the device key is signing every move behind the scenes.
Two kinds of transactions
The idea rests on a distinction every game makes whether it says so or not:
- Financial transactions - moving assets, minting, anything that spends value - always require explicit approval from the player's real wallet.
- Non-financial transactions - game moves, messages, state updates - carry no value and just need to prove the player authored them.
Asking the player to approve the second kind in a wallet pop-up is what makes on-chain apps feel like filling in forms. The device key-pair removes exactly that friction, and only that.
Creating the key-pair in the browser
When a session starts, the app generates a fresh key-pair on the device, in the browser, and keeps it in encrypted local storage. Nothing about it touches a server. The player then connects their real Cardano wallet once and signs a single delegation certificate that authorises the device key to submit non-financial transactions on their behalf.
That delegated key is deliberately limited:
- Scoped - it can only submit non-financial inputs. It can never move funds; financial transactions still route through the real Cardano wallet.
- Time-bounded - it expires with the session.
- Revocable - the player can cancel the delegation at any time.
So the security guarantee players expect is preserved - nobody moves their assets without an explicit signature - while the per-action friction disappears.
The code
The delegation lifecycle lives in @effectstream/wallets: key generation, certificate signing, session management, and silent signing of non-financial inputs. The flow is four steps:
import { WalletMode, walletLogin, signMessage } from "@effectstream/wallets";
// 1. Create the device-local key-pair, generated in the browser. With no
// `seedPhrase` a fresh BIP-39 seed is generated client-side; the app can
// persist it to encrypted local storage.
const device = await walletLogin({
mode: WalletMode.CardanoLocal,
network: "Preview", // "Mainnet" | "Preprod" | "Preview" | "Custom"
});
// 2. The player connects their real Cardano wallet (Lace, Eternl, Nami,
// NuFi...). This is the only step that shows a pop-up.
const real = await walletLogin({
mode: WalletMode.Cardano,
preference: { name: "lace" },
});
// 3. One-time delegation: link the device address to the player's account so
// the backend accepts the device key for non-financial inputs. The real
// wallet and the device key both sign the `&linkAddress` built-in - see
// `accountPayload.linkAddress` in @effectstream/concise.
// 4. Every later action is signed by the device key - no pop-up - then sent
// to the batcher.
const signature = await signMessage(device.result, "move|x10y20");
The signing model is uniform across chains: swap WalletMode.Cardano for WalletMode.Midnight, WalletMode.EvmInjected, or any other supported mode and the rest of the flow is identical. Full reference is in the wallets documentation.
Live today
This is not a prototype - the device key-pair is in production on midnight.fun, powering three games where pop-ups per action would make play impossible:
- Safe Solver - rapid puzzle move submission
- Kachina Kolosseum - real-time PvP combat
- Block Kart Legends - dozens of state updates per race
What's next
The device key-pair is the building block for the rest of this series:
- Part 2 - Social login (2-of-3): a wallet from a Google sign-in, backed by a recoverable 2-of-3 secret - no seed phrase.
- Part 3 - Biometric login: unlock the wallet with the platform's biometric authenticator (passkeys).
Source
- Package: https://www.npmjs.com/package/@effectstream/wallets
- Framework code: https://github.com/effectstream/effectstream
- Related: Auto-Sign: eliminating wallet pop-ups