Set a savings rule once. It runs without you. Withdraw any block.
AutoPilot is a self-custodial savings account on OPN Chain. You set a rule — move 0.01 OPN into savings every minute, never more than 0.1 per hour — and a permissionless network of keepers executes it for a tip you choose. You are never required to be online, and you can take everything out at any moment.
Built for the OPN Builders Programme, Season 1 · DeFi & Open Finance.
- Live demo: https://miningelectroneum.github.io/opn-autopilot/
- Chain: OPN Chain Testnet ·
984 - Chain capability report:
docs/OPN_CHAIN_REPORT.md
Recurring on-chain saving has an awkward shape. Doing it yourself means remembering to send a transaction on a schedule, forever. Handing it to a product means depositing into somebody's vault and trusting them with both the funds and the ability to change the rules.
Most automation designs quietly solve this by taking custody. AutoPilot doesn't.
Your account is a contract you own. Funds sit in it, not in a shared pool, and not with us.
When you create a plan you permanently fix four things:
| Fixed at creation | Why it matters |
|---|---|
| Destination | A keeper cannot send your money anywhere else. |
| Amount per run | A keeper cannot move more than you allowed. |
| Interval | A keeper cannot run it more often than you allowed. |
| Budget cap per hour | A hard ceiling. Any number of keepers cannot exceed it. |
runPlan(uint256 planId) is the only function a keeper can call, and its only
argument is which plan to run. There is no parameter through which a caller
could name a different recipient or a larger amount — this is asserted directly
in the test suite, not just claimed here.
The keeper's reward is the tip you attached. If every keeper on earth
disappears, nothing is stuck: you call runPlan yourself, or you call
withdraw and leave.
you your account keepers
──────── ──────────────── ───────────
createPlan() ─────────▶ rules stored, immutable
funds held by you
▲
│ runPlan(1) scan registry,
└────────────────────── execute, earn tip
transfers amountPerRun
│
▼
SavingsPool (10% APR)
| Contract | Address |
|---|---|
AutoPilotFactory |
0xd7212d97026f2a27e34adb9a2b82ee4ab3f50774 |
AutoPilotAccount (implementation) |
0xeEF061Bb772CE5437C910c5eD4d85eb7B8cD9791 |
SavingsPool |
0xf8acb8fb4a0bbbfba71d3ec5a9b91a1aef069113 |
AutoPilotRegistry |
0x3a820d0c11a021b1b9c485874df2eb760c87b65b |
A live account is running right now with a plan executing every 60 seconds:
0x1fC032A0194AdE7D8BDAfF4E238c590c50C9119a
Four contracts, each with one job.
AutoPilotAccount — the user's account. Holds funds, stores plans, enforces
every limit. Two callers are recognised: the owner, who may do anything
including arbitrary batched calls, and everyone else, who may only advance a
plan the owner already authorised.
AutoPilotFactory — deploys one account per user as an EIP-1167 minimal
proxy (~45 bytes instead of ~5kB of code). Addresses are deterministic, so your
account address is known before it exists and can be funded first. The factory
holds no funds, has no owner, and has no upgrade path.
AutoPilotRegistry — the keeper work queue. Accounts announce themselves;
scan() returns every runnable plan in a single eth_call, so a keeper needs
one request rather than N. It stores addresses and nothing else.
SavingsPool — a minimal native-OPN savings venue paying 10% APR from a
funded reserve. Deliberately the simplest thing that is still honest: no
rebasing, no share maths, no oracle. It is unaware AutoPilot exists, which is
why an account can pay into it with a bare transfer.
- Keepers cannot redirect funds. Destination and amount are immutable.
- Reentrancy.
runPlan,withdrawandexecuteBatchuse a transient-storage guard (EIP-1153) with strict checks-effects-interactions ordering, so a malicioustargetcannot re-enter to drain an epoch budget. - Storage collisions. All state is namespaced under an ERC-7201 root rather
than slots
0,1,2…, so an account can be pointed at a different implementation later without corrupting itself. - The exit is unconditional.
withdrawhas no lock-up, no notice period and no penalty, and cannot be disabled by anyone including the deployer. - Accounts cannot be stolen.
initializeis one-shot; the factory bakes the owner into both the salt and the init call, so paying to create someone's account grants no control over it.
AutoPilot was originally built on EIP-7702. Rather than a proxy, your own EOA would delegate to the implementation: no separate account, funds never moving at all. OPN Chain's documentation states that the Pectra upgrade is fully supported and describes EIP-7702 as live.
On the live testnet it is not. A type 0x04 transaction carrying an
authorization list is accepted, mines, and reports status: success — but the
0xef0100||implementation delegation indicator is never written. The
authorization is silently discarded. We verified this three independent ways,
including hand-serializing the raw 0x04 payload and sponsoring the
authorization from a separate account to rule out nonce handling.
That investigation produced docs/OPN_CHAIN_REPORT.md:
a reproducible report of what OPN Chain's EVM actually executes. It also found
that eth_estimateGas can return a limit that cannot execute, that receipt
gasUsed is misreported as exactly half the gas limit, and that the documented
minimum gas price is off by a factor of 10⁹. All of it is reproducible from
scripts/. We would be glad to be shown an error in any of it.
So AutoPilot ships in proxy mode — and the implementation is dual-mode by design:
function owner() public view returns (address) {
address o = _s().owner;
return o == address(0) ? address(this) : o;
}An uninitialised account reports itself as owner, which is exactly the authority model EIP-7702 provides. The same deployed contract already works as a delegation target; the test suite proves it against a spec-compliant Prague chain. The day OPN Chain honours EIP-7702, users point their EOA at this same address and stop using a proxy. No redeploy, no migration, no contract change.
The web app is a single page with no backend. Connect a wallet on OPN Chain,
create your account, set a rule, and watch keepers execute it live — the
activity feed reads PlanExecuted events straight from the chain.
npx serve web -l 4173npm install
npx hardhat compile
npx hardhat test # 24 tests, including EIP-7702 delegation modeDeploy and exercise the full lifecycle on OPN Chain:
cp .env.example .env # add DEPLOYER_KEY
npx hardhat run scripts/deploy.js --network opn
npx hardhat run scripts/activate.js --network opn
node scripts/keeper.js # run a keepernode scripts/evmprobe.mjs # which opcodes actually execute
node scripts/diag7702raw.mjs # EIP-7702, raw 0x04 on the wire
node scripts/diag7702sponsored.mjs # EIP-7702, sponsored authorization- Set explicit gas limits. The estimator can return a limit that dies on intrinsic gas, which looks exactly like an unsupported opcode. This cost us a wrong diagnosis.
- Compile for
cancun, notprague. Cancun is the highest fork honoured end to end. - Don't infer fork support from block headers. They omit Cancun/Prague fields even though TSTORE and MCOPY execute fine. Probe opcodes instead.
Now (testnet). Recurring native-OPN savings, permissionless keepers, deterministic accounts, live dashboard.
Next. Multiple concurrent plans per account. ERC-20 support so plans can DCA into any token. A keeper leaderboard with reputation, so owners can set tips against observed reliability rather than guesswork.
When OPN Chain enables EIP-7702. Switch accounts to delegation mode against the already-deployed implementation, removing the proxy entirely. Funds then never leave the user's own address.
Mainnet. Security review, an audited pool with real yield sources, and a keeper market where tips clear against actual gas costs.
MIT