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:
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 (queueMarketCreationFee → activateMarketCreationFee; 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]andmarkets[floatingMarketId]populated, both markedexists = true,marketOwner = msg.sender.Both IDs appended to
allMarketIds.Global
rateIndexinitialized 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) andMarketConfigured(×2), plusMarketCreationFeeCollectedif a fee was charged (orMarketCreationFeeForegoneinstead, if the multisig couldn't receive the fee — in which case the fee is refunded to the caller rather than collected).
Reverts:
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.E205—lpWhitelistEnabled == falseon 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