For the complete documentation index, see llms.txt. This page is also available as Markdown.

Market administration

Everything on this page is called by a market owner (or someone stepping into the role). A market is created by createMarket — whoever calls it becomes the owner of the resulting pair and can later transfer that role, update the LP whitelist, or permanently stop new swaps.

Source: SwapCore.sol (logic). The deployed protocol address is the SwapStore proxy — it holds all storage and funds and delegatecalls into SwapCore — so every call and token approval on this page targets the SwapStore address, and events are emitted under it.

Market lifecycle

createMarket ──► (market is live) ──► transferMarketOwnership ──► acceptMarketOwnership
                       │                                               │
                       │                                               ▼
                       │                                       (new owner active)

                       └──► setMarketLpWhitelist (if lpWhitelistEnabled)

                       └──► terminateMarket ──► (no new swaps; existing swaps still settle)

Note that every call to createMarket produces two market IDs — one BUY_FIXED and one BUY_FLOATING — that share the same reference rate oracle, collateral token, term, and config, while each side carries its own base swap rate oracle. The two sides are independent markets from an ownership and accounting standpoint, but transferMarketOwnership / terminateMarket / setMarketLpWhitelist each operate on a single marketId, so if you want to change both sides you call each function twice.

Termination is not always owner-initiated: if a market's reference oracle dies and a swap settles on the extrapolated-index fallback after the settlement grace period, the protocol terminates both markets of the pair automatically — see terminateMarket.


createMarket

function createMarket(
    address referenceRateOracle,
    address fixedBaseSwapRateOracle,
    address floatingBaseSwapRateOracle,
    address swapToken,
    uint64  leverageMultiplier,
    uint32  swapTerm,
    uint256 utilFeeSlopeWad,
    uint256 kinkUtilization,
    uint256 maxKinkFeeWad,
    bool    earlyExitAllowed,
    uint256 earlyExitFee,
    uint256 liquidationIncentive,
    uint32  numBuckets,
    uint32  bucketInterval,
    address fixedRiskPremiumOracle,
    address floatingRiskPremiumOracle,
    bool    lpWhitelistEnabled,
    uint88  minCollateral,
    Types.RateConvention rateConvention,
    address expectedFeeToken,
    uint256 maxFeeAmount,
    uint256 deadline
) external payable nonReentrant returns (bytes32 fixedMarketId, bytes32 floatingMarketId);

Who calls: anyone. No prior permission required — but if the protocol has a market creation fee configured in Admin, msg.sender has to pay it (ETH or ERC20, depending on CREATE_MARKET_FEE_TOKEN).

What it does: atomically creates a BUY_FIXED / BUY_FLOATING market pair. For each side, it validates oracles, checks parameter bounds, generates a unique market ID (hashing the params with creator, timestamp, and a nonce), initializes the pool and rate index, and emits MarketCreated + MarketConfigured. The caller is recorded as marketOwner on both sides.

Parameters:

Parameter
Meaning

referenceRateOracle

Oracle that provides the floating rate used by the swap's floating leg. Shared between the two sides of the pair.

fixedBaseSwapRateOracle

Oracle that provides the tenor-dependent base rate used in swap pricing and collateral sizing on the BUY_FIXED side. On Cumulative markets it must attest a compounded-equivalent tenor quote via isCompoundedTenorQuote() == true (reverts E613 otherwise).

floatingBaseSwapRateOracle

Base rate oracle for the BUY_FLOATING side. On Cumulative markets it must attest isCompoundedTenorQuote() == false (reverts E614, including when the attestation is missing) — so on Cumulative markets the two base-rate slots need distinct oracles. If either slot advertises a baseRateRing(), both must resolve to the same non-zero shared observation ring (reverts E615).

swapToken

ERC20 used for collateral and settlement. Must be a standard ERC20 (no rebasing, no fee-on-transfer).

leverageMultiplier

WAD-scaled leverage multiplier. 1e18 = 1x, 2e18 = 2x. Capped at 12e18.

swapTerm

Duration of each swap in seconds. Must be > 0.

utilFeeSlopeWad

Slope (WAD) of the linear region of the utilization fee curve. Capped at 10e18.

kinkUtilization

Utilization at which the quadratic region kicks in (WAD, must be < 1e18).

maxKinkFeeWad

Maximum additional fee from the quadratic region (WAD, capped at 10e18).

earlyExitAllowed

Whether buyers can close a swap before expiry via exitSwapEarly.

earlyExitFee

WAD early-exit fee. Capped at 1e18 (100%).

liquidationIncentive

WAD fraction that drives the liquidator reward. Buyer-side liquidations pay a prefunded liquidationBounty = requiredBuyerCollateral × liquidationIncentive / WAD (banked at swap entry, held outside collateralBalance, refunded to the buyer if the swap is never liquidated). Pool-side liquidations pay poolCollateralBacking × liquidationIncentive / WAD from the LP backing. Capped at 5e16 (5%).

numBuckets

Number of time buckets. Must be in [1, 181].

bucketInterval

Seconds per bucket. Must be ≥ 1200 and ≤ swapTerm. numBuckets × bucketInterval must ≥ swapTerm + bucketInterval, and bucketInterval × 6 must ≥ swapTerm (see E729).

fixedRiskPremiumOracle

Optional risk premium oracle for the BUY_FIXED side. Pass address(0) to disable.

floatingRiskPremiumOracle

Optional risk premium oracle for the BUY_FLOATING side.

lpWhitelistEnabled

If true, only addresses on marketLpWhitelist[marketId] can supplyCollateral. The caller is auto-whitelisted.

minCollateral

Minimum per-swap collateral (both buyer and LP). Denominated in swapToken decimals. Must be > 0.

rateConvention

How the reference rate oracle provides data. See Types.RateConvention (Cumulative, SpotRate, or SpotCompoundRate).

expectedFeeToken

Creation fee token the caller accepts (address(0) for ETH). If a non-zero fee is live and its token differs, reverts E154. Ignored when no fee is live.

maxFeeAmount

Maximum creation fee the caller accepts, in fee-token units. If a non-zero fee is live and exceeds this, reverts E155. Ignored when no fee is live.

deadline

Timestamp after which the call is no longer valid (reverts E153).

Returns: (fixedMarketId, floatingMarketId). These are also published in MarketCreated events.

Payable: if Admin.CREATE_MARKET_FEE_AMOUNT() > 0 and CREATE_MARKET_FEE_TOKEN() == address(0), send the fee as msg.value. Excess ETH is refunded to msg.sender at the end of the call. If the fee token is an ERC20, approve the SwapStore address for the fee amount before calling — no msg.value is required. If no protocol multisig is configured, no fee is charged at all (and the ETH sufficiency check E150 doesn't apply).

The creation fee itself is set in Admin behind a 3-day timelock (queueMarketCreationFeeactivateMarketCreationFee; disableMarketCreationFee takes effect immediately). Because a queued fee can activate between the time you sign a transaction and the time it lands, the expectedFeeToken / maxFeeAmount / deadline parameters bind your call to the fee configuration you agreed to — a fee change activated after signing makes the call revert instead of drawing on a standing token allowance.

State changes / events:

  • markets[fixedMarketId] and markets[floatingMarketId] populated, both marked exists = true, marketOwner = msg.sender.

  • Both IDs appended to allMarketIds.

  • Global rateIndex initialized for the reference rate oracle if it wasn't already. The reference oracle's health is probed on every creation — a market can't be created against an oracle that has gone stale since its first use.

  • If lpWhitelistEnabled, marketLpWhitelist[<each>][msg.sender] = true (creator is auto-whitelisted so they can seed liquidity).

  • Emits MarketCreated (×2) and MarketConfigured (×2), plus MarketCreationFeeCollected if a fee was charged (or MarketCreationFeeForegone instead, if the multisig couldn't receive the fee — in which case the fee is refunded to the caller rather than collected).

Reverts:

Code
Reason

E150

ETH fee required but msg.value < feeAmount (only checked when a protocol multisig is set).

E151

ETH refund to the caller failed (excess msg.value couldn't be returned). A failed send to the multisig does not revert — the fee is instead forgone and refunded to the caller.

E152

ERC20 fee token delivered a different amount than requested (fee-on-transfer / rebasing guard — never fires for a standard ERC20).

E153

block.timestamp > deadline.

E154

Live creation fee token differs from expectedFeeToken.

E155

Live creation fee exceeds maxFeeAmount.

E301

Market ID collision (should not happen in practice — nonces are unique).

E600 / E603

Base rate oracle call reverted or returned isValid = false.

E602 / E605

Risk premium oracle (if set) call reverted or returned isValid = false.

E606

Reference rate oracle is unhealthy. Probed on every creation (rateIndex.initialize only reads the oracle on first use, so this probe is what stops a later market from reusing a now-stale oracle).

E607

rateConvention doesn't match the convention already set for referenceRateOracle — it's fixed on first use and shared by every market on that oracle.

E613

fixedBaseSwapRateOracle quote attestation invalid for the market's convention (Cumulative requires isCompoundedTenorQuote() == true; the spot conventions forbid it).

E614

floatingBaseSwapRateOracle attests a compounded-equivalent tenor quote — or fails to attest at all on a Cumulative market.

E615

The paired base-rate oracles don't derive from one shared observation ring: if either advertises baseRateRing(), both must resolve to the same non-zero ring.

E703

numBuckets == 0 or > MAX_NUM_BUCKETS (181).

E704

bucketInterval < 1200 or > swapTerm.

E705

numBuckets × bucketInterval < swapTerm + bucketInterval.

E710

referenceRateOracle == address(0).

E711

Either base swap rate oracle is address(0).

E712

swapToken == address(0).

E713

leverageMultiplier == 0 or > MAX_LEVERAGE (12e18).

E714

liquidationIncentive > 5e16.

E715

swapTerm == 0 or > MAX_SWAP_TERM (10 years).

E716

kinkUtilization >= 1e18.

E717

earlyExitFee > 1e18.

E718

utilFeeSlopeWad > 10e18.

E719

maxKinkFeeWad > 10e18.

E720

Invalid rateType (unreachable from the enum but guarded).

E721

minCollateral == 0.

E729

bucketInterval × MAX_ACTIVE_WINDOWS < swapTerm (MAX_ACTIVE_WINDOWS = 6) — bucketInterval is too fine, which would leave too many active windows and expose the withdraw scan to DoS. Pick a longer bucketInterval (at least swapTerm / 6) or a shorter swapTerm.

See also: getAllMarketIds, transferMarketOwnership, terminateMarket.


transferMarketOwnership

Who calls: the current marketOwner of marketId.

What it does: step one of a two-step ownership handoff. Sets pendingMarketOwner[marketId] = newOwner. No state on the market itself changes yet — the actual owner only flips when newOwner accepts.

Pass newOwner = address(0) to cancel an in-flight transfer. There is no renounceOwnership — a market always has an owner once created.

Modifiers: marketOwner(marketId) (reverts with E202 if not the current owner, E300 if the market does not exist).

State changes / events:

  • pendingMarketOwner[marketId] = newOwner.

  • Emits MarketOwnershipTransferStarted(marketId, currentOwner, newOwner).

Reverts:

  • E300 — market doesn't exist.

  • E202 — caller is not the current market owner.

See also: acceptMarketOwnership.


acceptMarketOwnership

Who calls: the address previously set as pendingMarketOwner[marketId].

What it does: step two of the handoff. Flips markets[marketId].marketOwner to msg.sender and clears the pending slot. After this call, the new owner has full control over terminateMarket and setMarketLpWhitelist, and will receive the creator fee split on new swaps (if configured in Admin).

State changes / events:

  • markets[marketId].marketOwner = msg.sender.

  • pendingMarketOwner[marketId] = address(0).

  • Emits MarketOwnerChanged(marketId, msg.sender).

Reverts:

  • E300 — market doesn't exist.

  • E202 — caller is not the pending owner.


terminateMarket

Who calls: the marketOwner of marketId.

What it does: permanently flips markets[marketId].terminated = true. From that block forward, buySwap reverts on this market (see E304). Existing swaps are unaffected — LPs can still withdraw, keepers can still makePayment, liquidators can still liquidateSwap, and buyers can still exitSwapEarly if it was enabled at market creation. This is a one-way switch; there is no unterminate.

Terminate when you want to wind a market down cleanly — new positions can't be opened, but open ones still settle.

Markets can also terminate without the owner calling this function: if the reference oracle is dead and a swap settles on the extrapolated-index fallback after the settlement grace period, the protocol flips terminated = true on the settling market and its paired market, emitting the same MarketAdminChange(marketId, "terminateMarket", true) for each. Both sides price against the same oracle, so the auto-termination is always pair-wide.

Modifiers: marketOwner(marketId).

State changes / events:

  • markets[marketId].terminated = true.

  • Emits MarketAdminChange(marketId, "terminateMarket", true).

Reverts:

  • E300 — market doesn't exist.

  • E202 — not market owner.

  • E304 — market is already terminated.


setMarketLpWhitelist

Who calls: the marketOwner of marketId.

What it does: toggles an address's presence in marketLpWhitelist[marketId]. Only meaningful if the market was created with lpWhitelistEnabled = true — otherwise the call reverts. supplyCollateral checks this mapping inline and reverts with E201 if the target LP isn't listed. withdrawCollateral is not gated on the whitelist — an LP who is removed can still withdraw their existing shares.

The market creator is auto-whitelisted at createMarket time, so there's always at least one LP who can seed the pool.

Modifiers: marketOwner(marketId).

State changes / events:

  • marketLpWhitelist[marketId][lp] = status.

  • Emits MarketLpWhitelistUpdated(marketId, lp, status).

Reverts:

  • E300 — market doesn't exist.

  • E202 — not market owner.

  • E205lpWhitelistEnabled == false on this market (you can't toggle entries on a market that doesn't use a whitelist).

See also: supplyCollateral.


getAllMarketIds

Who calls: anyone — frontends, indexers, scripts.

What it does: returns every market ID that has ever been created, in creation order. BUY_FIXED and BUY_FLOATING sides appear consecutively for each createMarket call. Use the returned IDs as keys into the public markets(bytes32) getter to read full configs.

There is no pagination — if you expect thousands of markets, read this via staticCall off-chain rather than from on-chain code that would run out of gas.

See also: markets mapping getter.

Last updated