marmot

Marmot docs

20 pages, from the idea to the error codes

Reference

Contract reference

Marmot.sol and MarmotFactory.sol, function by function.

Solidity 0.8.26, optimizer 500 runs, via-IR, EVM cancun. Source in contracts/src/. The vault inherits OpenZeppelin EIP712 and uses SignatureChecker and the repository's WebAuthn library.

Constants and state

NameValueNotes
MIN_DELAY5 minutes
MAX_DELAY30 days
GRACE14 daysHow long a ripe announcement stays runnable
owneraddressChanges only by an announcement
guardian(address addr, uint256 x, uint256 y)addr != 0: wallet or ERC-1271. Otherwise a P-256 passkey (x, y).
delayuint64
rpIdHashbytes32, immutablesha256 of the passkey domain
epochuint64Incremented by panic and by owner rotation
opCountuint256Next announcement id

Owner functions

FunctionEffect
queueCall(to, value, data) returns (id)to may not be the vault or zero
queueTransfer(token, to, amount) returns (id)ETH if token == 0. to may not be zero or the vault.
queueSetDelay(newDelay) returns (id)Within MIN_DELAY and MAX_DELAY
queueSetGuardian(g) returns (id)Valid per the creation rules
queueSetOwner(o) returns (id)o != 0
cancel(id)Pending announcements only

Public functions

FunctionEffect
execute(id)Runs a pending, ripe, unexpired announcement of the current epoch. Reentrancy-locked.
vetoWithSig(id, sig)Verifies the guardian's signature on Veto(id, epoch), then refuses the announcement
panicWithSig(sig)Verifies the guardian's signature on Panic(epoch), then epoch + 1
receive()Accepts ETH, emits Deposited

Guardian functions (wallet guardian only)

FunctionEffect
veto(id)msg.sender must be guardian.addr
panic()Same, epoch + 1

Views

FunctionReturns
getOp(id)(kind, status, readyAt, epoch, to, value, data)
isLive(id)Pending, current epoch, not expired
vetoDigest(id), panicDigest()The EIP-712 digest to sign, for the current epoch

EIP-712 messages

domain: { name: "Marmot", version: "1", chainId, verifyingContract: vault }
Veto(uint256 id, uint64 epoch)
Panic(uint64 epoch)

For a passkey guardian the 32-byte digest is the WebAuthn challenge. For a wallet or ERC-1271 guardian the signature is the usual 65-byte (or contract-defined) signature over the digest.

Events

EventWhen
Deposited(from, amount)ETH received
Queued(id, kind, to, value, data, readyAt, expiresAt)Any announcement. Everything needed to explain it is here.
Executed(id, by)An announcement ran
Vetoed(id, by)The guardian refused it. by is the caller (a relayer for signed vetoes).
Cancelled(id)The owner withdrew it
Panicked(newEpoch)Panic, or owner rotation
OwnerChanged, GuardianChanged, DelayChangedAt creation and when an announcement changes them

MarmotFactory

create(owner, guardian, delay, rpIdHash, salt) returns (Marmot)
predict(owner, guardian, delay, rpIdHash, salt)   returns (address)
event Created(vault, owner, delay)

The CREATE2 salt is keccak256(owner, salt), so nobody can squat an address meant for you. The factory has no owner, no fee and no authority over the vaults it creates.