Skip to content

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:

FieldRule
versionMust be 1
wallet, budgetTokenValid addresses (0x0 allowed for native budget)
limits.maxPerTradeUsd> 0
limits.maxPerDayUsd>= maxPerTradeUsd
limits.maxTradesPerDayNon-negative integer; 0 = unlimited
limits.expiryFuture unix timestamp, at most 180 days out
allowTokensNon-empty, max 20, no duplicates, must include budgetToken
safety.minTokenScore0–100
slippageBpsInteger 10–300 (0.1%–3%)
strategiesNon-empty, max 10, unique ids
Every token a strategy touchesMust 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.

FieldRule
fromToken, toTokenAddresses, allow-listed
amountPositive numeric string
cadenceSecondsInteger >= 3600 (one hour minimum)

dip_buy

Buy when price drops by a percentage within a lookback window.

FieldRule
buyTokenAddress, allow-listed
dropPct> 0 and <= 90
windowHoursInteger 1–168 (one week max)
amountPositive numeric string
cooldownHoursOptional positive integer

take_profit

Sell a portion when the position is up by a target percentage.

FieldRule
positionTokenAddress, allow-listed
targetPct> 0
sellPortionBpsInteger 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.

FieldRule
targetWeightsMap of address → bps; must sum to exactly 10000
bandBpsInteger >= 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:

EventWhen
Trade executedEvery fill
Three consecutive failuresSomething is structurally wrong
Safety blockA token failed the score gate
48-hour expiry warningTracked 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.

PurposeEndpointAuth
Preview a policy (validate, no write)POST /defi/autopilot/preview
CreatePOST /defi/autopilot/createwalletAuth
Confirm on-chain creationPOST /defi/autopilot/confirm
Update policyPOST /defi/autopilot/update/:idwalletAuth
PausePOST /defi/autopilot/pause/:idwalletAuth
ResumePOST /defi/autopilot/resume/:idwalletAuth
Token priceGET /defi/autopilot/price/:chainId/:token
Force chain syncPOST /defi/autopilot/sync/:chainId/:agentId
List a wallet's agentsGET /defi/autopilot/list/:wallet
One agentGET /defi/autopilot/:chainId/:agentId
Activity feedGET /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:

  1. bz-agent classifies and extracts a rough shape.
  2. bz-backend/v1/services/autopilotPolicy.service.js → buildPolicyDraft asks the drafting model in JSON mode for symbols and numbers only.
  3. 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

ChainProxyNotes
Arbitrum (42161)0x689AaEc7E3012d84226e7c5F4DEeFdC01C86d23eImplementation 0x7bF6f14c53BB18BC5D5cd34c9A81c8fb658bB168, verified on Arbiscan
Robinhood Chain (4663)0x4dE7CD522f1715b2a48F3ad6612924841d450A0FNo routers allow-listed — run probe-autopilot-routers.cjs first
Arbitrum Sepolia (421614)0x7cA5A09E59e8d84e678854184F67D8aF505EfF9fTestnet

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 minOut floor 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 budgetToken outflows are metered against maxPerDay (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 copy strategy type was dropped from v1. See copy trading.

Internal + developer documentation. Usage figures are point-in-time snapshots — re-verify before publishing them.