Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions apps/docs/operations/mainnet-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ This does not include commissioning the security audit itself, which stays a fir

Unlike `admin`/`usdc`/`musdc`/`adapter`, which are constructor arguments chosen at deploy time, the values below are compiled-in Rust constants: baked into the WASM you build and upload, not something you pass on the command line. There is nothing to "select" for these in the sense of a CLI flag; the parameter-selection work is confirming, before you build, that the values already in source are the ones you want live, since changing any of them means a full redeploy (adapters and the vault have no in-place upgrade path, see "Rollback plan" below).

| Parameter | Value | Source | Notes |
| ------------------------------------------------------------------------------- | -------------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Migration timelock (`MIN_LEDGER_GAP`) | 17,280 ledgers (~1 day) | `packages/contracts/vault/src/storage.rs` | Raised from ~1 minute in [#557](https://github.com/drydocs/meridian/issues/557) specifically so this is a real observation window on mainnet, not just a valuation-stability check. Do not lower this for a "faster" mainnet migration flow without understanding you are removing the thing [#557](https://github.com/drydocs/meridian/issues/557) exists for. |
| Admin migration slippage ceiling (`MAX_ADMIN_SLIPPAGE_BPS`) | 500 bps (5%) | `packages/contracts/vault/src/storage.rs` | Hard ceiling on `migrate_adapter`'s caller-supplied `max_slippage_bps`, previously unbounded (up to 10,000/100%) other than the type range. A compromised or careless admin call can no longer authorize losing more than 5% in one migration. |
| Instance TTL bump / threshold (`INSTANCE_BUMP` / `INSTANCE_THRESHOLD`) | 30 days / 29 days | `packages/contracts/adapter-common/src/lib.rs` | Extended on every state-changing vault/adapter call ([#553](https://github.com/drydocs/meridian/issues/553)/#704) so a contract cannot archive itself between active use. Comfortably covers the 1-day migration timelock above; if that timelock is ever lengthened further, re-check it still fits well inside this window. |
| Position TTL bump / threshold (`POSITION_BUMP` / `POSITION_THRESHOLD`) | 120 days / 113 days | `packages/contracts/vault/src/lib.rs` | Per-depositor `Entry`/`Principal` records, deliberately bumped harder than instance TTL: a saver who deposits and does nothing for a quarter is the target user, not an edge case. The permissionless `extend_position_ttl()` entry point exists for a keeper or the depositor themselves to refresh this without needing a vault-level call. |
| DeFindex adapter slippage floor (`SLIPPAGE_BPS`) | 10 bps (0.1%) | `packages/contracts/defindex-adapter/src/lib.rs` | Floors `amounts_min`/`min_amounts_out` on DeFindex deposit/withdraw so those calls reject execution materially below what was requested ([#558](https://github.com/drydocs/meridian/issues/558)), rather than accepting any price DeFindex happens to offer. |
| Migration keeper default/max slippage (`MERIDIAN_MIGRATION_MAX_SLIPPAGE_BPS`) | default `100` bps, max `500` bps | Runtime env var, `packages/stellar-sdk-helpers/src/migration-keeper.ts` | This one _is_ a deploy-time choice, not a compiled constant, but its ceiling is enforced against the contract's own `MAX_ADMIN_SLIPPAGE_BPS` above, so it cannot be configured into a state the contract would reject anyway. |
| Migration keeper minimum improvement (`MERIDIAN_MIGRATION_MIN_IMPROVEMENT_BPS`) | default `50` bps | Runtime env var | Minimum rate improvement a candidate protocol must clear before the keeper migrates to it, to avoid churning between two protocols within noise of each other. |
| Parameter | Value | Source | Notes |
| ----------------------------------------------------------------------------------------------------- | --------------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Migration timelock (`MIN_LEDGER_GAP`) | 17,280 ledgers (~1 day) | `packages/contracts/vault/src/storage.rs` | Raised from ~1 minute in [#557](https://github.com/drydocs/meridian/issues/557) specifically so this is a real observation window on mainnet, not just a valuation-stability check. Do not lower this for a "faster" mainnet migration flow without understanding you are removing the thing [#557](https://github.com/drydocs/meridian/issues/557) exists for. |
| Admin migration slippage ceiling (`MAX_ADMIN_SLIPPAGE_BPS`) | 500 bps (5%) | `packages/contracts/vault/src/storage.rs` | Hard ceiling on `migrate_adapter`'s caller-supplied `max_slippage_bps`, previously unbounded (up to 10,000/100%) other than the type range. A compromised or careless admin call can no longer authorize losing more than 5% in one migration. |
| Instance TTL bump / threshold (`INSTANCE_BUMP` / `INSTANCE_THRESHOLD`) | 30 days / 29 days | `packages/contracts/adapter-common/src/lib.rs` | Extended on every state-changing vault/adapter call ([#553](https://github.com/drydocs/meridian/issues/553)/#704) so a contract cannot archive itself between active use. Comfortably covers the 1-day migration timelock above; if that timelock is ever lengthened further, re-check it still fits well inside this window. |
| Position TTL bump / threshold (`POSITION_BUMP` / `POSITION_THRESHOLD`) | 120 days / 113 days | `packages/contracts/vault/src/lib.rs` | Per-depositor `Entry`/`Principal` records, deliberately bumped harder than instance TTL: a saver who deposits and does nothing for a quarter is the target user, not an edge case. The permissionless `extend_position_ttl()` entry point exists for a keeper or the depositor themselves to refresh this without needing a vault-level call (no scheduled keeper calls it yet; wire it alongside `mUSDC.extend_ttl` when cutting over). |
| mUSDC share-token TTLs (`INSTANCE_BUMP` / `INSTANCE_THRESHOLD`, `BALANCE_BUMP` / `BALANCE_THRESHOLD`) | 30d / 29d instance, 120d / 113d balance | `packages/contracts/musdc-token/src/lib.rs` | Mirrors the native SAC ([#1037](https://github.com/drydocs/meridian/issues/1037)): instance TTL is bumped at initialization and on every entry point; existing persistent `Balance` entries are bumped on every read and write so share balances never archive ahead of the vault's 120-day position records; permissionless `extend_ttl(holder)` lets a keeper or holder refresh either without operator `RestoreFootprintOp` intervention. |
| DeFindex adapter slippage floor (`SLIPPAGE_BPS`) | 10 bps (0.1%) | `packages/contracts/defindex-adapter/src/lib.rs` | Floors `amounts_min`/`min_amounts_out` on DeFindex deposit/withdraw so those calls reject execution materially below what was requested ([#558](https://github.com/drydocs/meridian/issues/558)), rather than accepting any price DeFindex happens to offer. |
| Migration keeper default/max slippage (`MERIDIAN_MIGRATION_MAX_SLIPPAGE_BPS`) | default `100` bps, max `500` bps | Runtime env var, `packages/stellar-sdk-helpers/src/migration-keeper.ts` | This one _is_ a deploy-time choice, not a compiled constant, but its ceiling is enforced against the contract's own `MAX_ADMIN_SLIPPAGE_BPS` above, so it cannot be configured into a state the contract would reject anyway. |
| Migration keeper minimum improvement (`MERIDIAN_MIGRATION_MIN_IMPROVEMENT_BPS`) | default `50` bps | Runtime env var | Minimum rate improvement a candidate protocol must clear before the keeper migrates to it, to avoid churning between two protocols within noise of each other. |

Also confirm before building: `CONTRACT_ADDRESSES.mainnet` and `KNOWN_POOLS.mainnet` in `packages/shared/src/constants.ts` / `packages/stellar-sdk-helpers/src/known-pools.ts` now carry the real Meridian vault/mUSDC/Blend-pool addresses from the "Mainnet deployment record" below (DeFindex vault/factory are still placeholder-empty; no DeFindex mainnet deployment has happened). See "Updating the app to use the new deployment" below for what fills these in on a future redeploy, and don't assume they're already correct just because the testnet equivalents are.

Expand Down Expand Up @@ -73,7 +74,7 @@ CONTRACT_ADDRESSES.mainnet.blend.pool = "..."; // the Blend pool actually deploy
Adapter and vault contracts have no in-place upgrade path. This is already true on testnet (see "Pushing new adapter code to a live vault" and "Vault migration history" in the testnet doc) and does not change on mainnet, except that every scenario below now involves real depositor funds instead of testnet USDC. This section covers what to do when the deployed code itself needs to change. For live operational incidents where the code is fine but something is actively going wrong (a suspected admin key compromise, deciding whether to pause, rotating a keeper secret), see [Incident Response](./incident-response.md), not this page: [#706](https://github.com/drydocs/meridian/issues/706)'s own scope is deploy-time rollback, and duplicating that content here would only let the two drift out of sync.

- **A bug is found in adapter code, vault not yet compromised.** Deploy a fixed adapter targeting mainnet (`NETWORK=mainnet VAULT_ID=$VAULT_CONTRACT_ID DEPLOYER=my-deployer bash scripts/redeploy-blend-adapter.sh`) and cut over via `migrate_adapter` (vault has depositors) or `set_adapter` (it doesn't), matching the procedure documented in the testnet runbook, admin-authorized either way. No user action required; positions are denominated in vault shares and untouched by the swap.
- **A bug is found in vault code itself.** There is no adapter-swap equivalent for the vault. This requires a full cutover: deploy a new vault (plus new mUSDC) per "Deployment sequence" above, update `CONTRACT_ADDRESSES`/`KNOWN_POOLS` to point the app at it, and leave the old vault running untouched but unreachable through the app. `withdraw()` still works on the old contract for anyone holding shares there; there is no automatic migration or sweep. Communicate the cutover to depositors and expect a manual withdraw-then-redeposit on their part, the same as the 2026-08-20 testnet cutover documented in "Vault migration history".
- **A bug is found in vault code itself (or in mUSDC, whose address is an immutable vault constructor argument).** There is no adapter-swap equivalent for the vault or mUSDC. This requires a full cutover: deploy a new vault (plus new mUSDC) per "Deployment sequence" above, update `CONTRACT_ADDRESSES`/`KNOWN_POOLS` to point the app at it, and leave the old vault running untouched but unreachable through the app. `withdraw()` still works on the old contract for anyone holding shares there; there is no automatic migration or sweep. Communicate the cutover to depositors and expect a manual withdraw-then-redeposit on their part, the same as the 2026-08-20 testnet cutover documented in "Vault migration history".

`begin_migration`'s ~1-day timelock (see parameter selection above) is the intended detection-and-reaction window for a malicious `begin_migration` call, and the [alert keeper](./alert-keeper.md) is what surfaces that immediately rather than by chance; what to actually do about it once surfaced is [Incident Response](./incident-response.md)'s territory. `get_migration_snapshot()` and the admin-history endpoints (`getRpcAdminHistory`) are readable by anyone, not just the admin, and are useful for independently verifying on-chain state during any of the above rather than trusting the frontend's cached view of it.

Expand Down Expand Up @@ -104,7 +105,7 @@ None of these block a mainnet deploy today. What remains before going live:

## Mainnet deployment record

**Current deployment**, live as of 2026-09-07, using `mainnet-deployer` (`GAAMI2TTCG4526SWGFDA4LOBEOPLYFUMMGMSLRMWNMC4PGCC5F2SOVZU`) as `DEPLOYER` and `mainnet-admin` (`GBO6FBVTHGNDT33T4MCW3FAVIRP3H2LAHURRK6B4B23274V6RMFHOWMW`) as `ADMIN`/`ADMIN_KEY`, wired to Circle's mainnet USDC and the Blend mainnet USDC pool (Fixed V2). No audit was commissioned before this deploy; see the security-audit checklist item above, still open.
**Current deployment**, live as of 2026-09-07, using `mainnet-deployer` (`GAAMI2TTCG4526SWGFDA4LOBEOPLYFUMMGMSLRMWNMC4PGCC5F2SOVZU`) as `DEPLOYER` and `mainnet-admin` (`GBO6FBVTHGNDT33T4MCW3FAVIRP3H2LAHURRK6B4B23274V6RMFHOWMW`) as `ADMIN`/`ADMIN_KEY`, wired to Circle's mainnet USDC and the Blend mainnet USDC pool (Fixed V2). No audit was commissioned before this deploy; see the security-audit checklist item above, still open. Note ([#1037](https://github.com/drydocs/meridian/issues/1037)): the mUSDC contract below (`CAEJJPN73VOEWUVCXCXMIXOHMFLUXAYVFMCTCJHQFI5R2NIB2S5YTOFL`) was built from `be55e76` before mUSDC gained instance/balance TTL management, and `musdc` is an immutable constructor argument on `CBRAD5MD7CCXNXRLRGTRKG4NNZKR3N643VUEBNJGWB2L6KLZDLFWMXHQ`; activating on-chain mUSDC TTL management therefore requires a paired mUSDC + vault redeploy cutover per "Rollback plan" above, planned together with off-chain keeper refresh for `vault.extend_position_ttl` and `mUSDC.extend_ttl`.

- vault: `CBRAD5MD7CCXNXRLRGTRKG4NNZKR3N643VUEBNJGWB2L6KLZDLFWMXHQ`
- blend-adapter: `CBNKERYAG7VZNBH2V3TF5JBXLD3MXLVQW5GG4AO445EUDCPKP4D2DDP2`
Expand Down
Loading