Skip to content

The engine, end to end

One portfolio moves between idle assets and a liquidity band. Shares continue to represent the same portfolio through every operation.

Testnet, test tokens, unaudited. This describes the current source; deployment configuration is specific to each engine.

Configure once, then activate

The constructor pins an already initialized pool, its manager, quote token, non-quote mark and lifecycle module. It checks token contracts and decimals, mark asset/quote identity, aligned range ticks, and valid caps and recenter bounds. Native currency pools are not accepted; the assets must be ERC-20 tokens with at most 18 decimals.

The owner calls configureAdapters(address,address) once. Each distinct adapter must expose the correct asset() and be bound to this engine through vault(). The owner then calls activate(), which requires a fresh mark, pool price within the mark-deviation limit, and readable NAV from both adapters. Activation is one-way; pause is a separate state.

01
Constructed
Pool and immutable configuration validated. No deposits yet.
02
Adapters bound
Owner configures both dedicated adapters once.
03
Activated
Owner activates after price and adapter checks pass.
After activation, the pause state controls new risk ↓
Active + unpaused

Deposits, allocation, deployment and recenter are enabled subject to their guards.

Active + paused

Those four operations revert. Withdraw, harvest and recall remain callable, subject to their other checks.

Unpaused → paused: owner or guardian · Paused → unpaused: owner, or anyone after the enabled timeout
Pause does not deactivate the engine. It also does not promise that an exit will succeed.

Deposit one or both tokens

deposit(uint256 amount0, uint256 amount1, uint256 minSharesOut) mints to the caller. At least one amount and the minimum share floor must be nonzero. Token amounts use the decimals and order reported by the engine’s token0() and token1().

01
Collect & check
Collect pending LP fees; read a fresh mark and check spot deviation.
02
Receive & allocate
Measure exact token receipt; supply each adapter up to capacity.
03
Value & mint
Measure the NAV increase; enforce minimum shares and the principal cap.

The engine measures balances, so fee-on-transfer inputs are rejected. New funds enter the bound adapters up to maxSuppliable(); capacity overflow stays in the engine. An adapter deposit failure reverts the deposit rather than silently skipping that adapter.

After token and adapter calls, the engine reads spot again and repeats the deviation guard. It values the before and after portfolios at that same post-callback spot and the same mark. The measured NAV increase is your contribution. This prevents an intervening pool-price move from being counted as your contribution.

If supply is zero, your contribution must reach minInitialContribution. Initial shares equal the entire recognized post-deposit NAV normalized to 18 decimals, including recognized donations already present. Later deposits mint the contribution’s proportional share of existing NAV, rounded down. The share math explains both cases.

Allocate and recall idle assets

allocateIdle(uint256 amount0, uint256 amount1) is owner-only and blocked while paused. It moves existing engine balances into adapters, checks capacity, and requires the engine’s measured debit to match the amount. It does not add pool liquidity.

recallIdle(uint256 amount0, uint256 amount1) is callable by anyone, including while paused. The adapters send assets to the engine, never the caller. Receipts are measured and can be smaller than requested. A reverting adapter makes this standalone recall revert; withdrawals use separate isolated recall calls to handle failures.

Recorded current market adapters are custody-only: idle tokens earn no yield. The adapter interface also supports external yield sources, whose liquidity and valuation introduce additional dependencies.

Deploy into the resting band

The owner calls deployLiquidity(amount0, amount1, zeroForOne, amountIn, minAmountOut, sqrtPriceLimitX96, minLiquidity). The first two amounts request recalls from the adapters. For hookless pools, a nonzero amountIn optionally swaps idle tokens before deployment. It requires a nonzero minimum output and a price limit on the correct side of spot. Actual input may be less than requested. To skip the swap, input, minimum output and price limit must all be zero.

Deployment collects fees, checks a fresh mark and spot deviation, and checks NAV against the principal cap. It scales the idle budgets to the remaining position-cap room and computes liquidity for the current ticks. The result must meet a nonzero minLiquidity. After the pool add, it repeats spot, principal-cap and position-cap checks. If any part fails, the whole transaction—including recalls and an optional swap—reverts.

Inside the band, adding liquidity needs the appropriate ratio of both tokens; outside it, range math can use one side. Unused balances remain idle. The engine owns the position directly in Uniswap’s PoolManager, rather than through a position NFT.

Harvest fees

harvest() is permissionless and callable while paused. It collects accrued position fees into the engine’s token balances and emits FeesCollected. With no position it returns zero amounts. It does not pay the caller, mint shares or automatically redeploy the collected tokens. Deposit and withdraw also collect fees before changing shares.

Recenter only when due

recenter(uint128 minLiquidity) is also permissionless, but blocked while paused. There must be an existing position and a nonzero liquidity minimum. The cooldown must have elapsed since lastRecenterAt, and spot must be at or beyond either edge threshold: lower tick plus recenterEdgeTicks, or upper tick minus it.

The new band keeps the old width. Its center follows spot, rounded to tick spacing. The absolute change in the lower tick must be nonzero and no greater than maxRecenterTickMove. A large price jump can therefore prevent recentering rather than force an unrestricted move.

01
Check it is due
Cooldown, edge, mark deviation and maximum tick move.
02
Remove old range
Collect fees first; re-read prices before moving liquidity.
03
Add new range
Use only the removal proceeds; require minimum liquidity.
Removal and re-add are atomic. A failed add restores the old position. The module checks that pool spot does not change across the move; unused proceeds remain idle.

Recenter does not mint or burn shares, swap assets, or pull extra funds from idle adapters. It repeats the mark-deviation guard after the move and updates lastRecenterAt.

Withdraw pro-rata, in kind

withdraw(uint256 shares, address recipient, uint256 minAmount0, uint256 minAmount1) redeems the caller’s shares. The recipient must be nonzero. You receive underlying assets in their current portfolio proportions, which may differ from what you originally supplied. There is no contract lockup or engine pause check.

01
Size the claim
Collect fees; take a pro-rata share of idle tokens, adapters and LP liquidity.
02
Attempt delivery
Isolate LP removal, adapter recalls and each recipient transfer.
03
Check & burn
Enforce both minimum outputs; burn only the applicable shares.
All portions delivered

Burn the requested shares. A fresh mark is not required for complete pro-rata delivery.

Some portion unavailable

A usable exit mark and pool-deviation checks are required. Pay the permitted amount and retain shares, or revert if it cannot be done safely.

When an adapter or LP removal cannot deliver its requested portion, the engine calculates the least deliverable fraction across both token entitlements and reduces both payouts to that fraction. It burns the corresponding fraction of requested shares, rounded up, while requiring some requested shares to remain. This stops a recall failure from letting one holder take only the liquid side of everyone’s portfolio.

If a recipient transfer is blocked, the engine can still deliver the other leg. It then measures the portfolio’s NAV reduction at the same mark and spot, and rounds the corresponding share burn up. Undelivered assets remain in NAV and the holder retains shares; the retained claim is on the remaining portfolio, not a fixed token IOU.

Your minimum outputs are checked against actual delivery. Either minimum failing, both outputs being zero, or an incomplete exit consuming all requested shares causes a full revert. Token-unit and liquidity rounding can leave small residual balances.

See the role and pause matrix for exactly who may call each operation.

Source reference

Behavior above follows these files in the Mintware repository. Deployment records describe recorded state, not a fresh chain read.

  • contracts-v4/src/vaults/MintwareMarketEngineCore.sol
  • contracts-v4/src/vaults/MintwareMarketLifecycleModule.sol
  • contracts-v4/src/vaults/MintwareOwnedPoolMarketEngine.sol
  • contracts-v4/src/lib/MWGuardianPausable.sol
  • contracts-v4/src/lib/MWMarketShareMath.sol