harb/docs/ARCHITECTURE.md

7.8 KiB
Raw Blame History

ARCHITECTURE.md — System Map

Compressed overview for AI agents. Read this first, drill into source for details.

Contract Architecture

Kraiken.sol (ERC-20 token)
├── liquidityManager: address (set once, immutable after)
│   └── LiquidityManager.sol (ThreePositionStrategy)
│       ├── optimizer: Optimizer (private immutable ref)
│       ├── pool: IUniswapV3Pool
│       ├── kraiken: Kraiken
│       └── Positions: Floor, Anchor, Discovery
├── stakingPool: address
│   └── Stake.sol
│       ├── Staking positions with tax rates
│       ├── Snatch mechanics (competitive staking)
│       └── getPercentageStaked(), getAverageTaxRate()
└── feeDestination: address (protocol revenue — both WETH and KRK fees go HERE, not back to holders)

Optimizer.sol (UUPS Upgradeable Proxy)
├── Reads: stake.getPercentageStaked(), stake.getAverageTaxRate()
├── Computes: sentiment → 4 liquidity params
├── Versions: Optimizer, OptimizerV2, OptimizerV3, OptimizerV3Push3
└── Admin: single address, set at initialize()

Key Relationships

  • Kraiken → LiquidityManager: set once via setLiquidityManager(), reverts if already set
  • LiquidityManager → Optimizer: private immutable — baked into constructor, never changes
  • LiquidityManager → Kraiken: exclusive minting/burning rights
  • Optimizer → Stake: reads sentiment data (% staked, avg tax rate)
  • Optimizer upgrades: UUPS proxy, admin-only _authorizeUpgrade()
  • feeDestination receives both WETH and KRK fees: during recenter(), Uniswap V3 fee collection produces both tokens. WETH fees AND KRK fees are forwarded to feeDestination (see LiquidityManager._scrapePositions()).
  • feeDestination is a conditional-lock (not set-once): setFeeDestination() (deployer-only) allows repeated changes while the destination is an EOA, enabling staged deployment and testing. The moment a contract address is set, feeDestinationLocked is set to true and no further changes are allowed. A CREATE2 guard also blocks re-assignment if the current destination has since acquired bytecode. This differs from Kraiken's liquidityManager/stakingPool which are strictly set-once.
  • feeDestination KRK excluded from outstanding supply: _getOutstandingSupply() subtracts kraiken.balanceOf(feeDestination) before computing scarcity, because protocol-held KRK cannot be sold into the floor and should not inflate the supply count. This subtraction only occurs when feeDestination != address(0) && feeDestination != address(this) (see LiquidityManager.sol:324); when feeDestination is unset or is LM itself the balance is not subtracted.
  • Staking pool KRK excluded from outstanding supply: _getOutstandingSupply() also subtracts kraiken.balanceOf(stakingPoolAddr), because staked KRK is locked and similarly cannot be sold into the floor. This subtraction only occurs when stakingPoolAddr != address(0) (see LiquidityManager.sol:327-330); when the staking pool is unset the balance is not subtracted.

Three-Position Strategy

All managed by LiquidityManager via ThreePositionStrategy abstract:

Position Purpose Behavior
Floor Safety net Deep liquidity at VWAP-adjusted prices
Anchor Price discovery Near current price, width set by Optimizer
Discovery Fee capture Borders anchor, ~3x price range (11000 tick spacing)

Recenter = atomic repositioning of all three positions. Triggered by anyone, automated by txnBot.

Recenter constraints (enforced on-chain):

  • 60-second cooldown: MIN_RECENTER_INTERVAL = 60 (LiquidityManager.sol:61). A second recenter cannot succeed until at least 60 seconds have elapsed since the last one.
  • 300-second TWAP window: PRICE_STABILITY_INTERVAL = 300 (PriceOracle.sol:14). recenter() validates the current tick against a 5-minute TWAP average (±MAX_TICK_DEVIATION = 50 ticks). The pool must have at least 300 seconds of observation history; a fallback to a 60 000-second window is used if recent data are unavailable.

Optimizer Parameters

getLiquidityParams() returns 4 values:

  1. capitalInefficiency (0 to 1e18) — capital buffer level
  2. anchorShare (0 to 1e18) — % allocated to anchor position
  3. anchorWidth (ticks) — width of anchor position
  4. discoveryDepth (0 to 1e18) — depth of discovery position

Sentiment calculation: sentiment = f(averageTaxRate, percentageStaked)

  • High sentiment (bull) → wider discovery, more fee revenue for protocol treasury
  • Holder value comes from asymmetric slippage (structural ETH accumulation), NOT from fee reinvestment
  • Low sentiment (bear) → tight around floor, maximum protection

Stack

On-chain

  • Solidity, Foundry toolchain
  • Uniswap V3 for liquidity positions
  • OpenZeppelin for UUPS proxy, Initializable
  • Base L2 (deployment target)

Indexer

  • Ponder (services/ponder/) — indexes on-chain events
  • Schema: services/ponder/ponder.schema.ts
  • Stats table with 168-slot ring buffer (7d × 24h × 4 segments)
  • Ring buffer segments: [ethReserve, minted, burned, tax] (slot 3 being changed to holderCount)
  • GraphQL API at port 42069

Landing Page

  • Vue 3 + Vite (landing/)
  • @wagmi/vue for wallet connection (WalletButton, WalletCard)
  • @tanstack/vue-query — required peer dep for @wagmi/vue; provides TanStack Query context for Wagmi's reactive hooks
  • @harb/web3 shared composables (useAccount, useConnect, useDisconnect, useTokenBalance)
  • Three variants: HomeView (default), HomeViewOffensive (degens), HomeViewMixed
  • Docs section: HowItWorks, Tokenomics, Staking, LiquidityManagement, AIAgent, FAQ
  • LiveStats component polls Ponder GraphQL every 30s

Staking Web App

  • Vue 3 (web-app/)
  • Password-protected (multiple passwords in LoginView.vue)
  • ProtocolStatsCard shows real-time protocol metrics

Infrastructure

  • Docker Compose on 8GB VPS
  • Woodpecker CI at ci.niovi.voyage
  • Codeberg repo: johba/harb (private)
  • Container registry: registry.niovi.voyage

Directory Map

harb/
├── onchain/           # Solidity contracts + Foundry
│   ├── src/           # Contract source
│   ├── test/          # Forge tests
│   └── foundry.toml   # via_ir = true required
├── services/
│   ├── ponder/        # Indexer service
│   │   ├── ponder.schema.ts
│   │   ├── src/
│   │   │   ├── helpers/stats.ts  # Ring buffer logic
│   │   │   ├── lm.ts             # LiquidityManager indexing
│   │   │   └── stake.ts          # Stake indexing
│   └── txnBot/        # Automation bot: calls recenter() and payTax() on profitable opportunities
├── landing/           # Landing page (Vue 3)
│   ├── src/
│   │   ├── components/  # LiveStats, KFooter, WalletCard, etc.
│   │   ├── views/       # HomeView variants, docs pages
│   │   └── router/
├── web-app/           # Staking app (Vue 3)
│   ├── src/
│   │   ├── components/  # ProtocolStatsCard, etc.
│   │   └── views/       # LoginView, StakeView, etc.
├── kraiken-lib/       # Shared TypeScript helpers (bigint math, ABIs, encoding) for frontend and indexer
│   └── src/           # abis, format, ids, position, snatch, staking, subgraph, taxRates, version
├── containers/        # Docker configs, entrypoints
├── tools/             # Developer utilities
│   ├── push3-transpiler/  # Compiles Push3 programs to Solidity Optimizer
│   ├── push3-evolution/   # Evolutionary optimizer: fitness, mutation, crossover, seed generation
│   └── deploy-optimizer.sh  # Script to deploy a new Optimizer version
├── docs/              # This file, PRODUCT-TRUTH.md
└── .woodpecker/       # CI pipeline configs