---
title: "Deployer"
description: "Per-chain deploy order, CREATE3 addresses known before the ceremony, and the handover to the admin multisig."
audience: tech
type: reference
status: live
lang: en
updated: "2026-09-17"
publish: true
aliases: [3-2-deployment-upgrades]
---
# Deployer

## 1. Overview

Per chain, the protocol deploys:

1. The shared `AccessControl` singleton, one `ExternalOracle`, and two [UUPS](/docs/glossary#uups-universal-upgradeable-proxy-standard) singletons behind ERC-1967 proxies (`Admin`, `Flash`).
2. The `Pool` reference implementation and `PoolFactory`. The factory is the beacon: `beacon()` returns `address(this)` and `implementation()` is the one slot every live pool reads (`PoolFactory.sol`).
3. Four deployed, linked libraries auto-deployed by `forge` via CREATE2 and linked into their callers: `PoolConfig`, `PoolLiquidity`, `Pricing`, `NUQuartic`. See [§4](#4-non-upgradeable-components).
4. Per-pool ERC-1967 beacon proxies, deployed via `PoolFactory.createPool` (`LibClone.deployDeterministicERC1967BeaconProxy`). Each proxy has its own storage and reads `PoolFactory.implementation()` for its code.

The `Pool` implementation constructor deploys the shared `LPToken` implementation that every per-leg receipt clones (`Pool.sol`), so it is not a separate deploy step.

Three upgrade paths, all operated by the owner multisig after handover ([Admin](/docs/3-1-2-admin)):

- Beacon impl swap at `PoolFactory` (`GOVERNANCE` tier delay), re-pointing every live pool at once.
- UUPS on `Admin` and `Flash`, gated by the `GOVERNANCE` tier delay via `requestUpgrade` / `executeUpgrade` only.
- Oracle beacon: one immutable `OracleProxy` address per chain reads `OracleBeacon.implementation()`; the beacon swaps it through `requestUpgrade` → `LISTING` delay (1 day) → `executeUpgrade`, guardian-cancellable. Pools hold that one address in `OracleConfig`; there is no per-leg repoint op.

The `Router` has no upgrade path: it holds no state and no funds between calls, so a new one is a new deployment ([Composability §2](/docs/5-1-4-composability#2-routing-through-the-router)).

Tier durations are deploy-time data, not constants of the design; the schedule is in
[Access Control §3](/docs/3-1-overview#3-timelock-parameters).
Every duration quoted on this page is the `PROD_DELAYS` value.

`Pool` bakes the `Admin` and `Flash` proxy addresses as immutables and `PoolFactory._validateImplementation` pins them on every fleet upgrade, so a live pool can never be re-pointed at a different governance or flash contract. Those two must be proxies from the first deploy or their logic is frozen for the fleet's life.

Interactions between distinct contracts (Admin↔Pool, Factory↔Pool, Flash↔Pool) are normal external calls, not [`delegatecall`](/docs/glossary#delegatecall). The DELEGATECALLs happen only inside a single `Pool`, to its linked libraries; see [AIMM Overview §2.1](/docs/1-overview#21-standalone-singletons--pool-proxies).

## 2. Architecture

### 2.1. Core components

1. Pool reference impl (`Pool.sol`)
   - Standalone AIMM contract. Reads the external-mark feed (`ExternalOracle`); no internal TWAP.
   - Deployed once per protocol version; never called directly by users.
   - Each pool is an ERC-1967 beacon proxy pointing at the factory, initialised via `initialize(...)`.

2. `PoolFactory.sol`
   - Deploys `Pool` beacon proxies (`createPool(...)`) and holds the beacon-swap timelock (`pendingReferencePool`, `upgradeTimelock`).
   - Maintains `allPools` + `officialPools` + `isPool` registries.

3. UUPS singletons, each an ERC-1967 proxy over an implementation carrying the immutable `AC` (AccessControl) ref:
   - `Admin.sol`, per-pool timelock queue + restricted setters. Holds real per-pool state (`bootstrapSealed`, `riskFences`, `pendingOps`), so redeploy-and-repoint is not equivalent to an upgrade: a fresh `Admin` would arrive with `bootstrapSealed == false` fleet-wide and re-open the untimelocked `addAsset` / `setCurve` lanes on live sealed pools.
   - `Flash.sol`, ERC-3156-style (postFlashLoan variant) flash-loan provider. Holds no persistent state; `UpgradeGate`'s 50 slots are the whole layout.

4. `AccessControl.sol`, single owner source of truth, plus the treasury and factory pointers, the guardian set, and the packed governance delay schedule.

5. Fee sink: each pool's `treasury()` is a plain address, not a contract. See [Treasury](/docs/3-1-6-treasury).

## 3. Deployment process

### 3.0. Phase 0: the CREATE3 factory, and why addresses are known before the deploy

Every singleton below is deployed through a CREATE3 factory rather than plain `CREATE`, so its
address is `f(factory, deployerKey, salt)` and does not depend on the contract's bytecode.
Consequences: the address is identical on every chain BTR launches on; it is known before the
contract is compiled, so keeper configs, monitoring and integration constants can be filled in
ahead of the ceremony; and it survives a contract-generation change, since a new oracle version
deploys to the reserved address for that role.

The factory itself is deployed once per chain from a frozen initcode artifact via the canonical
arachnid `CREATE2` proxy, never rebuilt from source. A rebuild under different compiler settings
produces different initcode, therefore a different factory, moving every address derived from it.

The deployer key is part of the address. The factory salts with `keccak256(deployerEoa ++ salt)`,
so the same salt signed by a different key lands somewhere else, silently, with no revert. Mainnet
addresses are mined for one specific key; signing a mainnet deploy with any other burns the
reservation unrecoverably. Deploy scripts assert the signing key against the record before
broadcasting, and the reserved mainnet addresses are published in
[Contract Addresses](/docs/2-1-contract-addresses), so anything appearing at one of them ahead of a
published deployment is recognisably not BTR.

### 3.1. Phase 1: deploy shared and singletons

Phase 1 deploys the shared `AccessControl` singleton (owner, treasury, and the packed governance-delay word passed at construction; `GOV_DELAYS` is read from the environment with no default, so an unset variable aborts the deploy), then the `Admin` and `Flash` implementations, each carrying the immutable AC ref, behind ERC-1967 proxies via `LibClone.deployERC1967`. Every mapping starts empty: nothing to initialise, no uninitialised-proxy window.

A chain whose oracle stack shipped first reuses that chain's existing `AccessControl` rather than minting a second one. Two ACs would split protocol governance and leave the oracle's guardian unable to halt the pools it feeds. `ExternalOracle` takes the AC address as a constructor argument, so this is a deploy-script decision, not contract-enforced. The one deliberate exception is a reference oracle instance, governed separately from the primary it polices; see [Oracle Keeper](/docs/3-1-5-oracle-keeper).

### 3.2. Phase 2: deploy pool reference and factory

Phase 2 deploys the reference `Pool` implementation plus the `PoolFactory`, which is itself the beacon holding the fleet implementation slot. The `Pool` `AC` / `Admin` / `Flash` wiring is immutable and re-asserted on every fleet swap, with `admin` and `flash` the PROXY addresses, never the implementations. Its constructor also deploys the `LPToken` implementation the receipts clone.

### 3.3. Phase 3: deploy a pool

```solidity
// Permissionless: any address may call this. Deployment ≠ administration -
// there is no `owner` param; every pool's admin functions resolve to the
// single protocol-wide AccessControl owner regardless of who deployed it.
address pool = factory.createPool(
    baseToken,          // anchor token (e.g. USDC)
    tokens,             // address[] of assets to register on the pool
    initdata             // abi-encoded call forwarded to the pool (typically `initialize(baseToken, wnative, feeParams)`)
);
// Pool is now an initialised ERC-1967 beacon proxy reading `factory.implementation()`.
```

### 3.4. Phase 4: wire per-pool config

```solidity
admin.requestOp(pool, uint8(IPool.OpType.ADD_ASSET), subject, payload);
// wait the LISTING tier delay
admin.executeAddAsset(pool, token);   // execute paths stay named, one per op
// The three per-asset params are a separate owner call (instant pre-seal or on a tighten):
admin.setAssetParams(pool, token, minLiquidity, minFeePbps, vegaBps);
```

`decimals` is not an argument: it is read from the token at listing. Inventory skew takes no per-asset argument either; it is a fixed protocol law.

`setAssetParams` applies immediately only before `bootstrapSealed[pool]` or on a defensive tighten; otherwise it queues itself at `TUNING`. Policy: [Access Control §4](/docs/3-1-overview#4-owner-gated-op-classes).

## 4. Non-upgradeable components

A library is deployed and linked when it declares at least one `external` or `public` function; otherwise the compiler inlines it into its callers.

- Deployed, linked: `PoolConfig.sol`, `PoolLiquidity.sol`, `Pricing.sol`, `NUQuartic.sol`. `Pool` links the first three directly; `NUQuartic` is linked into `PoolConfig` and `Pricing` additionally into `PoolLiquidity`, so a link check that inspects only `Pool` reports three and misses one.
- Inlined: `AnchorTreeLib.sol`, `PoolIOLib.sol`, `PoolHooksLib.sol`, `FeedMathLib.sol`, `TransientCacheLib.sol`, `PoolConstantsLib.sol`.
- `PoolFactory`, the `LPToken` implementation, `ExternalOracle`.
- Shared `Constants.sol`, `Errors.sol`, `Timelock.sol`, `AccessControl.sol`.

## 5. Governance delays are deploy-time data

The delay schedule is a constructor argument, not a chain-dependent branch. `AccessControl(owner, treasury, govDelays)` stores the packed word in the immutable `GOV_DELAYS`, and every governed contract derives its own tier delays from it at construction. No contract reads `block.chainid` to pick a delay, so testnet and mainnet run identical code on different data.

Deploy scripts read `GOV_DELAYS` from the environment with no default: an unset variable aborts the deploy rather than silently picking a side. Production passes `Constants.PROD_DELAYS`; public testnets pass `Constants.TESTNET_DELAYS`. There is no throwaway zero-delay option: the deploy path rejects any schedule under `Constants.MIN_ARMED_DELAY`, so every deployed fleet arms its timelocks. Every delay quoted in these docs is the `PROD_DELAYS` value; a testnet fleet runs the shorter schedule for the same tier.

## 6. Permissionless pool deployment

```solidity
address myPool = factory.createPool(baseToken, tokens, initdata);
admin.requestOp(myPool, uint8(IPool.OpType.ADD_ASSET), subject, payload);
// ... wait timelock, then execute (as the AC owner - the deployer does NOT
// automatically gain admin rights over the pool they deployed).
```

- Timelocks are enforced on every pool, which protects pool users regardless of who administers it.
- Each asset picks its oracle mode at listing, and every non-base spoke carries a **mandatory** reference band (`requireExternalSpokeBound`, `PoolConfig.sol`); the modes are defined in [Oracle Keeper](/docs/3-1-5-oracle-keeper) and the band in [Flow Guards](/docs/3-2-1-flow-guards).

## 7. Best practices for pool reference-impl developers

1. Storage layout is append-only: `PoolStorage` field order and types are frozen across implementation versions, new fields appended only. Load-bearing, because a beacon swap moves every live pool onto the new code while keeping its existing storage.
2. No constructors with side effects. `Pool` runs behind a proxy; only `initialize(...)` runs per-instance.
3. No new external dependencies without an explicit migration story. A beacon swap migrates the whole fleet in one transaction, so a new dependency must be safe for every live pool simultaneously.
4. Test the implementation AND a deployed proxy. A proxy is what users hit; impl in isolation skips the real DELEGATECALL edge cases, since the linked-library DELEGATECALLs only trigger through a deployed proxy.

## 8. Upgrade checklist

- [ ] `PoolStorage` layout unchanged or safely extended (append-only).
- [ ] No constructor side effects in the reference impl.
- [ ] `forge test` green against a deployed proxy, not the implementation alone.
- [ ] Fleet-wide blast radius reviewed: the swap re-points every live pool, including third-party ones.
- [ ] Timelock requested and announced; the delay window is the review period.
- [ ] Testnet swap executed and observed.
- [ ] ABIs regenerated and republished to the backend ABI endpoint (`GET /v1/abis/<ContractName>`); abi-freshness check green.
- [ ] Emergency halt paths verified.
- [ ] Monitoring + alerting wired to the new impl + factory.
- [ ] Cancel path tested (so the swap can be aborted if needed).
