Deployed contracts

The deployed Base mainnet addresses, ownership, oracle configuration, canary evidence, and external dependencies, for third-party review.

The short version

The protocol is live on Base mainnet. Every contract source is verified on Basescan, the governance configuration was audited on chain, and the full create, fund, execute, withdraw lifecycle was observed in the mainnet canary recorded below. New strategies are created here.

These are the addresses your deposit interacts with. Deposit is USDC; you accumulate WETH or cbBTC on a schedule you set, and only you can withdraw.

Network: Base mainnet Chain: 8453 Deployed block: 51218977 Stablecoin: USDC Source: Verified

Contracts

The account implementation is cloned for each user strategy. Everything else is a shared protocol component, deployed once.

ContractAddressWhat to review
DCAFactory0xda2943c3a2548609824c04a66d9de7c57edb0dd3Creates strategy accounts, stores fees, treasury, minimums, and approved yield-adapter implementations.
BatchExecutor0xb781bd6f59f46768a95ee5621d435084c7151fddRuns pooled buys, enforces the asset allowlist and oracle floor, takes the fee, distributes the output.
SwapRouter0xa0d356850137141ab7fb0ca5df5f6e450baaf19dRoutes each swap to the adapter configured for that asset.
UniswapV3Adapter0xcb80e1d2cbc3fe6b7a9bdbf5495572b5df5016faThe only contract that talks to Uniswap V3. Holds the per-asset route.
OracleDispatcher0x540edca41ebab29b13fb1a0aab427d7e4aa181afRoutes each asset to its price oracle. This is the BatchExecutor's priceOracle().
ChainlinkPriceOracle0x1c0ad083a8450e45e7c8d8ec38de72ea68fad2b2Direct Chainlink USD feeds for WETH and cbBTC, with staleness and sequencer-uptime checks.
ComposedPriceOracle0xdb0f9212fffd41adaf99b64bbac9642d39f5c066Deployed for market-feed assets; no asset is routed through it at launch.
AaveV3YieldAdapter implementation0xdbb08d3adc290e78cfa90424e745dd424f1a5c87Approved idle-yield implementation, cloned per strategy that opts into yield.
UserStrategyAccount implementation0xf420ad09e466f76a2f52dfa35c6eb411764c9229Implementation behind each per-user strategy clone.
TimelockController0xd6ab44f85db23f2abc0e8d5ead04578d73e22ccaOwns every administrative contract above. Two-day minimum delay on all of them.

Ownership and roles

Administrative power is split deliberately. The timelock holds everything that could affect where money goes, and the guardian holds only the things that are useless if delayed.

RoleAddressPower
Owner (timelock)0xd6ab44f85db23f2abc0e8d5ead04578d73e22ccaOwns all governed contracts. Every configuration change (fees, treasury, oracle, routes, asset allowlist, swap router, approved yield implementations) is queued publicly on chain and cannot execute for two days.
Guardian0x76772411e94E207D54d6C37d9dC93010426a925D2-of-3 Safe. Instant pause and unpause on DCAFactory and BatchExecutor, setExecutor on BatchExecutor, and flushing accrued fees to the treasury. Nothing else. Neither power can change routing or touch a user balance.
Treasury0x310d228297ce1014b33B93F052008FEC3A0329E5Receives protocol fees, in USDC. No control over user strategy funds.
Executor0x596716A2554643098D989b203bFa32A2a39E3E7BThe only address allowed to call executeBatch. It can trigger a due batch and nothing else: no withdrawal, no parameter change, no ability to lower the oracle floor. It runs the scheduled automation and is swappable by the guardian via setExecutor.

The guardian Safe also holds the timelock's proposer, executor, and canceller roles. That lets it queue and later execute a change; it does not let it shorten the two-day delay.

The deploying address holds no privilege. The same deployment transaction batch that handed ownership to the timelock also revoked the deployer's proposer, executor, and canceller roles, and the deployment reverts unless that handover is complete.

User strategy accounts are not owned by the protocol. Each account stores its own user owner, and only that user can withdraw from it.

Canary evidence

Before opening to users, the lifecycle was exercised once on mainnet, end to end, and the figures below were read back from chain rather than taken from a run log. Anyone can verify them against the transactions.

Scope of this evidence

One account, WETH only, Daily tier, one non-empty buy, Aave idle yield enabled, keeper-signed execution. This is the single-account happy path.

It demonstrates the real fee, yield, and withdrawal accounting on mainnet. It does not by itself demonstrate multi-account pro-rata distribution or long-term scheduled automation; those are covered by the invariant, fork, and multi-account tests and by ongoing monitoring.

Execution

ObservationValue
Transaction0x5258dfd1…91fea
Block, result, gas51329142, success, 670,479 gas
Caller0x596716A2554643098D989b203bFa32A2a39E3E7B, the configured executor
FunctionexecuteBatch(address[],uint256[],address,uint256,uint256), selector 0xa3cb075e
Submitted account0xaad5c2969b5997b105dc71a57c3ee620e7bd0c4e, for exactly 5,000,000 USDC base units
Execution fee7,500 USDC base units to the treasury. Exactly 15 bps of the USDC pulled, deducted before the swap
Into the swap4,992,500 USDC
Output1,993,301,164,436,226 wei WETH, all of it delivered to the strategy owner
BatchExecutor balancesUnchanged: 0 USDC and 0 WETH dust, accruedFees 0 (INV-001 held)

Because the fee is taken in USDC up front, the entire swap output belongs to the accounts that paid for it. There is no second transfer of the purchased asset to the treasury to look for.

Withdrawal and closure

ObservationValue
Transaction0x7bc7c65c…cf01e
Block, result, gas51337972, success, 251,182 gas
Functionwithdraw(), signed by the strategy owner
Idle yield5,380 USDC base units gross Aave yield; 807 yield fee (15%); 4,573 net to the owner
ReturnedRemaining principal plus net yield to the owner; the treasury received 8,307 USDC total (execution fee plus yield fee)
Final readsclosed() == true, balance() == 0, depositedAmount() == 0
RegistryThe closed strategy left the active registry

Withdrawal is a full exit and it is terminal. The closed strategy left the active registry and can never be redeposited into.

Supported assets

The protocol launched with the two assets that have an approved direct Uniswap route and a direct Chainlink USD feed. Governance can add more, each behind the two-day timelock, and never an asset without oracle coverage.

AssetToken addressSwap routePrice floor
WETH0x4200000000000000000000000000000000000006Uniswap V3 direct from USDCChainlink ETH/USD, direct feed
cbBTC0xcbb7c0000ab88b473b1f5afd9ef808440eed33bfUniswap V3 direct from USDCChainlink cbBTC/USD, direct feed

An asset with no oracle coverage is never whitelisted, so it is not possible for a batch to execute against an asset that has no price floor.

Oracle configuration

The executor submits a minimum output for each batch, and the contract independently computes its own floor. The swap must clear the stricter of the two, so the executor can only ever make slippage tighter, never looser.

On mainnet that floor comes from Chainlink direct USD feeds for WETH and cbBTC, each with its own staleness window, a conservative slippage haircut, and an L2 sequencer-uptime check with a grace period after recovery. The ComposedPriceOracle is deployed for future market-feed assets but carries no active route at launch. The full design is on the Price oracle page.

Idle yield

USDC waiting between buys can earn Aave V3 yield, chosen per strategy at creation against the approved adapter implementation above. Yield is optional: a strategy can hold idle USDC without lending it. The yield fee applies only to positive yield and only at withdrawal.

External dependencies

DependencyAddressPurpose
USDC0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913Deposit asset.
Uniswap V3 Router0x2626664c2603336E57B271c5C0b26F421741e481Swap execution.
Aave V3 Pool0xA238Dd80C259a72e81d7e4664a9801593F98d1c5Idle-yield venue for strategies that opt in.
Chainlink L2 sequencer feed0xBCF85224fc0756B9Fa45aA7892530B47e10b6433Uptime check; pricing is rejected while the sequencer is down or inside its grace period.

Fees and controls

Fees are stored in DCAFactory and apply protocol-wide. A fee change applies to existing strategies going forward, and is bounded by a hard on-chain cap that no owner can exceed.

FeeCurrentHard capCharged when, and in what
Execution fee15 bps (0.15%)100 bps (1%)Computed on the USDC collected for each batch and deducted in USDC before the swap. The treasury is paid in USDC; the whole swap output goes to users.
Yield fee1500 bps (15%)3000 bps (30%)Only on positive idle yield, only at withdrawal.

There is nothing else. No subscription, no management fee, no performance cut, and no referral share: no contract computes, stores, or pays one.

Example: a batch collecting 1000 USDC pays 1.50 USDC to the treasury and swaps the remaining 998.50 USDC. Every unit of the asset bought with that 998.50 is distributed to the users who funded it.

The owner can change supported assets, the swap router, the oracle, the treasury, the approved yield implementations, and fees within caps, each after the two-day delay. Pausing, unpausing, and switching the executor belong to the guardian and are instant. Neither can withdraw from a user strategy account.

Execution and monitoring

Execution is scheduled automation from the executor address. What it does when it runs: it reads the factory's active registry for accounts that are both due and able to fund an execution, groups them by target asset, reads the live on-chain swap route, takes a quote, and submits a batch with a quote-derived minimum output. A failed or zero quote skips the batch rather than submitting it with a weak minimum. Each batch is simulated with a zero-gas eth_call first, so a batch that would revert deterministically is skipped without spending gas, while one that lost a transient in-block price race is retried with a fresh quote and escalating tolerance up to a cap. The contract's own oracle floor applies on every attempt and cannot be bypassed by any of this.

Monitoring covers executor gas balance, a scheduling heartbeat, oracle health and per-feed staleness, treasury-fee reconciliation, pending-withdrawal events, repeatedly skipped accounts, empty batches, and the timelock queue, each labelled by the stack it belongs to.

If execution is unavailable for any reason, scheduled buys are delayed. Withdrawals are unaffected: they do not depend on the executor, and no pause can block them.

Reviewer checklist

  • Confirm the verified source for each address above on Basescan for Base mainnet.
  • Read owner() on DCAFactory, BatchExecutor, SwapRouter, and the oracle. Each should be the TimelockController, not a Safe and not an individual key.
  • Read getMinDelay() on the timelock. It should be 172800 seconds (two days).
  • Confirm the deploying address holds no timelock role: check hasRole for proposer, executor, and canceller.
  • Read guardian() on DCAFactory and BatchExecutor, and confirm the guardian's reach is limited to pause, unpause, setExecutor, and fee flushing.
  • Read treasury(), executionFeeBps(), and yieldFeeBps() on DCAFactory. There is no referrer-share getter to read.
  • Read executor(), priceOracle(), and the whitelisted assets on BatchExecutor.
  • Check the SwapRouter adapter mapping and the Uniswap adapter route for WETH and cbBTC.
  • Verify the canary transactions above: the caller, the selector, the fee split, and that the whole swap output reached the user.
  • Review the withdrawal path in UserStrategyAccount. Withdrawal is owner-only, remains available while the protocol is paused, and is terminal.