Onchain Separately Managed Accounts Run By Agents
Sail is a minimal account-abstraction primitive for Separately Managed Accounts (SMAs). Capital is held in a self-custodial Safe the owner controls; a designated manager — typically an autonomous agent — executes only within a mandate enforced by smart contracts on every dispatch. The mandate is a set of user-deployed contracts implementing IPermission. On each dispatch the manager names one registered permission, and the kernel evaluates it via staticcall under a gas cap, forwarding the call to the Safe only if it returns true. Because a permission is arbitrary Solidity, any DeFi primitive can be expressed as one — adding an integration is a contract deployment, not a protocol upgrade.
Whitepaper · sail.money · Documentation
The trusted core and shared permission templates were reviewed by Octane, an AI source-code security scanner, across three analyses. See docs/security/.
flowchart TD
Owner["`**Owner**
holds the Safe ·
signs the mandate`"]
Manager["`**Manager**
agent · signs dispatches`"]
Mandate["`**Mandate**
set of permission contracts`"]
Kernel["`**Sail Kernel**
evaluates permission · trusted core
dispatches to Safe on success`"]
SMA["`**SMA**
holds assets · executes`"]
Owner -- "03 appoints · instant revocation" --> Manager
Owner -- "02 signs mandate · EIP-712" --> Mandate
Owner -. "01 deploys & owns" .-> SMA
Manager -- "04 signs dispatch · EIP-712" --> Kernel
Mandate -- "05 defines bounds" --> Kernel
Kernel -- "06 executes on SMA · reverts outside mandate" --> SMA
classDef default fill:none,stroke:#999
classDef kernelNode fill:none,stroke:#555,stroke-width:2px
class Kernel kernelNode
Sail separates three roles. The Owner holds the Safe and custodies the SMA's capital — always self-custodial. The Permission Signer authorizes the mandate, signing registration, configuration, and revocation of permissions over EIP-712; in retail setups it collapses to the Owner, and for institutional setups it is a separate key or multisig. The Manager executes transactions within bounds and can never exceed what the registered permissions allow; its signing key may be an EOA, multisig, or MPC wallet, and it may be an autonomous agent or a human. The address that submits a signed dispatch and pays gas is not an authority role — authority derives from the manager's signature and the registered permissions — which makes Sail natively compatible with relayers, paymasters, and ERC-4337 bundlers.
The kernel enforces four properties on every dispatch: permissions are called via staticcall (state mutation and reentrancy are structurally impossible); under a fixed gas cap (150,000 for a single evaluate, 1,000,000 for a batch evaluateBatch), where exceeding the cap is treated as a false; under selective authorization, where only the one permission named in the manager's signature is consulted, so unrelated templates coexist on one account without falsely denying each other; and fail-closed, where any revert, out-of-gas, malformed return, or false denies the dispatch. A second entry point, dispatchBatch, executes a sequence of Safe module calls atomically, gated by one IBatchPermission that owns all cross-call invariants.
See docs/ARCHITECTURE.md for the data flow and diagrams, and docs/spec.md for the normative specification.
The trusted core is the code every account must trust to use Sail at all: SailKernel, SailGovernance, its TimelockController, MandateFactory, StandardFeePolicy, and SafeModuleEnabler. It is roughly 1,022 nSLOC (the kernel itself ~791) — logical SLOC via solidity-code-metrics, comments and blanks excluded — small enough to review in isolation.
The permission templates and the reference fee policy are not "the protocol." The seven shared templates and StandardFeePolicy are demonstrations of the pattern, deployable and forkable by anyone; the kernel registers and dispatches through any contract implementing IPermission, and any account may register a different IFeePolicy. Trust them only if you choose to register them.
Non-guarantees. A security review is not a guarantee of correctness. The kernel verifies that a permission is a contract and respects its true/false answer — it does not verify that the permission's logic enforces what its author claims; permission correctness is the author's responsibility. The reference fee policy uses a manager-attested NAV, appropriate when the manager and owner are the same party. Venues with off-chain components (order books, off-chain matching) can only be constrained at their on-chain boundary. See the Known Limitations in the security model.
Repository layout:
contracts/
core/ SailKernel — execution engine, dispatch, fee accounting [trusted core]
governance/ SailGovernance — parameters, timelock, allowlists [trusted core]
factory/ MandateFactory — UX orchestrator, holds no privileges [trusted core]
safe/ SafeModuleEnabler — stateless module-enable helper [trusted core]
policies/ StandardFeePolicy — reference IFeePolicy (swappable per account)
templates/ the 7 shared permission templates + ConfigurablePermission base [not the protocol]
interfaces/ IPermission, IFeePolicy, IOracle, … (MIT import surface)
utils/ supporting implementation (e.g. CloneInitializable)
test/ test suite (not deployed)
script/ deployment scripts (not deployed as protocol)
A reference set of multi-tenant templates ships with the protocol as swappable defaults. All inherit a shared base, ConfigurablePermission, which provides per-account configuration (EIP-712 domain, per-account nonces, ECDSA and ERC-1271 verification) and is not deployed on its own. In brief: SwapPermission (oracle-gated DEX swaps), SwapPermissionNoOracle (swaps sanity-banded against a reference pool's live price), BorrowPermission (bounded lending borrows with an optional LTV ceiling), DepositPermission (deposits into allowlisted vaults/pools, credited to the account), WithdrawPermission (exits from allowlisted vaults/lending pools — ERC-4626 withdraw/redeem, Aave v2/v3 withdraw — with recipients pinned to the account), TransferPermission (ERC-20 sends to an allowlisted recipient set), and ApproveAndCallBatchPermission (an atomic approve / call / reset-to-zero batch). Each template documents the exact boundary of what it enforces — and what it does not — in docs/TEMPLATES.md.
Two independent fee mechanisms, each capped by an immutable constitutional limit and tunable within it by governance. The permission registration fee is a flat native-token amount paid to the treasury per permission registered; excess msg.value is refunded. The protocol cut on manager-collected fees is managerGrossFee × currentProtocolCutBps / 10,000, bounded above by an immutable cap of 25% (MAX_PROTOCOL_CUT_BPS = 2,500) and set to 0 at launch. The fee computation itself lives in the registered IFeePolicy; StandardFeePolicy provides a management fee on AUM and a performance fee above a per-account high-water mark. See docs/FEE_POLICIES.md.
The trusted core and the shared templates are deployed at identical CREATE2 addresses on 10 mainnets and 2 testnets (12 chains total), through the standard CREATE2 factory 0x4e59b44847b379578588920cA78FbF26c0B4956C with chain-independent salts and byte-for-byte identical constructor arguments — so every core contract and every template has the same address on every chain. The most recent addition, Robinhood (4663), has both the trusted core and the shared templates deployed at those same addresses (via calldata-replay from Base's original deploy transactions — see deployments/addresses.md), with genesis allowlists bootstrapped by the admin Safe — fully live, identical to every other chain. Addresses are shown once below; per-chain manifests are under deployments/. The machine-readable, validated index is deployments/deployments.json; the human-readable version is deployments/addresses.md.
Core (identical on every chain):
| Contract | Address |
|---|---|
| SailKernel | 0x38b508756c976e876EFF05a29E731A4d348BA6ED |
| SailGovernance | 0x4315B37cA4A315A7042af1Fcb37F8436f4D24356 |
| TimelockController | 0xC1E5F9A581D4100Aa949f80204540a33aD97A7b6 |
| MandateFactory | 0x6d2C802ffa0d9A8Ed69A5Bf22c1b63ccB566B8Fc |
| StandardFeePolicy | 0x1087312447C8a2BfA15EB9cE23590E3502DBA04b |
| SafeModuleEnabler | 0x7897Cb53a4be4a2eaAf46D60573C4Fd83b33fE1F |
Shared permission templates (identical on every chain):
| Template | Address |
|---|---|
| SwapPermission | 0x35cEEa0db96997Cc3CF3beB42FFa36A499342F7C |
| SwapPermissionNoOracle | 0x34Ba96CbEd1f46c88A5265E645DC5fe41662b519 |
| BorrowPermission | 0x3e2666051599223cEAb10De55C89A0842857d8AF |
| DepositPermission | 0xBfB5e13a97b12Ee89d2F2b9B65eCf7e0E371911f |
| WithdrawPermission | 0xB8A6CC40466c0C33a230f87a1EBC368568B96269 |
| TransferPermission | 0xda909a1CC584fb7559Ce4A828b008B473Da095e1 |
| ApproveAndCallBatchPermission | 0x0535A4D51333484ef583103DAB1a9449756ab732 |
The original ERC-20-transfer WithdrawPermission remains live but superseded at
0xF5eF5dda450a130e3020d54f565E830e4a7531f8 — see
deployments/addresses.md.
Chains:
| Chain | Chain ID | Native |
|---|---|---|
| Ethereum | 1 | ETH |
| Optimism | 10 | ETH |
| Unichain | 130 | ETH |
| Arbitrum | 42161 | ETH |
| MegaETH | 4326 | ETH |
| World | 480 | ETH |
| BSC | 56 | BNB |
| Base | 8453 | ETH |
| HyperEVM | 999 | HYPE |
| Ethereum Sepolia | 11155111 | ETH |
| Base Sepolia | 84532 | ETH |
| Robinhood | 4663 | ETH |
Governance (identical on every chain; Safe threshold m/n):
| Role | Threshold | Address |
|---|---|---|
| Admin Safe — parameter governance behind the 48h timelock | 3/5 | 0x152a32c851d317Cd54F1E6423377d7D58Dd3DE8C |
| Treasury Safe — protocol fee recipient | 3/5 | 0x7b37F85575F1568a37dBA342BC5FE6d393F0872f |
| Emergency Safe — pause (auto-expiry + cooldown) | 2/3 | 0xFf02DE6630F192Bc6d14608f5C52a9f1ae478961 |
The deployer EOA 0xB01dCE443d052e44b7D13726c0EC9fFB7f5815B6 was used for deployment only and holds no protocol authority.
Fees (native units):
| Parameter | Value |
|---|---|
| Registration fee cap (immutable ceiling) | 0.01 native token (10000000000000000 wei) |
| Registration fee — deploy-time (all chains) | 0.00015 native token |
| Registration fee — live | 0.00015 ETH (ten ETH-gas chains) · 0.005 HYPE (HyperEVM) · 0.00045 BNB (BSC) |
| Manager fee cut | 0 at launch; immutable cap 25% |
The registration fee is set immutably at construction to the same value on every chain — a prerequisite for the identical CREATE2 address — and then tuned per chain post-deploy by governance through the 48h timelock, which does not affect the already-locked addresses. The live per-chain values differ for that reason.
Sail is a Foundry project (solc 0.8.26, EVM cancun, via_ir = true, optimizer runs 200).
forge install # install submodule dependencies
forge build # compile all contracts
forge test # run the test suite — 923 tests across 47 suites
forge snapshot # regenerate the committed gas snapshotThe contracts target the Cancun EVM and their bytecode uses MCOPY; deploy only to chains that support Cancun or later.
Sail's trusted core and its seven shared permission templates underwent an AI security review by Octane (octane.security), an AI source-code security scanner, across three successive analyses (2026-06-24, 2026-06-26, 2026-06-29). The third and final analysis (2026-06-29) identified no critical- or high-severity findings; all reported vulnerabilities were resolved or acknowledged, and the remaining lower-severity warnings are documented, accepted by design, or out of scope. Reports are in docs/security/. A security review is not a guarantee of correctness — see the Known Limitations in the security model.
To report a vulnerability, do not open a public issue — see the Security Policy and report privately to hello@sail.money.
Start with the documentation index. Key documents:
- spec.md — normative protocol specification
- ARCHITECTURE.md — narrative + diagram overview
- KERNEL.md —
SailKernelreference - GOVERNANCE.md —
SailGovernancereference - FEE_POLICIES.md — fee policies and
StandardFeePolicy - TEMPLATES.md — the seven permission templates and their boundaries
- INTEGRATION.md — operator and permission/fee-policy author guide
- DEPLOYMENT.md — CREATE2 deployment runbook
- SECURITY_MODEL.md — trust model, properties, and known limitations
- oracle-adapters.md —
IOracleadapter specification - agent-identity.md · off-chain-attribution.md — advanced topics
The off-chain SDK, CLI, and local dashboard for building and operating mandated agents — Sailor — is a separate, open-source project (sail-money/Sailor), not part of this repository or the trusted core.
Contributions are welcome — see CONTRIBUTING.md. Permission templates, docs, tooling, and bug fixes are especially welcome; trusted-core changes carry a higher bar. Security issues must be reported privately via SECURITY.md, never as public issues.
GPL-2.0-or-later for the trusted core and the shared permission templates; MIT for the interface/import-surface files. Per-file SPDX headers are authoritative. See LICENSE.
Built on Gnosis Safe v1.4.1 and OpenZeppelin Contracts v5.
