Skip to content

CoilMakerStrategy

ERC-4626-shaped MSV strategy (asset = USDC). Rests oracle-floored sell-USDC / buy-TARE Coil intents; accumulates TARE; harvest swaps back to USDC via Curve EMA pool.

Source: khomdev-keep/src/strategies/CoilMakerStrategy.vy (Vyper 0.4.x)

Concept: FLYWHEEL 2.0 priority allocation — MultiStrategyVault coil_maker_strategy

Pairs with: Coil · TARE/USDC Curve NG pool · CoilFeeRouter

Implementation overview

  • Inventory — one-sided maker: USDC → TARE via Coil fills; valuation = USDC + TARE×EMA (not spot)
  • Order auth — keeper approve_intent stores EIP-712 digest; isValidSignature (1271) for Coil fills
  • Exit — vault-only withdraw/redeem; harvest() permissionless TARE→USDC (capped by max_swap_in)
  • Maker floor — default maker_margin_bps = carry-positive spread above EMA; deposits blocked if 0
  • Depeg exit — optional depeg_exit_bps + Chainlink TARE/USD feed confirms before spot-realised exit
  • Shares — internal total_shares / shares_held (not ERC-20); only vault may deposit/withdraw
flowchart LR
    MSV[MultiStrategyVault] --> CMS[CoilMakerStrategy]
    CMS -->|resting intents| Coil[Coil DEX]
    Coil -->|TARE| CMS
    CMS -->|harvest swap| Pool[Curve TARE/USDC]

Immutables

Name Role
asset USDC
vault Parent MSV (sole depositor/withdrawer)
coil Coil batch-auction DEX
pool TARE/USDC Curve NG (EMA price_oracle)
TARE Buy-side inventory token
ORACLE_NUMER Scale bridge for EMA valuation

Roles

Role Powers
DEFAULT_ADMIN_ROLE APR, slippage, margins, feeds, pause deposits, sweep stray tokens
KEEPER_ROLE approve_intent, cancel_intent, set_coil_allowance

Events

Event When
Deposit / Withdraw Vault capital in/out
IntentApproved / IntentCancelled Resting Coil orders
Harvested TARE → USDC realisation
AprUpdated, SlippageUpdated, … Admin config

ERC-4626 views

totalAssets

CoilMakerStrategy.totalAssets() -> uint256

Strategy NAV in USDC terms for MSV debt accounting.

Returns: uint256USDC.balanceOf + TARE.balanceOf * price_oracle / ORACLE_NUMER. Pending-order USDC still in balance (counted once).

Access: view, any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:322-323, _total_assets 778-785):

@external
@view
def totalAssets() -> uint256:
    return self._total_assets()

Example:

nav = coil_maker.totalAssets()

Cross-links: convertToShares · harvest · MultiStrategyVault report

balanceOf

CoilMakerStrategy.balanceOf(account) -> uint256

Internal strategy shares held by account (MSV only in practice).

Param Type Description
account address Share holder

Returns: uint256shares_held[account].

Access: view, any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:327-328):

@external
@view
def balanceOf(account: address) -> uint256:
    return self.shares_held[account]

Cross-links: totalSupply · convertToAssets

totalSupply

CoilMakerStrategy.totalSupply() -> uint256

Total strategy shares outstanding (vault's position).

Returns: uint256total_shares.

Access: view, any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:332-333):

@external
@view
def totalSupply() -> uint256:
    return self.total_shares

Cross-links: totalAssets · balanceOf

convertToAssets

CoilMakerStrategy.convertToAssets(shares) -> uint256

Shares → USDC quote (floor).

Param Type Description
shares uint256 Strategy shares

Returns: uint256 — USDC value at current NAV.

Access: view, any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:337-338):

@external
@view
def convertToAssets(shares: uint256) -> uint256:
    return self._convert_to_assets(shares, False)

Cross-links: convertToShares · previewRedeem

convertToShares

CoilMakerStrategy.convertToShares(assets) -> uint256

USDC → shares quote (floor).

Param Type Description
assets uint256 USDC amount

Returns: uint256 — shares at current NAV.

Access: view, any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:342-343):

@external
@view
def convertToShares(assets: uint256) -> uint256:
    return self._convert_to_shares(assets, False)

Cross-links: convertToAssets · previewDeposit

maxDeposit

CoilMakerStrategy.maxDeposit(receiver) -> uint256

Max USDC depositable via ERC-4626 view.

Param Type Description
receiver address Must be vault

Returns: uint2560 if deposits_paused or receiver != vault; else max_uint256. Actual deposit also requires maker_margin_bps > 0.

Access: view, any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:347-350):

@external
@view
def maxDeposit(receiver: address) -> uint256:
    if self.deposits_paused or receiver != vault:
        return 0
    return max_value(uint256)

Cross-links: deposit · maxMint

maxWithdraw

CoilMakerStrategy.maxWithdraw(owner) -> uint256

Max USDC withdrawable for owner.

Param Type Description
owner address Must be vault

Returns: uint256convertToAssets(shares_held[vault]); 0 if owner != vault.

Access: view, any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:354-357):

@external
@view
def maxWithdraw(owner: address) -> uint256:
    if owner != vault:
        return 0
    return self._convert_to_assets(self.shares_held[owner], False)

Cross-links: withdraw · maxRedeem

previewDeposit

CoilMakerStrategy.previewDeposit(assets) -> uint256

Simulate deposit — shares minted (floor).

Param Type Description
assets uint256 USDC in

Returns: uint256 — expected shares.

Access: view, any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:361-362):

@external
@view
def previewDeposit(assets: uint256) -> uint256:
    return self._convert_to_shares(assets, False)

Cross-links: deposit · convertToShares

previewWithdraw

CoilMakerStrategy.previewWithdraw(assets) -> uint256

Simulate withdraw — shares burned (ceil).

Param Type Description
assets uint256 USDC out

Returns: uint256 — shares required.

Access: view, any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:366-367):

@external
@view
def previewWithdraw(assets: uint256) -> uint256:
    return self._convert_to_shares(assets, True)

Cross-links: withdraw · previewRedeem

previewRedeem

CoilMakerStrategy.previewRedeem(shares) -> uint256

Simulate redeem — USDC out (floor).

Param Type Description
shares uint256 Shares burned

Returns: uint256 — expected USDC (may differ on exit if TARE swap needed).

Access: view, any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:371-372):

@external
@view
def previewRedeem(shares: uint256) -> uint256:
    return self._convert_to_assets(shares, False)

Cross-links: redeem · convertToAssets

maxMint

CoilMakerStrategy.maxMint(receiver) -> uint256

Max shares mintable (vault-only).

Param Type Description
receiver address Must be vault

Returns: uint256 — same gates as maxDeposit.

Access: view, any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:376-379):

@external
@view
def maxMint(receiver: address) -> uint256:
    if self.deposits_paused or receiver != vault:
        return 0
    return max_value(uint256)

Cross-links: mint · maxDeposit

maxRedeem

CoilMakerStrategy.maxRedeem(owner) -> uint256

Max shares redeemable by owner.

Param Type Description
owner address Must be vault

Returns: uint256 — full shares_held[owner]; 0 if not vault.

Access: view, any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:383-386):

@external
@view
def maxRedeem(owner: address) -> uint256:
    if owner != vault:
        return 0
    return self.shares_held[owner]

Cross-links: redeem · maxWithdraw

Order-flow queries

getAvailableOrderVolume

CoilMakerStrategy.getAvailableOrderVolume() -> uint256

Idle USDC not committed to resting Coil intents — deploy capacity for new orders.

Returns: uint256USDC.balanceOf - pending_order_volume (floored at 0).

Access: view, any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:395-401, _get_available_order_volume 953-960):

@external
@view
def getAvailableOrderVolume() -> uint256:
    return self._get_available_order_volume()

Example:

deployable = coil_maker.getAvailableOrderVolume()

Cross-links: deployAvailable · Rebalancer compute_coilmaker_allocation

deployAvailable

CoilMakerStrategy.deployAvailable(amount) -> uint256

How much of amount the strategy can deploy to Coil right now.

Param Type Description
amount uint256 Requested USDC deployment

Returns: uint256min(amount, getAvailableOrderVolume()).

Access: view, any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:406-413):

@external
@view
def deployAvailable(amount: uint256) -> uint256:
    return min(amount, self._get_available_order_volume())

Cross-links: getAvailableOrderVolume · MultiStrategyVault allocateCapital

IStrategy hooks

getApr

CoilMakerStrategy.getApr() -> uint256

Cached APR signal for MSV smart routing (keeper/admin set).

Returns: uint256apr_bps (not on-chain realised yield).

Access: view, any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:421-422):

@external
@view
def getApr() -> uint256:
    return self.apr_bps

Cross-links: set_apr_bps · Rebalancer compute_smart_targets

harvest

CoilMakerStrategy.harvest() -> uint256

Permissionless: swap held TARE → USDC via Curve EMA pool; realise maker yield in vault asset.

Returns: uint256 — USDC received (0 if no TARE). Updates pending_order_volume / filled_order_volume tracking.

Returns / state: Enforces max_swap_in cap on harvest path (not on vault exits). min_dy from EMA oracle.

Access: Any caller. nonreentrant.

Events: FilledVolumeUpdated, Harvested(tare_in, usdc_out).

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:426-454):

@external
@nonreentrant
def harvest() -> uint256:
    tare_bal = IERC20(TARE).balanceOf(self)
    if tare_bal == 0: return 0
    # reduce pending_order_volume by oracle-estimated fill
    out = self._swap_tare_for_usdc(tare_bal, is_exit=False)
    return out

Example:

usdc = coil_maker.harvest()

Cross-links: set_max_swap_in · MultiStrategyVault report · Accountant assess

sync

CoilMakerStrategy.sync()

No-op. TARE valued live from pool EMA on every totalAssets read.

Access: Any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:457-459):

@external
def sync():
    pass

Cross-links: totalAssets · MultiStrategyVault report

ERC-4626 writes

deposit

CoilMakerStrategy.deposit(assets, receiver) -> uint256

Vault pulls USDC into strategy; mints internal shares to receiver (must be vault).

Param Type Description
assets uint256 USDC amount
receiver address Share recipient (vault only)

Returns: uint256 — shares minted.

Access: vault only. Requires maker_margin_bps > 0, not deposits_paused.

Events: Deposit.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:467-485):

@external
@nonreentrant
def deposit(assets, receiver) -> uint256:
    self._only_vault()
    assert maker_margin_bps > 0 and not deposits_paused
    IERC20(asset).transferFrom(vault, self, assets)
    total_shares += shares; shares_held[receiver] += shares

Cross-links: previewDeposit · withdraw

mint

CoilMakerStrategy.mint(shares, receiver) -> uint256

Mint exact shares; vault pays ceil-rounded USDC.

Param Type Description
shares uint256 Target shares
receiver address Must be vault

Returns: uint256 — USDC pulled from vault.

Access: vault only. Same gates as deposit.

Events: Deposit.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:489-505):

@external
@nonreentrant
def mint(shares, receiver) -> uint256:
    assets = self._convert_to_assets(shares, True)
    # same deposit path as deposit()

Cross-links: deposit · previewMint

withdraw

CoilMakerStrategy.withdraw(assets, receiver, owner) -> uint256

Burn shares; pay USDC to receiver. Swaps TARE→USDC via _pay_usdc if idle short.

Param Type Description
assets uint256 Target USDC
receiver address Payout address
owner address Must be vault

Returns: uint256 — shares burned.

Access: vault only. Exit path bypasses max_swap_in; may use M-2 depeg exit.

Events: Withdraw (logs actual USDC paid).

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:509-527):

@external
@nonreentrant
def withdraw(assets, receiver, owner) -> uint256:
    # burn shares; _pay_usdc(assets_eff, receiver, full)
    return shares

Cross-links: redeem · harvest

redeem

CoilMakerStrategy.redeem(shares, receiver, owner) -> uint256

Burn exact shares; pay USDC to receiver.

Param Type Description
shares uint256 Shares to burn
receiver address USDC recipient
owner address Must be vault

Returns: uint256 — USDC paid (got from _pay_usdc).

Access: vault only.

Events: Withdraw.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:531-543):

@external
@nonreentrant
def redeem(shares, receiver, owner) -> uint256:
    assets_eff = self._convert_to_assets(shares, False)
    return self._pay_usdc(assets_eff, receiver, full)

Cross-links: withdraw · previewRedeem

Keeper / Coil

approve_intent

CoilMakerStrategy.approve_intent(intent)

Register a resting sell-USDC / buy-TARE Coil intent for EIP-1271 fills.

Param Type Description
intent ICoil.Intent Resting order (owner must be this strategy)

Returns / state: Stores intent_digest → deadline in approved_intents; increments pending_order_volume (capped at 90% NAV).

Access: KEEPER_ROLE only. Rejects min_buy_amount below EMA oracle floor (maker_margin_bps spread or legacy slippage floor).

Events: IntentApproved, PendingVolumeUpdated.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:550-602):

@external
def approve_intent(intent: ICoil.Intent):
    access_control._check_role(KEEPER_ROLE, msg.sender)
    assert intent.min_buy_amount >= min_buy_floor  # EMA*(1+margin) or EMA*(1-slippage)
    digest = ICoil(coil).intent_digest(intent)
    approved_intents[digest] = intent.deadline

Cross-links: isValidSignature · cancel_intent · Coil

cancel_intent

CoilMakerStrategy.cancel_intent(intent)

Revoke approved intent; release pending_order_volume; cancel on Coil if still fillable.

Param Type Description
intent ICoil.Intent Order to revoke

Access: KEEPER_ROLE only.

Events: IntentCancelled, PendingVolumeUpdated.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:605-624):

@external
def cancel_intent(intent: ICoil.Intent):
    approved_intents[digest] = 0
    pending_order_volume -= intent.sell_amount  # bounded
    if ICoil(coil).is_fillable(intent):
        ICoil(coil).cancel_intent(intent)

Cross-links: approve_intent · set_coil_allowance

set_coil_allowance

CoilMakerStrategy.set_coil_allowance(amount)

Set standing USDC allowance Coil may pull on intent fills.

Param Type Description
amount uint256 New USDC allowance for coil

Returns / state: Reset-approve pattern: approve(coil, 0) then approve(coil, amount).

Access: KEEPER_ROLE only. Fills still gated per-order by isValidSignature + oracle floor.

Events: CoilAllowanceSet(amount).

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:627-637):

@external
def set_coil_allowance(amount: uint256):
    access_control._check_role(KEEPER_ROLE, msg.sender)
    IERC20(asset).approve(coil, 0)
    IERC20(asset).approve(coil, amount)

Example:

coil_maker.set_coil_allowance(1_000_000 * 10**6)  # KEEPER_ROLE

Cross-links: approve_intent · Coil

isValidSignature

CoilMakerStrategy.isValidSignature(_hash, _signature) -> bytes4

EIP-1271 hook Coil calls before filling a resting intent.

Param Type Description
_hash bytes32 Intent digest (from Coil)
_signature Bytes[1024] Ignored — auth is on-chain registry

Returns: bytes40x1626ba7e (magic) if digest approved and unexpired; else 0xffffffff.

Access: view, any caller.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:645-654):

@external
@view
def isValidSignature(_hash: bytes32, _signature: Bytes[1024]) -> bytes4:
    deadline = approved_intents[_hash]
    if deadline != 0 and block.timestamp <= deadline:
        return ERC1271_MAGIC_VALUE
    return ERC1271_INVALID

Cross-links: approve_intent · cancel_intent · Coil

Admin

set_apr_bps

CoilMakerStrategy.set_apr_bps(new_bps)

Update cached APR for MSV smart-routing (getApr).

Param Type Description
new_bps uint256 APR signal bps (≤ MAX_APR_BPS = 5000)

Returns / state: Sets apr_bps.

Access: DEFAULT_ADMIN_ROLE only.

Events: AprUpdated(new_bps).

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:661-665):

@external
def set_apr_bps(new_bps: uint256):
    access_control._check_role(DEFAULT_ADMIN_ROLE, msg.sender)
    assert new_bps <= MAX_APR_BPS
    self.apr_bps = new_bps

Cross-links: getApr · MultiStrategyVault smart routing

set_slippage_bps

CoilMakerStrategy.set_slippage_bps(new_bps)

Swap tolerance for EMA-floored min_dy on TARE→USDC exits and harvest.

Param Type Description
new_bps uint256 Slippage bps (≤ MAX_SLIPPAGE_BPS = 1000)

Returns / state: Sets slippage_bps. Used in legacy intent floor when maker_margin_bps == 0.

Access: DEFAULT_ADMIN_ROLE only.

Events: SlippageUpdated(new_bps).

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:668-672):

@external
def set_slippage_bps(new_bps: uint256):
    access_control._check_role(DEFAULT_ADMIN_ROLE, msg.sender)
    assert new_bps <= MAX_SLIPPAGE_BPS
    self.slippage_bps = new_bps

Cross-links: set_maker_margin_bps · harvest · approve_intent

set_max_swap_in

CoilMakerStrategy.set_max_swap_in(new_max)

Absolute TARE cap per permissionless harvest swap.

Param Type Description
new_max uint256 Max TARE notional per harvest; 0 locks harvest

Returns / state: Sets max_swap_in. Vault exit path ignores this cap.

Access: DEFAULT_ADMIN_ROLE only. Production must set non-zero for harvest to work.

Events: MaxSwapInUpdated(new_max).

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:675-688):

@external
def set_max_swap_in(new_max: uint256):
    access_control._check_role(DEFAULT_ADMIN_ROLE, msg.sender)
    self.max_swap_in = new_max

Cross-links: harvest · withdraw · redeem

set_tare_usd_feed

CoilMakerStrategy.set_tare_usd_feed(feed)

Set independent Chainlink TARE/USD feed for M-2 depeg exit confirmation.

Param Type Description
feed address Chainlink AggregatorV3; 0x0 unsets

Returns / state: Sets tare_usd_feed + caches tare_usd_feed_decimals. Unset clears decimals. Required before enabling set_depeg_exit_bps.

Access: DEFAULT_ADMIN_ROLE only.

Events: TareUsdFeedSet(feed, decimals).

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:692-712):

@external
def set_tare_usd_feed(feed: address):
    access_control._check_role(DEFAULT_ADMIN_ROLE, msg.sender)
    if feed == empty(address):
        tare_usd_feed = empty(address); tare_usd_feed_decimals = 0
    else:
        dec = IAggregatorV3(feed).decimals()
        tare_usd_feed = feed; tare_usd_feed_decimals = dec

Cross-links: set_depeg_exit_bps · withdraw · oracle_lib

set_maker_margin_bps

CoilMakerStrategy.set_maker_margin_bps(new_bps)

Maker spread above EMA mid for resting intent floor (carry-positive mode).

Param Type Description
new_bps uint256 Spread bps (≤ MAX_MAKER_MARGIN_BPS = 1000); 0 = legacy slippage floor

Returns / state: Sets maker_margin_bps. If new_bps > 0, must exceed slippage_bps. deposit blocked while 0.

Access: DEFAULT_ADMIN_ROLE only.

Events: MakerMarginUpdated(new_bps).

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:715-732):

@external
def set_maker_margin_bps(new_bps: uint256):
    access_control._check_role(DEFAULT_ADMIN_ROLE, msg.sender)
    assert new_bps <= MAX_MAKER_MARGIN_BPS
    assert new_bps == 0 or new_bps > slippage_bps
    self.maker_margin_bps = new_bps

Cross-links: approve_intent · set_slippage_bps · deposit

set_depeg_exit_bps

CoilMakerStrategy.set_depeg_exit_bps(new_bps)

Depeg threshold for forced vault exits: realise TARE at spot when EMA lags.

Param Type Description
new_bps uint256 Bps below EMA to trigger M-2 exit (≤ MAX_DEPEG_EXIT_BPS = 5000); 0 = disabled

Returns / state: Sets depeg_exit_bps. Enabling (> 0) requires set_tare_usd_feed first. Spot + Chainlink must both confirm depeg.

Access: DEFAULT_ADMIN_ROLE only. Applies to vault exit path only — not harvest or intent floor.

Events: DepegExitBpsUpdated(new_bps).

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:735-755):

@external
def set_depeg_exit_bps(new_bps: uint256):
    access_control._check_role(DEFAULT_ADMIN_ROLE, msg.sender)
    assert new_bps <= MAX_DEPEG_EXIT_BPS
    assert new_bps == 0 or tare_usd_feed != empty(address)
    self.depeg_exit_bps = new_bps

Cross-links: set_tare_usd_feed · withdraw · redeem

set_deposits_paused

CoilMakerStrategy.set_deposits_paused(state)

Pause/resume vault deposits into strategy. Withdrawals always allowed.

Param Type Description
state bool True = pause new deposits

Returns / state: Sets deposits_paused.

Access: DEFAULT_ADMIN_ROLE only.

Events: DepositsPaused(state).

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:758-761):

@external
def set_deposits_paused(state: bool):
    access_control._check_role(DEFAULT_ADMIN_ROLE, msg.sender)
    self.deposits_paused = state

Cross-links: deposit · maxDeposit

sweep

CoilMakerStrategy.sweep(token, to)

Recover stray ERC-20 (not USDC or TARE).

Param Type Description
token address Token to sweep
to address Recipient

Access: DEFAULT_ADMIN_ROLE only. Cannot sweep asset or TARE.

Source (khomdev-keep/src/strategies/CoilMakerStrategy.vy:764-770):

@external
def sweep(token: address, to: address):
    assert token != asset and token != TARE
    bal = IERC20(token).balanceOf(self)
    IERC20(token).transfer(to, bal)