Start
Quickstart
Try it in the browser, then from the terminal, then from your own code.
1. In the browser (30 seconds)
Open the demo on the home page. You hold a stolen key. Press "Send 4 ETH to me", skip time with "+1 day", try "Try to execute now", then let the guardian whistle. Tick "The guardian is asleep" to see what an unattended vault costs you.
2. Run the site locally
launch.bat # builds and serves on http://localhost:5900
3. Run the proofs
cd contracts && forge test # 43 tests
node --test tests-js/engine.test.mjs # engine vs the real contract on a local node, real P-256
node --test tests-js/diff.test.mjs # 560 random steps, JS simulator vs contract
node scripts/real-chain-check.mjs # real chain 4663, real P-256 precompile
node scripts/anvil-e2e.mjs # full CLI + watcher run on a local node
4. The CLI
Keys are read from the environment, never from arguments.
set MARMOT_PRIVATE_KEY=0x...
set RPC=https://rpc.mainnet.chain.robinhood.com
node cli/marmot.mjs deploy # once per network, prints the factory
set MARMOT_FACTORY=0x...
node cli/marmot.mjs create --guardian 0xGuardianWallet --delay 24h
set MARMOT_VAULT=0x...
# send some ETH to the vault address with any wallet, then:
node cli/marmot.mjs queue-eth %MARMOT_VAULT% --to 0xFriend --amount 0.5
node cli/marmot.mjs status %MARMOT_VAULT%
node cli/marmot.mjs execute %MARMOT_VAULT% 0 # once it is ripe
The contract is not audited. Start with an amount you can afford to lose, a short waiting time, and read Limits and risks.
5. A passkey guardian
On the Guardian page your browser makes a passkey and shows its public coordinates. The CLI takes them:
node cli/marmot.mjs create --guardian-passkey 0x<x>,0x<y> --rp your.domain --delay 24h
Then open the Lookout on the vault address.
6. The terminal lookout
node cli/marmot.mjs watch %MARMOT_VAULT% --notify-url https://discord.com/api/webhooks/<your-private-webhook>
See Lookout and watcher for auto-refusing anything that is not on an allow list.
7. From your own code
import * as E from './src/engine/marmot-engine.mjs';
import * as P from './src/engine/passkey.mjs';
const auth = await P.softAuthenticator({ rpId: 'marmot.example', origin: 'https://marmot.example' });
const guardian = E.guardianPasskey(auth.x, auth.y);
const vault = E.predictVault(FACTORY, { owner, guardian, delay: 86400, rpIdHash: auth.rpIdHash, salt });
// later, to refuse announcement 3:
const digest = E.vetoDigest(4663, vault, 3, /* epoch */ 0);
const sig = await P.signWithPasskey(auth, digest);
await relayer.sendTransaction({ to: vault, data: E.call.vetoWithSig(3n, sig), gasLimit: 900000n });