Skip to content

PegKeeper

crvUSD-style Curve StableSwap-NG peg defender. Mints/deposits TARE when pool price above $1; withdraws/burns when below.

Source: TARE-Stablecoin/src/peg_keeper.vy (Vyper 0.4.3)

Curve pool (Sepolia): 0xcBa8532fb8c16B7c193977e1A76E49fd06142d54address book

Concept guide: PegKeeper protocol page

Implementation overview

  • Above peg — mint TARE (minter role), single-sided add_liquidity → increase pool supply, push price down.
  • Below pegremove_liquidity_one_coin + burn TARE (up to debt previously provided) → reduce supply, lift price.
  • Partial correction — each update() moves 1/5 of imbalance (CORRECTION_DIVISOR = 5).
  • Rate limitACTION_DELAY 15 min between state-changing ops.
  • Minter bounddebt_ceiling + engine grants minter via set_peg_keeper.
flowchart LR
    PK[PegKeeper] --> Pool[Curve NG pool]
    PK -->|mint/burn| TARE[TareToken]
    Engine[TareEngine] -->|set_peg_keeper| PK

Immutables

Name Role
POOL Curve StableSwap-NG pool
TARE / PEGGED TARE token
I / I_OTHER TARE vs paired stable coin indices
PAIRED_DECIMALS Paired stable decimals for balance normalization

Events

Event When
Provided TARE minted into pool
Withdrawn TARE removed and burned
DebtCeilingUpdated Admin changes ceiling
SlippageSet Slippage tolerance updated
AdminSet Admin transferred

Admin

set_debt_ceiling

PegKeeper.set_debt_ceiling(new_ceiling)

Sets max TARE the keeper may have outstanding in the Curve pool (debt). Caps mint-and-provide peg defense.

Param Type Description
new_ceiling uint256 Max outstanding TARE debt (18 decimals)

Returns / state: Writes debt_ceiling.

Access: admin only.

Events: DebtCeilingUpdated(ceiling=new_ceiling).

Source (TARE-Stablecoin/src/peg_keeper.vy:160-167):

@external
def set_debt_ceiling(new_ceiling: uint256):
    assert msg.sender == self.admin, "PegKeeper: not admin"
    self.debt_ceiling = new_ceiling
    log DebtCeilingUpdated(ceiling=new_ceiling)

Example:

peg_keeper.set_debt_ceiling(1_000_000 * 10**18)

Cross-links: update · TareEngine set_peg_keeper

set_slippage_bps

PegKeeper.set_slippage_bps(new_bps)

Sets min-out slippage tolerance for pool provide/withdraw ops. Hard-capped at MAX_SLIPPAGE_BPS (1000 = 10%).

Param Type Description
new_bps uint256 Slippage tolerance in basis points

Returns / state: Writes slippage_bps. Drives non-zero min_mint_amount / min_received on Curve calls.

Access: admin only. new_bps <= MAX_SLIPPAGE_BPS.

Events: SlippageSet(slippage_bps=new_bps).

Source (TARE-Stablecoin/src/peg_keeper.vy:170-182):

@external
def set_slippage_bps(new_bps: uint256):
    assert msg.sender == self.admin, "PegKeeper: not admin"
    assert new_bps <= MAX_SLIPPAGE_BPS, "PegKeeper: slippage too high"
    self.slippage_bps = new_bps
    log SlippageSet(slippage_bps=new_bps)

Example:

peg_keeper.set_slippage_bps(50)  # 0.5% min-out floor

Cross-links: update · set_debt_ceiling

set_admin

PegKeeper.set_admin(new_admin)

Transfers keeper admin rights (debt ceiling, slippage config). Typically engine owner.

Param Type Description
new_admin address New admin address (non-zero)

Returns / state: Writes admin.

Access: current admin only.

Events: AdminSet(admin=new_admin).

Source (TARE-Stablecoin/src/peg_keeper.vy:185-193):

@external
def set_admin(new_admin: address):
    assert msg.sender == self.admin, "PegKeeper: not admin"
    assert new_admin != empty(address), "PegKeeper: zero admin"
    self.admin = new_admin
    log AdminSet(admin=new_admin)

Cross-links: set_debt_ceiling · set_slippage_bps


Keeper

update

PegKeeper.update() -> uint256

Permissionless peg nudge. Corrects 1/5 of pool imbalance: mint+provide when TARE scarce (above peg), withdraw+burn when abundant (below peg). EMA price gate prevents flash-manipulation mints.

Param Type Description
No inputs

Returns: uint256 — TARE provided (mint direction) or withdrawn (burn direction); 0 if no action.

State: May update debt, last_change. Respects ACTION_DELAY (15 min), debt_ceiling, slippage_bps.

Access: anyone. Reverts if called too soon after last action.

Events: Provided or Withdrawn when change > 0.

Source (TARE-Stablecoin/src/peg_keeper.vy:200-243):

@external
def update() -> uint256:
    assert block.timestamp >= self.last_change + ACTION_DELAY, "PegKeeper: too soon"
    p_tare: uint256 = self._tare_price()  # EMA guard
    if balance_pegged > balance_other and p_tare <= PRECISION:
        change = self._withdraw((balance_pegged - balance_other) // CORRECTION_DIVISOR)
    elif balance_other > balance_pegged and p_tare >= PRECISION:
        change = self._provide((balance_other - balance_pegged) // CORRECTION_DIVISOR)
    if change > 0:
        self.last_change = block.timestamp
    return change

Example:

# Keeper bot — max once per 15 min
amount = peg_keeper.update()

Cross-links: tare_price · peg_deviation_bps · estimate_update · TareToken API


Views

tare_price

PegKeeper.tare_price() -> uint256

TARE's Curve pool EMA price in paired-stable terms (18-decimal; 1e18 = $1 target).

Param Type Description
No inputs

Returns: uint256 — USD value of 1 TARE from POOL.price_oracle(0), oriented via coin index I.

State: None (view). Uses manipulation-resistant EMA, not spot balances.

Access: anyone (@view).

Events: None.

Source (TARE-Stablecoin/src/peg_keeper.vy:322-338):

@external
@view
def tare_price() -> uint256:
    raw: uint256 = staticcall POOL.price_oracle(0)
    if I == 1:
        return raw
    return PRECISION * PRECISION // raw

Example:

price = peg_keeper.tare_price()
above_peg = price > 10**18

Cross-links: peg_deviation_bps · update · estimate_update

peg_deviation_bps

PegKeeper.peg_deviation_bps() -> uint256

Absolute TARE peg deviation from $1 in basis points (pool EMA).

Param Type Description
No inputs

Returns: uint256|price - 1e18| / 1e18 × 10_000 bps.

State: None (view). Uses _tare_price() internally.

Access: anyone (@view).

Events: None.

Source (TARE-Stablecoin/src/peg_keeper.vy:341-348):

@external
@view
def peg_deviation_bps() -> uint256:
    price: uint256 = self._tare_price()
    if price >= PRECISION:
        return (price - PRECISION) * 10_000 // PRECISION
    return (PRECISION - price) * 10_000 // PRECISION

Example:

bps = peg_keeper.peg_deviation_bps()  # 50 => 0.5% off peg

Cross-links: tare_price · update

estimate_update

PegKeeper.estimate_update() -> (bool, uint256)

Preview next update() direction and size from pool balances. Does not apply EMA gate or ACTION_DELAY — dry-run sizing only.

Param Type Description
No inputs

Returns: (is_provide, amount)True = mint/provide path; False = withdraw/burn. amount = TARE that would move (withdraw capped at debt).

State: None (view).

Access: anyone (@view).

Events: None.

Source (TARE-Stablecoin/src/peg_keeper.vy:360-378):

@external
@view
def estimate_update() -> (bool, uint256):
    if balance_pegged > balance_other:
        return (False, min((balance_pegged - balance_other) // CORRECTION_DIVISOR, self.debt))
    if balance_other > balance_pegged:
        return (True, (balance_other - balance_pegged) // CORRECTION_DIVISOR)
    return (True, 0)

Example:

is_provide, amount = peg_keeper.estimate_update()

Cross-links: update · peg_deviation_bps