Contents

Reference · For integrators

Contracts and addresses

ParameterizedVault is the imdUSD vault: it holds every position (an account's collateral and debt) and is the only contract that mints or burns imdUSD. Start with What is imdUSD for the overview.

All addresses are on Ethereum mainnet, and every contract below is verified on Etherscan and Sourcify. The vault was deployed at block 26166417 on 11 October 2026.

Some contracts are deployed on their own; the vault creates the rest in its constructor. To find a vault-created contract, read the vault's getter, then check that the contract names the vault back before you integrate with it. A listed address is not proof of a deployment until you have checked it on chain.

ContractAddressFixed links and governed values
SwarmRelay0x9AEb55c7A16C11B37DC96BD22a33906D1668e20BNo owner, settings or upgrade path. Each call names its own targets.
WorkOracleFactory0x24458aeF6cf074C7FB09B6734b1247508f137409Anyone may call it; no governor or settings. The vault's copy of its address is written into the contract.
PriceFeed0x070e0603737242B01aBC1D223f7C92D63E86EC97Inherits SwarmFeed. Signer, relayer, data chain, answer type and question are written into the contract. Maximum age (one hour) and deviation bound (2,000 basis points, 20%) are set once at deployment.
NhiFeed0x014DabF6F940c9C383D9ef48636F70D842F65D7ASame setup as PriceFeed, with its own question. Maximum age (one day) and deviation bound (20%) are set once at deployment.
SpotFeed0x77E1684402869Bf2B7c70B8bf21E1C7Ca7104951Same setup, with its own question. Maximum age (one hour) and deviation bound (20%) are set once at deployment.
OracleAsker0x00856Bd495941f3b95dA5b7e374c76d822C9059ENo owner or settings. Its feeds and their request bodies are fixed at deployment. Pays only when the health feed is close to stale, when any feed has been silent a whole lifetime with its allowance wide open, or when IMD's pool has fallen below a price feed (never for a rise); see How updates are paid for. The Intake reports a request that ends without an answer straight back to it, which frees the feed for the next purchase at once.
ParameterizedVault0x0b69F8DA8D8cE521d74Ea69a7A3475B77302EF82Collateral (sIMD), stablecoin, the three feeds, parameters, treasury, usdPriceFeed and collateralPriceFeed are all fixed. Economic settings come from its own Parameters. No upgrade path and no way to swap a feed. The work oracle it creates can be replaced through Parameters, only while minting from work is off; oracle() returns whichever is in use.
ImdUSD0x61aAF8a992A3143e2B0C9cB0030b9fE702854a25Bound to the vault for good; only the vault mints and burns. No governor, pause or upgrade.
Parameters0x3D96A6Ffada9C2E5B8fF8eb3f8AFB360116dA881Its vault cannot be changed. The governor and every hard limit are written into the contract; changes go through the delay in Parameters.
Treasury0x6Ac8fF96Fb8DCF5d79A8eF1E59D9AC23f2BD27BcServes only its vault; that vault's Parameters governs which reserve assets are listed and how they are priced. feeRecipient() returns it. The operator can never withdraw the collateral (sIMD) or a listed reserve asset, and can withdraw imdUSD only above what outstanding bad debt still needs. Anyone may call payStream() (a governed daily imdUSD payment to a governed payee) and fundOracle() (a governed daily IMD budget for price updates).
TreasuryFactory0xbF6410aD823F49D24935135EE230db89faBca6B2No owner or settings. It exists so the vault's deployment code stays under the network's size limit.
UsdPriceFeed0x297b85afc6bbF9d9cCa636F7ED74c56DA79287A0Its IMD/ETH feed is fixed; the Chainlink address and its maximum age (two hours) are written into the contract. No settings or governor.
SharePriceFeed0xF065C8b65AA9Ac6fd9cBAd15301738e91389d9e1Fixed to sIMD and to the vault's UsdPriceFeed. No settings. Quotes USD per 1e18 raw sIMD units.
SwarmWorkOracle0xC7335a836ad85a6ac0830c6CEf602d735B9d8a8AVault, signer, relayer, question and identity adapter are fixed; maximum age (one day) is set once. It has no move limit, since its value is a Merkle root. wage() reads the vault's governed settings.

Contracts the protocol uses

These are not part of the imdUSD deployment, but the contracts above read or pay them.

ContractAddressRole
IMD0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7What sIMD stakes, and what oracle updates are paid in.
sIMD (Staked IMD)0x9Efa934D9fAd4AE28c998a40195646b965a97247The vault's collateral (gem()); IdentityMD's ownerless staking vault.
IdentityMD Intake0xa43e6F75ee006411F79Ac1C84120606C2330DE82Sells oracle answers; OracleAsker pays it and only it may call back.
Chainlink ETH/USD0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419Turns the IMD/ETH price into USD in UsdPriceFeed.
Governance Safe0xbeFd108085613662356aa26A2466Ad3426DA9C32The governor of Parameters and the Treasury's operator, a 2-of-3 Safe. Every change waits out the delay in Parameters.
PegFeeHook0x33cA5c16208EC08aC97d4f6315aaFA792A916044The imdUSD/USDC pool's fee hook; its surcharge goes to the Treasury. See The peg pool.

Constructor and read-back checks

ParameterizedVault(address gem_, address stablecoin_, address oracle_, address priceFeed_, address nhiFeed_, address spotFeed_) takes six contract addresses. The three feeds must be different deployed contracts. The collateral must be a deployed contract and must not be the stablecoin. A work oracle passed in must answer mintingRights(address), and if it has a vault() getter, that getter must name this vault. On mainnet the vault is given a reserved placeholder address for the work oracle instead, which tells it to build one through the factory.

The deployment runs in two stages. The first deploys everything but the vault at addresses computed in advance from fixed salts (the contracts that read each other's addresses as constants need them before they are compiled). The feeds' first values are then bought, relayed and checked against IMD's pool and an outside reference price, because nothing on chain bounds a feed's first value. Only then does the second stage deploy the vault, from a salt the operator keeps private and through a private transaction relay, so nobody can deploy the vault at its address first. Nothing reads the vault's address as a constant.

Once deployed, parameters().vault(), treasury().vault() and stablecoin().vault() must all return the vault, and so must oracle().vault() for SwarmWorkOracle. No follow-up transaction is needed to link the contracts the vault created.

Collateral is sIMD

The vault's collateral is sIMD (Staked IMD), the 24-decimal share token of IdentityMD's staking vault, which holds IMD. gem() returns its address. Because sIMD is a share, the vault creates a SharePriceFeed to price it: the staking vault's convertToAssets(1e18) times the IMD/USD price from UsdPriceFeed, quoted per 1e18 raw sIMD units. The vault never reads decimals(), so quoting per raw unit is what keeps the 24-decimal share correctly valued.

lock takes sIMD. lockIMD takes IMD, stakes it on the depositor's behalf and credits the sIMD that comes back. The vault never unstakes: free, bite and cash pay sIMD, and the staking vault's one-block hold applies to whoever unstakes it.

The Treasury values the vault's collateral per 1e18 raw units too, so sIMD listed as a reserve asset with collateralPriceFeed() as its price counts at the same value the vault gives it. Listing it is a governance proposal like any other reserve change; see Parameters.

See Oracle and question binding for the price sources, Vault functions for everything you can call, and Risks and open questions for who holds which powers.

Sources: docs/MAINNET-RUNBOOK.md, script/DeployMainnet.s.sol, src/ParameterizedVault.sol, src/CDPVault.sol, src/ImdUSD.sol, src/SwarmFeed.sol, src/SwarmRelay.sol, src/Treasury.sol, src/WorkOracleFactory.sol, src/SwarmWorkOracle.sol, src/SharePriceFeed.sol, src/UsdPriceFeed.sol, src/OracleAsker.sol