Appearance
Autopilot
A budgeted trading agent that runs 24/7 inside rules the user sets — and that Solidity enforces. Define the policy once, sign it, fund the vault, and the executor acts on it until expiry.
Route: /defi/autopilot · Contract: 0x689AaEc7E3012d84226e7c5F4DEeFdC01C86d23e (Arbitrum) · Fee: 20 bps of output
Live but gated
Contracts are deployed and audited on Arbitrum mainnet. Before promoting to users: the owner must call setRouters([...], true) (the vault is fail-safe — no allow-listed routers means no trade can ever execute), the owner EOA needs moving to a multisig, the backend Autopilot code needs deploying, and a live end-to-end trade needs running. Full list in the status matrix.
What it does
The user creates an agent: a budget in one token, a list of tokens it may hold, hard spending limits, an expiry, and one or more strategies. They sign the policy; its hash goes on-chain. From then on the platform's executor evaluates triggers every cron tick and submits trades that satisfy the policy.
The user can pause, resume, top up, edit the policy (re-signing), or close and withdraw at any time.
The policy document
The policy is the source of truth. Its canonical JSON is hashed with keccak256 and that hash lives on-chain in AgentAccount.policyHash. The engine refuses to act whenever the DB policy hash differs from the on-chain hash.
json
{
"version": 1,
"wallet": "0x…",
"chainId": 42161,
"budgetToken": "0x…",
"allowTokens": ["0x…", "0x…"],
"limits": {
"maxPerTradeUsd": 100,
"maxPerDayUsd": 500,
"maxTradesPerDay": 4,
"expiry": 1790000000
},
"safety": { "minTokenScore": 70 },
"slippageBps": 100,
"strategies": [ … ]
}Validation rules enforced by bz-backend/utils/policy.js → validatePolicy:
| Field | Rule |
|---|---|
version | Must be 1 |
wallet, budgetToken | Valid addresses (0x0 allowed for native budget) |
limits.maxPerTradeUsd | > 0 |
limits.maxPerDayUsd | >= maxPerTradeUsd |
limits.maxTradesPerDay | Non-negative integer; 0 = unlimited |
limits.expiry | Future unix timestamp, at most 180 days out |
allowTokens | Non-empty, max 20, no duplicates, must include budgetToken |
safety.minTokenScore | 0–100 |
slippageBps | Integer 10–300 (0.1%–3%) |
strategies | Non-empty, max 10, unique ids |
| Every token a strategy touches | Must be in allowTokens |
All addresses are lowercased on the way out, so a checksum-casing difference cannot change the hash.
Canonicalisation
The hash is only stable if serialisation is canonical: recursively sorted object keys, no whitespace, arrays in author order.
The frontend mirror must stay byte-identical
defi-dex/src/utils/autopilotPolicy.ts is a deliberate byte-for-byte mirror of bz-backend/utils/policy.js plus middleware/walletAuth.js. If the two drift, the frontend computes a hash the backend rejects and every creation fails. Change them together, always.
The message a wallet signs is:
Blazpay Autopilot policy {policyHash} for agent {agentId} on chain {chainId}with agentId = "new" before creation.
Strategy types
Five, all validated structurally before a policy can be signed.
dca
Buy on a fixed cadence.
| Field | Rule |
|---|---|
fromToken, toToken | Addresses, allow-listed |
amount | Positive numeric string |
cadenceSeconds | Integer >= 3600 (one hour minimum) |
dip_buy
Buy when price drops by a percentage within a lookback window.
| Field | Rule |
|---|---|
buyToken | Address, allow-listed |
dropPct | > 0 and <= 90 |
windowHours | Integer 1–168 (one week max) |
amount | Positive numeric string |
cooldownHours | Optional positive integer |
take_profit
Sell a portion when the position is up by a target percentage.
| Field | Rule |
|---|---|
positionToken | Address, allow-listed |
targetPct | > 0 |
sellPortionBps | Integer 1–10000 (1 bp to 100%) |
Entry price is a VWAP of the agent's own fills, with a 1-hour anti-hammer guard so a wobbling price cannot trigger repeatedly.
stop_loss
Mirror image of take-profit: dropPct instead of targetPct, same sellPortionBps and same VWAP-entry and anti-hammer treatment.
rebalance
Hold a target weighting across tokens.
| Field | Rule |
|---|---|
targetWeights | Map of address → bps; must sum to exactly 10000 |
bandBps | Integer >= 50 — the drift tolerance before rebalancing |
Rebalancing is pairwise, not a single multi-leg transaction.
Precedence
When several strategies fire on the same tick, policy order decides. The first strategy in the array that has a valid trade wins that tick. This is why strategies is order-significant and canonicalisation preserves array order.
On-chain guardrails
The contract is what makes this trustworthy. See autopilot vault for the full walkthrough; the essentials:
solidity
struct Limits {
uint128 maxPerTrade; // budgetToken units
uint128 maxPerDay; // rolling 24h
uint64 expiry; // executeTrade reverts at/after
uint16 maxTradesPerDay; // 0 = unlimited
}Plus per-agent allowedTokens, an owner-only active pause switch, the policyHash check, and a global owner-curated allowedRouter allow-list covering both targetContract and spender.
The router allow-list exists because of a critical finding
An adversarial audit found that without it, a compromised executor could make the vault call anyToken.transfer(attacker, all) and drain every agent's pooled funds. The data.length > 68 guard was padding-bypassable. The fix was deployed as a UUPS upgrade to the same proxy with state preserved, and 61 contract tests now cover it — including an explicit drain-block case. Details: custody.
Trades credit the measured balance delta, minus the 20 bps fee. minOut is only a floor, so a slippage buffer is never a user haircut. batchExecuteTrades isolates failures — one reverting agent does not kill the batch.
Safety gate
Before buying a token, the cron runs it through the RugCheck scanner and compares against safety.minTokenScore.
- Trusted majors are skipped — scanning USDC every tick is wasted work.
- Unscored tokens block rather than pass. Fail-closed.
Notifications
bz-backend emits blazNotification records with type autopilot for:
| Event | When |
|---|---|
| Trade executed | Every fill |
| Three consecutive failures | Something is structurally wrong |
| Safety block | A token failed the score gate |
| 48-hour expiry warning | Tracked via expiryWarnedAt so it fires once |
API
Mutating routes are wallet-signature-authenticated via middleware/walletAuth.js — unlike the legacy DCA routes, a forged policy mutation here would change what the executor does with escrowed funds.
| Purpose | Endpoint | Auth |
|---|---|---|
| Preview a policy (validate, no write) | POST /defi/autopilot/preview | — |
| Create | POST /defi/autopilot/create | walletAuth |
| Confirm on-chain creation | POST /defi/autopilot/confirm | — |
| Update policy | POST /defi/autopilot/update/:id | walletAuth |
| Pause | POST /defi/autopilot/pause/:id | walletAuth |
| Resume | POST /defi/autopilot/resume/:id | walletAuth |
| Token price | GET /defi/autopilot/price/:chainId/:token | — |
| Force chain sync | POST /defi/autopilot/sync/:chainId/:agentId | — |
| List a wallet's agents | GET /defi/autopilot/list/:wallet | — |
| One agent | GET /defi/autopilot/:chainId/:agentId | — |
| Activity feed | GET /defi/autopilot/:chainId/:agentId/activity | — |
Full detail: Autopilot API reference.
In BlazAI
The AUTOPILOT intent handles setup and management conversationally. The interesting part is how hallucination is contained:
bz-agentclassifies and extracts a rough shape.bz-backend/v1/services/autopilotPolicy.service.js → buildPolicyDraftasks the drafting model in JSON mode for symbols and numbers only.- The backend resolves symbols to addresses and runs
validatePolicy.
So a hallucinated token name resolves to nothing, and a malformed policy fails validation. An LLM cannot produce a signable policy.
v1/services/autopilot.service.js → handleAutopilot supports sub-actions create, status, pause, resume, close, help. Read-and-guide sub-actions never auto-execute. The chat card (swapAI/actions/AutopilotCard.tsx) runs create inline as sign → create → confirm; management deep-links to /defi/autopilot.
Deployments
| Chain | Proxy | Notes |
|---|---|---|
| Arbitrum (42161) | 0x689AaEc7E3012d84226e7c5F4DEeFdC01C86d23e | Implementation 0x7bF6f14c53BB18BC5D5cd34c9A81c8fb658bB168, verified on Arbiscan |
| Robinhood Chain (4663) | 0x4dE7CD522f1715b2a48F3ad6612924841d450A0F | No routers allow-listed — run probe-autopilot-routers.cjs first |
| Arbitrum Sepolia (421614) | 0x7cA5A09E59e8d84e678854184F67D8aF505EfF9f | Testnet |
Owner and deployer: 0x2Ed05570214f6C0F7612B580aB37C163076e0162. Executor: 0x5935745431D1385ae1a9CE2644fb5f5d1f140459. feeBps 20. Deploy cost was 0.0000659 ETH.
Addresses are hardcoded as defaults in bz-backend/utils/constants.js (AUTOPILOT_ADDRESSES) and defi-dex/src/constants/index.ts, env-overridable via AUTOPILOT_ADDRESS_ARBITRUM, _BASE, _ROBINHOOD. The cron skips chains with an empty address.
Known limits
- Residual executor risk. Post-audit, a compromised executor can still force bad-slippage churn on an agent's own positions through allow-listed routers. Bounded by liquidity, and funds return to the agent rather than the attacker. An oracle-based
minOutfloor would close it. - Owner EOA is not a multisig. It can UUPS-upgrade the proxy.
- The LLM never originates trades — a locked v1 decision, not a limitation to work around.
- Only
budgetTokenoutflows are metered againstmaxPerDay(audit finding H-1, accepted for launch). - Base is configured but not deployed.
AUTOPILOT_ADDRESSES[8453]is empty. - Autopilot balances do not appear in portfolio totals.
- Autopilot
copystrategy type was dropped from v1. See copy trading.