harb/landing/src/views/docs/CodeDocs.vue

853 lines
35 KiB
Vue
Raw Normal View History

<template>
<div>
<h1 id="first">Source Code</h1>
<p>
The Kraiken protocol is built on four core Solidity contracts deployed on Base.
Every contract is immutable (except the upgradeable Optimizer) what you see here is what runs on-chain.
</p>
<p class="warning">Full source and verification will be published at mainnet launch.</p>
<div class="code-addresses" v-if="krkAddress || stakeAddress">
<h2 id="contracts">Contract Addresses</h2>
<div class="code-address-list">
<div v-if="krkAddress" class="code-address-item">
<span class="code-address-label">KRK Token</span>
<a :href="`https://basescan.org/address/${krkAddress}`" target="_blank" rel="noopener noreferrer">{{ krkAddress }}</a>
</div>
<div v-if="stakeAddress" class="code-address-item">
<span class="code-address-label">Stake</span>
<a :href="`https://basescan.org/address/${stakeAddress}`" target="_blank" rel="noopener noreferrer">{{ stakeAddress }}</a>
</div>
</div>
</div>
<h2 id="kraiken">Kraiken.sol</h2>
<p>
ERC20 token with controlled minting/burning exclusively by the LiquidityManager.
The staking pool grows proportionally with every mint 20% of supply is reserved for staking positions.
</p>
<div class="code-block">
<pre><code v-html="highlightSolidity(kraikenSol)"></code></pre>
</div>
<h2 id="liquidity-manager">LiquidityManager.sol</h2>
<p>
Manages the three-position anti-arbitrage strategy on Uniswap V3 (1% fee tier).
Handles FLOOR, ANCHOR, and DISCOVERY positions with dynamic parameter adjustment via the Optimizer.
Uses 5-minute TWAP with 50-tick tolerance to prevent oracle manipulation.
</p>
<div class="code-block">
<pre><code v-html="highlightSolidity(liquidityManagerSol)"></code></pre>
</div>
<h2 id="stake">Stake.sol</h2>
<p>
Harberger tax-based staking with self-assessed valuations. 30-tier discrete tax rates prevent micro-increment snatching.
Stakers set their own tax rate higher rates protect against buyouts but cost more. Tax revenue flows back as UBI.
</p>
<div class="code-block">
<pre><code v-html="highlightSolidity(stakeSol)"></code></pre>
</div>
<h2 id="optimizer">OptimizerV3.sol</h2>
<p>
Binary bear/bull liquidity optimizer using a direct 2D mapping from (staking%, avgTax) to configuration.
Bull requires &gt;91% staked with low tax any decline snaps instantly to bear mode.
UUPS upgradeable proxy pattern allows parameter tuning without redeploying the core contracts.
</p>
<div class="code-block">
<pre><code v-html="highlightSolidity(optimizerSol)"></code></pre>
</div>
<p>
View the full repository on <a href="https://codeberg.org/johba/harb" target="_blank" rel="noopener noreferrer">Codeberg</a>.
</p>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue';
interface CodeDocsEmits {
(event: 'onmounted'): void;
}
const emit = defineEmits<CodeDocsEmits>();
onMounted(() => {
emit('onmounted');
});
const krkAddress = import.meta.env.VITE_KRAIKEN_ADDRESS || '';
const stakeAddress = import.meta.env.VITE_STAKE_ADDRESS || '';
function highlightSolidity(code: string): string {
// Escape HTML first
let html = code
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
// Comments (single-line and multi-line)
html = html.replace(/(\/\/[^\n]*)/g, '<span class="sol-comment">$1</span>');
html = html.replace(/(\/\*[\s\S]*?\*\/)/g, '<span class="sol-comment">$1</span>');
// Strings
html = html.replace(/(&quot;[^&]*?&quot;|"[^"]*?")/g, '<span class="sol-string">$1</span>');
// Numbers
html = html.replace(/\b(\d[\d_]*(?:e\d+)?)\b/g, '<span class="sol-number">$1</span>');
// Keywords
const keywords = [
'pragma', 'solidity', 'import', 'contract', 'interface', 'library', 'abstract',
'function', 'modifier', 'event', 'error', 'struct', 'enum', 'mapping',
'public', 'private', 'internal', 'external', 'view', 'pure', 'payable',
'returns', 'return', 'if', 'else', 'for', 'while', 'do', 'break', 'continue',
'require', 'revert', 'emit', 'new', 'delete', 'try', 'catch',
'memory', 'storage', 'calldata', 'immutable', 'constant', 'override', 'virtual',
'is', 'using', 'constructor', 'receive', 'fallback',
];
const kwPattern = new RegExp(`\\b(${keywords.join('|')})\\b`, 'g');
html = html.replace(kwPattern, '<span class="sol-keyword">$1</span>');
// Types
const types = [
'address', 'bool', 'string', 'bytes', 'bytes32',
'uint256', 'uint128', 'uint64', 'uint32', 'uint24', 'uint8',
'int256', 'int24',
];
const typePattern = new RegExp(`\\b(${types.join('|')})\\b`, 'g');
html = html.replace(typePattern, '<span class="sol-type">$1</span>');
return html;
}
const kraikenSol = `// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;
import { ERC20 } from "@openzeppelin/token/ERC20/ERC20.sol";
import { ERC20Permit } from "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol";
import { Math } from "@openzeppelin/utils/math/Math.sol";
/**
* @title stakeable ERC20 Token
* @notice This contract implements an ERC20 token with mechanisms for minting and burning in which a single account (staking Pool) is proportionally receiving a share. Only the liquidity manager has permission to manage token supply.
* @dev Key features:
* - Controlled minting exclusively by LiquidityManager
* - Tax collection and redistribution mechanism through staking pool
* - 20% supply cap for staking (20,000 positions max)
* - Staking pool receives proportional share of all mints/burns
*/
contract Kraiken is ERC20, ERC20Permit {
using Math for uint256;
uint256 public constant VERSION = 2;
uint256 private constant MIN_STAKE_FRACTION = 3000;
address private immutable deployer;
address private liquidityManager;
address private stakingPool;
uint256 public previousTotalSupply;
error ZeroAddressInSetter();
error AddressAlreadySet();
modifier onlyLiquidityManager() {
require(msg.sender == address(liquidityManager), "only liquidity manager");
_;
}
constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) ERC20Permit(name_) {
deployer = msg.sender;
}
function setLiquidityManager(address liquidityManager_) external {
require(msg.sender == deployer, "only deployer");
if (address(0) == liquidityManager_) revert ZeroAddressInSetter();
if (liquidityManager != address(0)) revert AddressAlreadySet();
liquidityManager = liquidityManager_;
}
function setStakingPool(address stakingPool_) external {
require(msg.sender == deployer, "only deployer");
if (address(0) == stakingPool_) revert ZeroAddressInSetter();
if (stakingPool != address(0)) revert AddressAlreadySet();
stakingPool = stakingPool_;
}
function peripheryContracts() external view returns (address, address) {
return (liquidityManager, stakingPool);
}
function minStake() external view returns (uint256) {
return previousTotalSupply / MIN_STAKE_FRACTION;
}
function mint(uint256 _amount) external onlyLiquidityManager {
if (_amount > 0) {
uint256 stakingPoolBalance = balanceOf(stakingPool);
if (stakingPoolBalance > 0) {
uint256 newStake = stakingPoolBalance * _amount / (totalSupply() - stakingPoolBalance);
_mint(stakingPool, newStake);
}
_mint(address(liquidityManager), _amount);
}
if (previousTotalSupply == 0) {
previousTotalSupply = totalSupply();
}
}
function burn(uint256 _amount) external onlyLiquidityManager {
if (_amount > 0) {
uint256 stakingPoolBalance = balanceOf(stakingPool);
if (stakingPoolBalance > 0) {
uint256 excessStake = stakingPoolBalance * _amount / (totalSupply() - stakingPoolBalance);
_burn(stakingPool, excessStake);
}
_burn(address(liquidityManager), _amount);
}
}
function setPreviousTotalSupply(uint256 _ts) external onlyLiquidityManager {
previousTotalSupply = _ts;
}
function outstandingSupply() public view returns (uint256) {
return totalSupply() - balanceOf(liquidityManager);
}
}`;
const liquidityManagerSol = `// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;
import { Kraiken } from "./Kraiken.sol";
import { Optimizer } from "./Optimizer.sol";
import { PriceOracle } from "./abstracts/PriceOracle.sol";
import { ThreePositionStrategy } from "./abstracts/ThreePositionStrategy.sol";
import { IWETH9 } from "./interfaces/IWETH9.sol";
import { CallbackValidation } from "@aperture/uni-v3-lib/CallbackValidation.sol";
import { PoolAddress, PoolKey } from "@aperture/uni-v3-lib/PoolAddress.sol";
import { IERC20 } from "@openzeppelin/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import { IUniswapV3Pool } from "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
import { PositionKey } from "@uniswap-v3-periphery/libraries/PositionKey.sol";
/**
* @title LiquidityManager
* @notice Manages liquidity provisioning on Uniswap V3 using the three-position anti-arbitrage strategy
* @dev Inherits from modular contracts for better separation of concerns and testability
*
* Key features:
* - Three-position anti-arbitrage strategy (ANCHOR, DISCOVERY, FLOOR)
* - Dynamic parameter adjustment via Optimizer contract
* - Asymmetric slippage profile prevents profitable arbitrage
* - Exclusive minting rights for KRAIKEN token
*
* Price Validation:
* - 5-minute TWAP with 50-tick tolerance
* - Prevents oracle manipulation attacks
*/
contract LiquidityManager is ThreePositionStrategy, PriceOracle {
using SafeERC20 for IERC20;
uint24 internal constant FEE = uint24(10_000);
address private immutable factory;
IWETH9 private immutable weth;
Kraiken private immutable kraiken;
Optimizer private immutable optimizer;
IUniswapV3Pool private immutable pool;
bool private immutable token0isWeth;
PoolKey private poolKey;
address private immutable deployer;
address public feeDestination;
int24 public lastRecenterTick;
uint256 public lastRecenterTime;
uint256 internal constant MIN_RECENTER_INTERVAL = 60;
event Recentered(int24 indexed currentTick, bool indexed isUp);
error ZeroAddressInSetter();
error AddressAlreadySet();
constructor(address _factory, address _WETH9, address _kraiken, address _optimizer) {
deployer = msg.sender;
factory = _factory;
weth = IWETH9(_WETH9);
poolKey = PoolAddress.getPoolKey(_WETH9, _kraiken, FEE);
pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
kraiken = Kraiken(_kraiken);
token0isWeth = _WETH9 < _kraiken;
optimizer = Optimizer(_optimizer);
}
function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata) external {
CallbackValidation.verifyCallback(factory, poolKey);
uint256 kraikenPulled = token0isWeth ? amount1Owed : amount0Owed;
uint256 kraikenBalance = kraiken.balanceOf(address(this));
if (kraikenBalance < kraikenPulled) {
kraiken.mint(kraikenPulled - kraikenBalance);
}
uint256 ethOwed = token0isWeth ? amount0Owed : amount1Owed;
if (weth.balanceOf(address(this)) < ethOwed) {
weth.deposit{ value: address(this).balance }();
}
if (amount0Owed > 0) IERC20(poolKey.token0).safeTransfer(msg.sender, amount0Owed);
if (amount1Owed > 0) IERC20(poolKey.token1).safeTransfer(msg.sender, amount1Owed);
}
function setFeeDestination(address feeDestination_) external {
require(msg.sender == deployer, "only deployer");
if (address(0) == feeDestination_) revert ZeroAddressInSetter();
if (feeDestination != address(0)) revert AddressAlreadySet();
feeDestination = feeDestination_;
}
function recenter() external returns (bool isUp) {
(, int24 currentTick,,,,,) = pool.slot0();
// Always enforce cooldown and TWAP price stability — no bypass path
require(block.timestamp >= lastRecenterTime + MIN_RECENTER_INTERVAL, "recenter cooldown");
require(_isPriceStable(currentTick), "price deviated from oracle");
lastRecenterTime = block.timestamp;
isUp = false;
if (positions[Stage.ANCHOR].liquidity > 0) {
int24 anchorTickLower = positions[Stage.ANCHOR].tickLower;
int24 anchorTickUpper = positions[Stage.ANCHOR].tickUpper;
int24 centerTick = anchorTickLower + (anchorTickUpper - anchorTickLower) / 2;
bool isEnough;
(isUp, isEnough) = _validatePriceMovement(currentTick, centerTick, TICK_SPACING, token0isWeth);
require(isEnough, "amplitude not reached.");
}
bool shouldRecordVWAP;
if (cumulativeVolume == 0) {
shouldRecordVWAP = true;
} else {
shouldRecordVWAP = token0isWeth ? (currentTick > lastRecenterTick) : (currentTick < lastRecenterTick);
}
lastRecenterTick = currentTick;
_scrapePositions(shouldRecordVWAP, currentTick);
if (isUp) {
kraiken.setPreviousTotalSupply(kraiken.totalSupply());
}
try optimizer.getLiquidityParams() returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) {
PositionParams memory params = PositionParams({
capitalInefficiency: (capitalInefficiency > 10 ** 18) ? 10 ** 18 : capitalInefficiency,
anchorShare: (anchorShare > 10 ** 18) ? 10 ** 18 : anchorShare,
anchorWidth: (anchorWidth > 100) ? 100 : anchorWidth,
discoveryDepth: (discoveryDepth > 10 ** 18) ? 10 ** 18 : discoveryDepth
});
_setPositions(currentTick, params);
} catch {
PositionParams memory defaultParams = PositionParams({
capitalInefficiency: 0,
anchorShare: 3e17,
anchorWidth: 100,
discoveryDepth: 3e17
});
_setPositions(currentTick, defaultParams);
}
emit Recentered(currentTick, isUp);
}
function _scrapePositions(bool recordVWAP, int24 currentTick) internal {
uint256 fee0 = 0;
uint256 fee1 = 0;
uint256 currentPrice = _priceAtTick(token0isWeth ? -1 * currentTick : currentTick);
for (uint256 i = uint256(Stage.FLOOR); i <= uint256(Stage.DISCOVERY); i++) {
TokenPosition storage position = positions[Stage(i)];
if (position.liquidity > 0) {
(uint256 amount0, uint256 amount1) = pool.burn(position.tickLower, position.tickUpper, position.liquidity);
(uint256 collected0, uint256 collected1) =
pool.collect(address(this), position.tickLower, position.tickUpper, type(uint128).max, type(uint128).max);
fee0 += collected0 - amount0;
fee1 += collected1 - amount1;
}
}
if (feeDestination != address(this)) {
if (fee0 > 0) {
if (token0isWeth) {
IERC20(address(weth)).safeTransfer(feeDestination, fee0);
} else {
IERC20(address(kraiken)).safeTransfer(feeDestination, fee0);
}
}
if (fee1 > 0) {
if (token0isWeth) {
IERC20(address(kraiken)).safeTransfer(feeDestination, fee1);
} else {
IERC20(address(weth)).safeTransfer(feeDestination, fee1);
}
}
}
if (recordVWAP) {
uint256 ethFee = token0isWeth ? fee0 : fee1;
if (ethFee > 0) _recordVolumeAndPrice(currentPrice, ethFee);
}
}
receive() external payable { }
function _getPool() internal view override returns (IUniswapV3Pool) { return pool; }
function _getKraikenToken() internal view override returns (address) { return address(kraiken); }
function _getWethToken() internal view override returns (address) { return address(weth); }
function _isToken0Weth() internal view override returns (bool) { return token0isWeth; }
function _mintPosition(Stage stage, int24 tickLower, int24 tickUpper, uint128 liquidity) internal override {
pool.mint(address(this), tickLower, tickUpper, liquidity, abi.encode(poolKey));
positions[stage] = TokenPosition({ liquidity: liquidity, tickLower: tickLower, tickUpper: tickUpper });
}
function _getEthBalance() internal view override returns (uint256) {
return address(this).balance + weth.balanceOf(address(this));
}
function _getOutstandingSupply() internal view override returns (uint256) {
uint256 supply = kraiken.outstandingSupply();
if (feeDestination != address(0)) {
supply -= kraiken.balanceOf(feeDestination);
}
(, address stakingPoolAddr) = kraiken.peripheryContracts();
if (stakingPoolAddr != address(0)) {
supply -= kraiken.balanceOf(stakingPoolAddr);
}
return supply;
}
}`;
const stakeSol = `// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;
import { Kraiken } from "./Kraiken.sol";
import { IERC20 } from "@openzeppelin/token/ERC20/ERC20.sol";
import { ERC20Permit } from "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol";
import { IERC20Metadata } from "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol";
import { SafeERC20 } from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import { Math } from "@openzeppelin/utils/math/Math.sol";
error ExceededAvailableStake(address receiver, uint256 stakeWanted, uint256 availableStake);
error TooMuchSnatch(address receiver, uint256 stakeWanted, uint256 availableStake, uint256 smallestShare);
/**
* @title Stake Contract for Kraiken Token
* @notice Manages staking positions with Harberger tax. Stakers set and adjust tax rates,
* which affect UBI paid from the tax pool. Handles position creation, snatching,
* tax calculation, rate adjustment, and exit.
*/
contract Stake {
using Math for uint256;
uint256 internal DECIMAL_OFFSET = 5 + 2;
uint256 internal constant MAX_STAKE = 20;
uint256 internal constant TAX_FLOOR_DURATION = 60 * 60 * 24 * 3;
uint256[] public TAX_RATES =
[1, 3, 5, 8, 12, 18, 24, 30, 40, 50, 60, 80, 100, 130, 180, 250, 320, 420, 540, 700, 920, 1200, 1600, 2000, 2600, 3400, 4400, 5700, 7500, 9700];
uint256 internal constant TAX_RATE_BASE = 100;
error TaxTooLow(address receiver, uint64 taxRateWanted, uint64 taxRateMet, uint256 positionId);
error StakeTooLow(address receiver, uint256 assets, uint256 minStake);
error NoPermission(address requester, address owner);
error PositionNotFound(uint256 positionId, address requester);
event PositionCreated(uint256 indexed positionId, address indexed owner, uint256 kraikenDeposit, uint256 share, uint32 taxRate);
event PositionTaxPaid(uint256 indexed positionId, address indexed owner, uint256 taxPaid, uint256 newShares, uint256 taxRate);
event PositionRateHiked(uint256 indexed positionId, address indexed owner, uint256 newTaxRate);
event PositionShrunk(uint256 indexed positionId, address indexed owner, uint256 newShares, uint256 kraikenPayout);
event PositionRemoved(uint256 indexed positionId, address indexed owner, uint256 kraikenPayout);
struct StakingPosition {
uint256 share;
address owner;
uint32 creationTime;
uint32 lastTaxTime;
uint32 taxRate;
}
Kraiken private immutable kraiken;
address private immutable taxReceiver;
uint256 public immutable totalSupply;
uint256 public outstandingStake;
uint256 public nextPositionId;
mapping(uint256 => StakingPosition) public positions;
uint256[] public totalSharesAtTaxRate;
constructor(address _kraiken, address _taxReceiver) {
kraiken = Kraiken(_kraiken);
taxReceiver = _taxReceiver;
totalSupply = 10 ** (kraiken.decimals() + DECIMAL_OFFSET);
nextPositionId = 654_321;
totalSharesAtTaxRate = new uint256[](TAX_RATES.length);
}
function authorizedStake() private view returns (uint256) {
return totalSupply * MAX_STAKE / 100;
}
function _payTax(uint256 positionId, StakingPosition storage pos, uint256 taxFloorDuration) private {
uint256 ihet = (block.timestamp - pos.creationTime < taxFloorDuration) ? pos.creationTime + taxFloorDuration : block.timestamp;
uint256 elapsedTime = ihet - pos.lastTaxTime;
uint256 assetsBefore = sharesToAssets(pos.share);
uint256 taxAmountDue = assetsBefore * TAX_RATES[pos.taxRate] * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE;
if (taxAmountDue >= assetsBefore) {
taxAmountDue = assetsBefore;
}
if (assetsBefore - taxAmountDue > 0) {
uint256 shareAfterTax = assetsToShares(assetsBefore - taxAmountDue);
uint256 deltaShare = pos.share - shareAfterTax;
totalSharesAtTaxRate[pos.taxRate] -= deltaShare;
outstandingStake -= deltaShare;
pos.share = shareAfterTax;
pos.lastTaxTime = uint32(block.timestamp);
emit PositionTaxPaid(positionId, pos.owner, taxAmountDue, shareAfterTax, pos.taxRate);
} else {
totalSharesAtTaxRate[pos.taxRate] -= pos.share;
outstandingStake -= pos.share;
emit PositionTaxPaid(positionId, pos.owner, taxAmountDue, 0, pos.taxRate);
emit PositionRemoved(positionId, pos.owner, 0);
delete pos.owner;
delete pos.creationTime;
delete pos.share;
}
SafeERC20.safeTransfer(kraiken, taxReceiver, taxAmountDue);
}
function _exitPosition(uint256 positionId, StakingPosition storage pos) private {
totalSharesAtTaxRate[pos.taxRate] -= pos.share;
outstandingStake -= pos.share;
address owner = pos.owner;
uint256 assets = sharesToAssets(pos.share);
emit PositionRemoved(positionId, owner, assets);
delete pos.owner;
delete pos.creationTime;
delete pos.share;
SafeERC20.safeTransfer(kraiken, owner, assets);
}
function _shrinkPosition(uint256 positionId, StakingPosition storage pos, uint256 sharesToTake) private {
require(sharesToTake < pos.share, "position too small");
uint256 assets = sharesToAssets(sharesToTake);
pos.share -= sharesToTake;
totalSharesAtTaxRate[pos.taxRate] -= sharesToTake;
outstandingStake -= sharesToTake;
emit PositionShrunk(positionId, pos.owner, pos.share, assets);
SafeERC20.safeTransfer(kraiken, pos.owner, assets);
}
function assetsToShares(uint256 assets) public view returns (uint256) {
return assets.mulDiv(totalSupply, kraiken.totalSupply(), Math.Rounding.Down);
}
function sharesToAssets(uint256 shares) public view returns (uint256) {
return shares.mulDiv(kraiken.totalSupply(), totalSupply, Math.Rounding.Down);
}
function snatch(uint256 assets, address receiver, uint32 taxRate, uint256[] calldata positionsToSnatch) public returns (uint256 positionId) {
uint256 sharesWanted = assetsToShares(assets);
{
uint256 minStake = kraiken.minStake();
if (assets < minStake) {
revert StakeTooLow(receiver, assets, minStake);
}
}
require(taxRate < TAX_RATES.length, "tax rate out of bounds");
uint256 smallestPositionShare = totalSupply;
uint256 availableStake = authorizedStake() - outstandingStake;
if (positionsToSnatch.length >= 2) {
for (uint256 i = 0; i < positionsToSnatch.length - 1; i++) {
StakingPosition storage pos = positions[positionsToSnatch[i]];
if (pos.creationTime == 0) { revert PositionNotFound(positionsToSnatch[i], receiver); }
if (taxRate <= pos.taxRate) { revert TaxTooLow(receiver, taxRate, pos.taxRate, positionsToSnatch[i]); }
if (pos.share < smallestPositionShare) { smallestPositionShare = pos.share; }
_payTax(positionsToSnatch[i], pos, 0);
_exitPosition(positionsToSnatch[i], pos);
}
}
availableStake = authorizedStake() - outstandingStake;
if (positionsToSnatch.length > 0) {
uint256 index = positionsToSnatch.length - 1;
StakingPosition storage lastPos = positions[positionsToSnatch[index]];
if (lastPos.creationTime == 0) { revert PositionNotFound(positionsToSnatch[index], receiver); }
if (taxRate <= lastPos.taxRate) { revert TaxTooLow(receiver, taxRate, lastPos.taxRate, positionsToSnatch[index]); }
if (lastPos.share < smallestPositionShare) { smallestPositionShare = lastPos.share; }
_payTax(positionsToSnatch[index], lastPos, TAX_FLOOR_DURATION);
if (availableStake > sharesWanted) { revert TooMuchSnatch(receiver, sharesWanted, availableStake, smallestPositionShare); }
uint256 lastSharesNeeded = sharesWanted - availableStake;
if (lastSharesNeeded > lastPos.share * 80 / 100) {
_exitPosition(positionsToSnatch[index], lastPos);
} else {
_shrinkPosition(positionsToSnatch[index], lastPos, lastSharesNeeded);
}
}
availableStake = authorizedStake() - outstandingStake;
if (sharesWanted > availableStake) { revert ExceededAvailableStake(receiver, sharesWanted, availableStake); }
if (availableStake - sharesWanted > smallestPositionShare) { revert TooMuchSnatch(receiver, sharesWanted, availableStake, smallestPositionShare); }
SafeERC20.safeTransferFrom(kraiken, msg.sender, address(this), assets);
positionId = nextPositionId++;
StakingPosition storage sp = positions[positionId];
sp.share = sharesWanted;
sp.owner = receiver;
sp.lastTaxTime = uint32(block.timestamp);
sp.creationTime = uint32(block.timestamp);
sp.taxRate = taxRate;
totalSharesAtTaxRate[taxRate] += sharesWanted;
outstandingStake += sharesWanted;
emit PositionCreated(positionId, sp.owner, assets, sp.share, sp.taxRate);
}
function permitAndSnatch(
uint256 assets, address receiver, uint32 taxRate, uint256[] calldata positionsToSnatch,
uint256 deadline, uint8 v, bytes32 r, bytes32 s
) external returns (uint256 positionId) {
ERC20Permit(address(kraiken)).permit(receiver, address(this), assets, deadline, v, r, s);
return snatch(assets, receiver, taxRate, positionsToSnatch);
}
function changeTax(uint256 positionId, uint32 taxRate) external {
require(taxRate < TAX_RATES.length, "tax rate out of bounds");
StakingPosition storage pos = positions[positionId];
if (pos.creationTime == 0) { revert PositionNotFound(positionId, msg.sender); }
if (pos.owner != msg.sender) { revert NoPermission(msg.sender, pos.owner); }
require(taxRate > pos.taxRate, "tax too low to snatch");
_payTax(positionId, pos, 0);
totalSharesAtTaxRate[pos.taxRate] -= pos.share;
totalSharesAtTaxRate[taxRate] += pos.share;
pos.taxRate = taxRate;
emit PositionRateHiked(positionId, pos.owner, taxRate);
}
function exitPosition(uint256 positionId) external {
StakingPosition storage pos = positions[positionId];
if (pos.owner != msg.sender) { revert NoPermission(msg.sender, pos.owner); }
if (pos.creationTime == 0) { revert PositionNotFound(positionId, msg.sender); }
_payTax(positionId, pos, TAX_FLOOR_DURATION);
_exitPosition(positionId, pos);
}
function payTax(uint256 positionId) external {
StakingPosition storage pos = positions[positionId];
if (pos.creationTime == 0) { revert PositionNotFound(positionId, msg.sender); }
_payTax(positionId, pos, 0);
}
function taxDue(uint256 positionId, uint256 taxFloorDuration) public view returns (uint256 amountDue) {
StakingPosition storage pos = positions[positionId];
uint256 ihet = (block.timestamp - pos.creationTime < taxFloorDuration) ? pos.creationTime + taxFloorDuration : block.timestamp;
uint256 elapsedTime = ihet - pos.lastTaxTime;
uint256 assetsBefore = sharesToAssets(pos.share);
amountDue = assetsBefore * TAX_RATES[pos.taxRate] * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE;
}
function getAverageTaxRate() external view returns (uint256 averageTaxRate) {
averageTaxRate = 0;
if (outstandingStake > 0) {
for (uint256 i = 0; i < TAX_RATES.length; i++) {
averageTaxRate += TAX_RATES[i] * totalSharesAtTaxRate[i];
}
averageTaxRate = averageTaxRate / outstandingStake;
averageTaxRate = averageTaxRate * 1e18 / TAX_RATES[TAX_RATES.length - 1];
}
}
function getPercentageStaked() external view returns (uint256 percentageStaked) {
percentageStaked = (outstandingStake * 1e18) / authorizedStake();
}
}`;
const optimizerSol = `// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;
import { Kraiken } from "./Kraiken.sol";
import { Stake } from "./Stake.sol";
import { Initializable } from "@openzeppelin/proxy/utils/Initializable.sol";
import { UUPSUpgradeable } from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol";
/**
* @title OptimizerV3
* @notice Direct 2D (staking%, avgTax) to binary bear/bull liquidity optimizer.
* @dev Replaces the three-zone score-based model with a direct mapping:
*
* staked <= 91% -> BEAR always (no euphoria signal)
* staked > 91% -> BULL if deltaS^3 * effIdx / 20 < 50, else BEAR
*
* Bear: AS=30%, AW=100, CI=0, DD=0.3e18
* Bull: AS=100%, AW=20, CI=0, DD=1e18
*/
contract OptimizerV3 is Initializable, UUPSUpgradeable {
Kraiken private kraiken;
Stake private stake;
uint256[48] private __gap;
error UnauthorizedAccount(address account);
function initialize(address _kraiken, address _stake) public initializer {
_changeAdmin(msg.sender);
kraiken = Kraiken(_kraiken);
stake = Stake(_stake);
}
modifier onlyAdmin() {
_checkAdmin();
_;
}
function _checkAdmin() internal view virtual {
if (_getAdmin() != msg.sender) {
revert UnauthorizedAccount(msg.sender);
}
}
function _authorizeUpgrade(address newImplementation) internal override onlyAdmin { }
function _taxRateToEffectiveIndex(uint256 averageTaxRate) internal pure returns (uint256) {
uint256 idx;
if (averageTaxRate <= 206_185_567_010_309) idx = 0;
else if (averageTaxRate <= 412_371_134_020_618) idx = 1;
else if (averageTaxRate <= 618_556_701_030_927) idx = 2;
else if (averageTaxRate <= 1_030_927_835_051_546) idx = 3;
else if (averageTaxRate <= 1_546_391_752_577_319) idx = 4;
else if (averageTaxRate <= 2_164_948_453_608_247) idx = 5;
else if (averageTaxRate <= 2_783_505_154_639_175) idx = 6;
else if (averageTaxRate <= 3_608_247_422_680_412) idx = 7;
else if (averageTaxRate <= 4_639_175_257_731_958) idx = 8;
else if (averageTaxRate <= 5_670_103_092_783_505) idx = 9;
else if (averageTaxRate <= 7_216_494_845_360_824) idx = 10;
else if (averageTaxRate <= 9_278_350_515_463_917) idx = 11;
else if (averageTaxRate <= 11_855_670_103_092_783) idx = 12;
else if (averageTaxRate <= 15_979_381_443_298_969) idx = 13;
else if (averageTaxRate <= 22_164_948_453_608_247) idx = 14;
else if (averageTaxRate <= 29_381_443_298_969_072) idx = 15;
else if (averageTaxRate <= 38_144_329_896_907_216) idx = 16;
else if (averageTaxRate <= 49_484_536_082_474_226) idx = 17;
else if (averageTaxRate <= 63_917_525_773_195_876) idx = 18;
else if (averageTaxRate <= 83_505_154_639_175_257) idx = 19;
else if (averageTaxRate <= 109_278_350_515_463_917) idx = 20;
else if (averageTaxRate <= 144_329_896_907_216_494) idx = 21;
else if (averageTaxRate <= 185_567_010_309_278_350) idx = 22;
else if (averageTaxRate <= 237_113_402_061_855_670) idx = 23;
else if (averageTaxRate <= 309_278_350_515_463_917) idx = 24;
else if (averageTaxRate <= 402_061_855_670_103_092) idx = 25;
else if (averageTaxRate <= 520_618_556_701_030_927) idx = 26;
else if (averageTaxRate <= 680_412_371_134_020_618) idx = 27;
else if (averageTaxRate <= 886_597_938_144_329_896) idx = 28;
else idx = 29;
if (idx >= 14) {
idx = idx + 1;
if (idx > 29) idx = 29;
}
return idx;
}
function isBullMarket(uint256 percentageStaked, uint256 averageTaxRate) public pure returns (bool bull) {
require(percentageStaked <= 1e18, "Invalid percentage staked");
uint256 stakedPct = percentageStaked * 100 / 1e18;
if (stakedPct <= 91) return false;
uint256 deltaS = 100 - stakedPct;
uint256 effIdx = _taxRateToEffectiveIndex(averageTaxRate);
uint256 penalty = deltaS * deltaS * deltaS * effIdx / 20;
return penalty < 50;
}
function getLiquidityParams() external view returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) {
uint256 percentageStaked = stake.getPercentageStaked();
uint256 averageTaxRate = stake.getAverageTaxRate();
capitalInefficiency = 0;
if (isBullMarket(percentageStaked, averageTaxRate)) {
anchorShare = 1e18;
anchorWidth = 20;
discoveryDepth = 1e18;
} else {
anchorShare = 3e17;
anchorWidth = 100;
discoveryDepth = 3e17;
}
}
}`;
</script>
<style scoped lang="sass">
.code-addresses
margin: 32px 0
padding: 20px
background: rgba(255, 255, 255, 0.05)
border-radius: 8px
.code-address-list
display: flex
flex-direction: column
gap: 12px
margin-top: 12px
.code-address-item
display: flex
align-items: center
gap: 12px
flex-wrap: wrap
.code-address-label
font-weight: 600
font-size: 14px
color: #9A9898
min-width: 80px
a
font-family: monospace
font-size: 13px
color: #7550AE
word-break: break-all
.code-block
margin: 16px 0 32px
border-radius: 8px
overflow: hidden
pre
background: #0d1117
padding: 20px
overflow-x: auto
margin: 0
border: 1px solid rgba(255, 255, 255, 0.1)
border-radius: 8px
code
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace
font-size: 13px
line-height: 1.5
color: #e6edf3
white-space: pre
:deep(.sol-keyword)
color: #c084fc
:deep(.sol-type)
color: #7dd3fc
:deep(.sol-comment)
color: #6b7280
:deep(.sol-string)
color: #a5d6ff
:deep(.sol-number)
color: #79c0ff
</style>