Market Creation

Complete guide to creating Lotus markets with MarketParams construction, IRM and liquidation parameter encoding, and calling createMarket with examples.

Create a Lotus market end-to-end: construct MarketParams, encode IRM and liquidation module parameters, and call createMarket.

Prerequisites

  • The IRM, each LLTV value, and the liquidation module must be enabled via the admin enablelists (see Reference → Admin).

  • You need collateral token addresses, oracle addresses, and LLTV values for each tranche.

  • Market creation is permissionless — anyone can call createMarket as long as all components are on the enablelists.

Step 1 — Construct MarketParams

Build the MarketParams struct. All arrays must be the same length, which defines the number of tranches. Tranches are ordered by index: tranche 0 is the most senior (lowest risk), and the last tranche is the most junior (highest risk).

MarketParams memory params = MarketParams({
    loanToken: 0xA0b8...USDC,                         // ERC-20 that lenders supply
    irm: 0x1234...AdaptiveLinearKinkIrm,               // Must be enabled
    liquidationModule: 0x5678...OracleIncentiveModule,  // Must be enabled
    collateralTokens: new address[](3),
    oracles: new address[](3),
    lltvs: new uint128[](3)
});

// Tranche 0 (senior): WETH collateral, 80% LLTV
params.collateralTokens[0] = 0xC02a...WETH;
params.oracles[0]          = 0xABCD...ETH_USDC_Oracle;
params.lltvs[0]            = 0.80e18;

// Tranche 1 (mid): wstETH collateral, 85% LLTV
params.collateralTokens[1] = 0x7f39...wstETH;
params.oracles[1]          = 0xBCDE...wstETH_USDC_Oracle;
params.lltvs[1]            = 0.85e18;

// Tranche 2 (junior): cbBTC collateral, 90% LLTV
params.collateralTokens[2] = 0x2260...cbBTC;
params.oracles[2]          = 0xCDEF...cbBTC_USDC_Oracle;
params.lltvs[2]            = 0.90e18;

Each field explained:

Field
Description

loanToken

The ERC-20 that lenders supply and borrowers receive

irm

The IRM contract (must be on the enablelist)

liquidationModule

The liquidation module contract (must be on the enablelist)

collateralTokens[]

One per tranche — the ERC-20 borrowers post as collateral

oracles[]

One per tranche — must implement IOracle.price()

lltvs[]

One per tranche — the liquidation loan-to-value threshold (WAD); each must be on the enablelist

Validation rules enforced by createMarket:

  • All three arrays must have the same length (at least 1).

  • Each LLTV must be enabled via enableLltv.

  • The IRM must be enabled via enableIrm.

  • The liquidation module must be enabled via enableLiquidationModule.

  • The market must not already exist (duplicate MarketParams hashes are rejected).

Step 2 — Encode IRM Parameters

The irmParams bytes are forwarded to the IRM's initialize function. The encoding depends on which IRM you use.

AdaptiveLinearKinkIrm

The adaptive IRM automatically adjusts rates toward a target utilization. It decodes irmParams as abi.encode(address operator, AdaptiveConfig[] configs).

The helper function getEncodedAdaptiveIrmParams(operator, configs) on the IRM contract produces the same encoding.

Validation rules per config:

  • targetUtilization must be in [0.01e18, 0.99e18]

  • initialRateAtTarget must be in [minRateAtTarget, maxRateAtTarget]

  • maxRateAtTarget must be ≤ 56,816,320,054 (MAX_BORROW_RATE_PER_SECOND, ~500% APY)

  • curveParameter must be in [1e18, 10e18]

  • adjustmentSpeed must be ≤ 1e18

  • gracePeriod must be ≤ 7 days

  • minUpdateInterval must be ≤ 24 hours

  • maxRateChange must be in [0.01e18, 0.5e18]

FixedRateIrm

The fixed IRM sets a constant borrow rate per tranche. It decodes irmParams as abi.encode(address operator, uint256[] fixedRates).

The helper function getEncodedIrmParams(operator, fixedRates) on the IRM contract produces the same encoding. Each rate must be ≤ MAX_BORROW_RATE_PER_SECOND (56,816,320,054).

ManagedLinearKinkIrm

The managed IRM uses a piecewise-linear curve with a configurable kink. It decodes irmParams as abi.encode(address operator, TrancheRateConfig[] rateConfig).

The helper function getEncodedIrmParams(operator, rateConfig) on the IRM contract produces the same encoding.

Validation rules per config:

  • baseBorrowRate must be ≤ targetBorrowRate

  • gentleSlope must be ≤ steepSlope

  • targetBorrowRate must be ≤ MAX_BORROW_RATE_PER_SECOND

  • steepSlope must be ≤ MAX_SLOPE_PER_SECOND (56,816,320,054)

Rate formula: Below the kink, rate = baseBorrowRate + gentleSlope.mulWad(utilization). Above the kink, rate = targetBorrowRate + steepSlope.mulWad(utilization - targetUtilization). Here mulWad(x) means * x / 1e18.

Converting APR to WAD per Second

All IRM rates are in WAD per second. To convert from an annual percentage rate:

Where SECONDS_PER_YEAR = 31,536,000. For example, 2% APR = 0.02e18 / 31_536_000 ≈ 634,195,839.

Step 3 — Encode Liquidation Parameters

The liquidationParams bytes are forwarded to the liquidation module's initializeMarket function.

OracleIncentiveLiquidationModule

This module prices liquidations using the tranche oracle and applies a configurable incentive factor. It decodes liquidationParams as abi.encode(address operator, uint256[] factors).

The helper function getEncodedLiquidationParams(operator, factors) on the module contract produces the same encoding.

Each factor must be in [MIN_LIQUIDATION_INCENTIVE, MAX_LIQUIDATION_INCENTIVE] = [1e18, 1.15e18]. A factor of 1.05e18 means the liquidator receives 5% more collateral value than the debt they repay.

Step 4 — Call createMarket

The function:

  1. Validates all enablelist requirements and array lengths.

  2. Creates empty tranches for each entry.

  3. Calls IIrm(irm).initialize(marketId, irmParams) to configure the IRM.

  4. Calls ILiquidationModule(liquidationModule).initializeMarket(marketId, liquidationParams) to configure the liquidation module.

Step 5 — Verify

Compute the market ID and verify the market was created:

TypeScript Example

See Also

Last updated