For the complete documentation index, see llms.txt. This page is also available as Markdown.
SailKernel
SailKernel is the single trusted execution contract. It is deployed at the same address on every supported chain (see addresses) and inherits OpenZeppelin's EIP712 (domain name = "SailKernel", version = "1") and ReentrancyGuard. License: GPL-2.0-or-later.
Responsibilities
Account instantiation — deploy and/or register a Safe (createAccount, registerAccount).
Permission registry — per-account ordered list of IPermission addresses with an O(1) index map.
Manager dispatch — verify the manager's EIP-712 signature, evaluate the named permission, execute via the Safe module path (dispatch, dispatchBatch).
Fee accounting — validate manager fee collection against the registered IFeePolicy and enforce the protocol/distributor split (collectFees).
Principal tracking — informational cumulative deposit/withdrawal counters (recordDeposit, recordWithdrawal).
The permission set lives in a private address[] per account plus a _permissionIndex map storing index + 1 (so 0 means "not registered"), enabling O(1) membership and swap-and-pop removal.
Three nonce namespaces
The kernel maintains three independent per-account nonce sequences, so signatures for one operation class can never be replayed as another:
Any restrictive signer operation (revoke, replace, revoke-session, manager rotation) additionally bumps managerNonces and batchNonces by a large epoch increment (NONCE_EPOCH_INCREMENT = 1 << 128). This invalidates every dispatch the manager pre-signed but did not yet submit, so tightening the mandate cannot be raced by an in-flight dispatch.
Both manager and permission-signer signatures are verified with _recoverOrERC1271: ECDSA recovery is tried first (so EIP-7702 accounts that install transient code but don't implement ERC-1271 still work), falling back to ERC-1271 isValidSignature when the signer is a contract. Both roles can therefore be EOAs, multisigs, or smart accounts.
Module-execution boundary
The kernel moves assets only through ISafe.execTransactionFromModule(target, value, data, 0) — always operation 0 (CALL), never DELEGATECALL. Two structural guards apply on dispatch:
No self-targeting the Safe. A call whose target == account reverts (AccountSelfTarget) — this blocks module-triggered enableModule / setGuard / owner changes that would satisfy the Safe's onlySelf guard.
No self-targeting the kernel (batch only) — a subcall targeting the kernel reverts (KernelSelfTarget).