Skip to main content
Deploy governance first, followed by the controller, price aggregator, central pool, and position NFT. Configure markets and spoke listings before unpausing. This procedure follows committed source at d008ee1a / a2486b25. It describes deployment behavior, not the current state of a network. Record the release, WASM hashes, transaction results, and verified contract links for each deployment. Operators use the stellar CLI, usually wrapped by make targets. The same sequence applies to testnet and mainnet. Substitute testnet or mainnet for <network> below.

Deployment configuration

Prerequisites

  • The stellar CLI and jq. The committed build uses Soroban SDK 28 and pins CLI 28.0.0 in make install-stellar-cli.
  • A compatible Rust toolchain and the contract’s WASM build target.
  • A funded signing identity (default deployer), or SIGNER=ledger for a ledger-backed signer.

Build artifacts

make deploy-artifacts stages governance.wasm, controller.wasm, pool.wasm, position_nft.wasm, and price_aggregator.wasm. Deployment uploads the required WASM and passes the returned hashes to the deployment calls; governance does not start with those hashes. To use an attested release instead of building the local checkout, pass RELEASE_TAG=<release-tag> to a deployment or upgrade target. Select a release whose ABI matches the integration. Local changes are not part of that release.

Deployment sequence

Governance deploys first because it controls subsequent deployment calls. The controller starts paused. The pool deploys once for the whole protocol, and every asset registers on that one pool. make <network> deploy runs the first five phases below: governance, controller, price aggregator, pool, and position NFT. make <network> setup also wires addresses, creates hubs and spokes, and configures markets and memberships.
1

Upload WASM and deploy governance

Uploads the pool and controller WASM, then deploys governance with __constructor(admin, min_delay).That constructor sets admin as owner, sets it as the access-control admin, grants it all five roles (PROPOSER, EXECUTOR, CANCELLER, GUARDIAN, ORACLE), and stores min_delay. A zero delay is rejected with #39 InvalidTimelockDelay.Record the governance id and delay. Later steps need both.
2

Deploy the controller

Governance calls deploy_controller(controller_wasm_hash) immediately. It is one-time and owner-gated; a second call reverts #5 PoolAlreadyDeployed.The constructor sets governance as owner, both position limits to POSITION_LIMIT_MAX = 5, and the minimum-borrow floor to $5 WAD. It starts the controller paused. Controller administration uses owner authorization; governance holds the role registry.
3

Deploy the price aggregator

The owner calls deploy_price_aggregator(wasm_hash) on governance. Because the controller was deployed first, this also wires it atomically. Without that wiring, priced calls fail with #27 AggregatorNotSet. Replacing the address uses the timelocked SetPriceAggregator operation through execute_self.
4

Deploy the central pool

Schedule and execute DeployPool(wasm_hash) through the timelock. The hash is an argument — there is no template-registration step.Run it once; a repeat reverts #5 PoolAlreadyDeployed. Code upgrades use UpgradePool(hash).
5

Deploy the position NFT

Schedule and execute DeployPositionNft(...) through the timelock.The account_id is the NFT token_id, and the controller asks the NFT contract who owns an account on every authority check. Missing NFT wiring can raise #53 PositionNftNotSet during account creation; an ownership lookup can instead raise #24 AccountNotFound. Metadata-only views do not establish ownership or prove NFT deployment.
6

Wire the swap aggregator and accumulator

make <network> configure-controller sets the swap aggregator and the revenue accumulator.The target requires both addresses. Without an accumulator, claim_revenue fails with #211 NoAccumulator. Set ALLOW_MISSING_AGGREGATOR=1 or ALLOW_MISSING_ACCUMULATOR=1 only if you deliberately want to defer one.
7

Register each market on the shared pool

Governance schedules a typed AdminOperation per step through propose(proposer, op, salt), then execute after the delay. Create each hub (CreateHub) and target spoke (AddSpoke) before its listings. Governance also exposes immediate GUARDIAN calls to create these empty containers. Market registration and asset listing still require their scheduled operations. Per asset:
  1. AdminOperation::CreateLiquidityPool — registers the hub asset on the existing central pool.
  2. AdminOperation::ConfigureAssetOracle — price reads work only after this.
  3. AdminOperation::AddAssetToSpoke (or EditAssetInSpoke) per spoke.
Market registration needs no token approval. User transactions that pull tokens still need their nested transfer authorization.
create_liquidity_pool reverts with #30 PoolNotInitialized if deploy_pool has not run, #2 AssetAlreadySupported if the market already exists, #43 HubNotActive if the hub is missing or deactivated, and #8 WrongToken if params.asset_id does not match the asset.
8

Configure liquidation curves and Blend pools

Propose and execute SetSpokeLiquidationCurve for each spoke. Propose and execute ApproveBlendPool(pool) for each permitted Blend migration pool.
9

Unpause

Complete the readiness checks, then schedule and execute AdminOperation::Unpause. Only pause is immediate.Mainnet setup leaves the protocol paused. Before unpausing, compare governance’s get_min_delay() with timelock_min_delay_ledgers in configs/networks.json. The mainnet unpause script rejects a delay below that configured floor:
To increase the delay, use make mainnet updateDelay <ledgers> and wait for the timelocked change to execute before retrying unpause.The committed registry uses 12 ledgers for both networks. The script checks that configured floor; it does not establish a production governance policy. Verify the intended delay and deployed governance code before enabling users.

Setup and resume targets

make <network> setup chains: deploy → configure-controller → market, spoke, and Blend setup → rent prepay → status. On testnet it also unpauses; on mainnet it leaves the protocol paused. make <network> resume re-runs everything from configure-controller onward. Run individual targets only for partial deploys or recovery.

Readiness checks

Complete these checks before enabling a network for users:
  • Review workspace tests, integration harness results, fuzz runs, and Certora profiles for the release.
  • Verify contract links, ownership, and governance roles. Confirm pool and position-NFT deployment.
  • Confirm every launched HubAssetKey has a pool market, working oracle, and the required spoke listings and caps.
  • Confirm swap-aggregator and accumulator wiring, position limits, and the minimum-borrow floor.
  • Check keeper coverage for launched markets and required storage entries.
  • Complete a pause/unpause drill on the target network.
Cap values are set per network at listing time. Prefer on-chain views and the app for live caps.

Upgrades

upgradeAll does not upgrade the position NFT or price aggregator. Run their targets separately when the release requires them. Governance self-upgrade uses AdminOperation::UpgradeGov(hash) scheduled with propose and applied with execute_self (make <network> upgradeGovernance). Governance-self operations cannot go through execute, because Soroban does not allow the generic self-call pattern used for controller calls.

Smoke tests

Read market state with make <network> getAllMarkets and make <network> getAllIndexes. make <network> info prints local registry addresses; it does not verify deployed wiring.

Next

Governance

Timelock roles and the propose/execute lifecycle.

Configuration

Validation rules for risk, oracle, and spoke parameters.

Addresses

Network registry and contract-link verification.