diff --git a/.claude/settings.json b/.claude/settings.json
index f422b4ed..ace55eed 100644
--- a/.claude/settings.json
+++ b/.claude/settings.json
@@ -2,7 +2,20 @@
"permissions": {
"allow": [
"Bash(grep:*)",
- "Bash(git log:*)"
+ "Bash(git log:*)",
+ "Bash(forge coverage:*)",
+ "Bash(yarn sizes:*)",
+ "WebSearch",
+ "WebFetch(domain:fxprotocol.gitbook.io)",
+ "WebFetch(domain:www.openzeppelin.com)",
+ "Read(~/.claude/plans/**)",
+ "Bash(git -C ~/.claude/plans commit -am )",
+ "Bash(yarn slither:*)",
+ "WebFetch(domain:github.com)",
+ "WebFetch(domain:docs.liquity.org)",
+ "WebFetch(domain:www.liquity.org)",
+ "WebFetch(domain:raw.githubusercontent.com)",
+ "WebFetch(domain:liquity.gitbook.io)"
]
}
}
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
deleted file mode 100644
index 43fe7e51..00000000
--- a/.claude/settings.local.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "permissions": {
- "allow": [
- "Bash(forge test:*)",
- "Bash(cat:*)",
- "WebFetch(domain:dashboard.tenderly.co)",
- "Bash(/tmp/investigate_contract.sh:*)",
- "Bash(chmod:*)",
- "Bash(bash:*)",
- "Bash(forge inspect:*)",
- "Bash(cast call:*)",
- "Bash(forge clean:*)",
- "Bash(forge build:*)",
- "Bash(./script/check_pool_overflow_risk.py:*)",
- "Bash(find:*)",
- "Bash(yarn coverage:*)",
- "Bash(git ls-tree:*)",
- "Bash(git add:*)",
- "Bash(python3:*)",
- "Bash(cast sig-event:*)",
- "Bash(TOPIC0=0x5548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62)",
- "Bash(cast logs:*)",
- "Bash(jq:*)",
- "Bash(script/check-blockchain:*)",
- "Bash(echo:*)",
- "WebFetch(domain:forum.gl-inet.com)",
- "WebFetch(domain:workspace.google.com)"
- ]
- }
-}
diff --git a/.github/workflows/CI-test-foundry-stable.yml b/.github/workflows/CI-test-foundry-stable.yml
index 0ad96662..418e1fe0 100644
--- a/.github/workflows/CI-test-foundry-stable.yml
+++ b/.github/workflows/CI-test-foundry-stable.yml
@@ -16,6 +16,8 @@ env:
jobs:
test_foundry_os:
strategy:
+ max-parallel: 1
+ fail-fast: false # ensure all jobs run even if one fails
matrix:
include:
- os: ubuntu-latest
@@ -27,20 +29,14 @@ jobs:
# - os: windows-latest
# foundry: stable
- fail-fast: false # ensure all jobs run even if one fails
-
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repository with submodules
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
- - name: Verify audited sources unchanged
- shell: bash
- run: lib/bao-base/bin/verify-audit audit-2025-07
-
- name: Run Bao-base CI actions
uses: ./lib/bao-base/.github/actions/test-foundry
with:
diff --git a/.gitignore b/.gitignore
index e1b50e49..ec6cbdaf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,7 +2,6 @@
.env
# put your personal stuff in your personal vscode settings file
-.vscode/settings.json
# libraries
node_modules
@@ -11,6 +10,9 @@ venv
# wake
.wake/
+# claude
+.claude/settings.local.json
+
# generated files
cache/
out/
@@ -54,4 +56,9 @@ docs/
*.log
# temp or prliminary files
-*.*-
\ No newline at end of file
+*.*-
+.wake
+pytypes
+*.py[cod]
+.hypothesis/
+wake-coverage.cov
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
index 25fd9207..e970266c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -10,9 +10,10 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
-[submodule "lib/bao-base"]
- path = lib/bao-base
- url = https://github.com/baofinance/bao-base
[submodule "lib/chainlink-brownie-contracts"]
path = lib/chainlink-brownie-contracts
url = https://github.com/smartcontractkit/chainlink-brownie-contracts
+[submodule "lib/bao-base"]
+ path = lib/bao-base
+ url = https://github.com/baofinance/bao-base
+ branch = main
diff --git a/.solhintignore b/.solhintignore
index dffc2f65..bd08fb11 100644
--- a/.solhintignore
+++ b/.solhintignore
@@ -1,3 +1,3 @@
src/util/WordCodec.sol
*_v1.sol
-src/minter/PostRebalanceRemediationForStabilityPool_v2.sol
\ No newline at end of file
+script/verify/spl-remediation/PostRebalanceRemediationForStabilityPool_v2.sol
\ No newline at end of file
diff --git a/.validate-ignore b/.validate-ignore
new file mode 100644
index 00000000..1010cd98
--- /dev/null
+++ b/.validate-ignore
@@ -0,0 +1,32 @@
+# Syntax: check file
+# Supported checks: naming, pragma, storage, upgrades, base-imports, relative-imports
+
+# These contracts predate the filename=contract-name convention. The class
+# names intentionally omit the _v2 suffix (they are abstract bases, not
+# deployed contracts) while the files carry _v2 to distinguish from v1.
+naming src/reward/accumulator/MultipleRewardCompoundingAccumulator_v2.sol
+naming src/reward/distributor/LinearMultipleRewardDistributor_v2.sol
+
+# Deployed contracts that predate the remapped-import convention.
+# Their source files cannot be modified (audit traceability).
+bare-imports src/reward/accumulator/MultipleRewardCompoundingAccumulator.sol
+bare-imports src/reward/accumulator/MultipleRewardCompoundingAccumulator_v2.sol
+bare-imports src/reward/distributor/LinearMultipleRewardDistributor.sol
+bare-imports src/reward/distributor/LinearMultipleRewardDistributor_v2.sol
+bare-imports src/minter/StabilityPoolManager_v1.sol
+bare-imports src/minter/library/ConfigIncentiveLib.sol
+bare-imports src/minter/library/Config_v1.sol
+bare-imports src/minter/ReservePool_v1.sol
+bare-imports src/minter/StabilityPool_v1.sol
+bare-imports src/minter/StabilityPool_v2.sol
+bare-imports src/minter/TokenDistributor_v1.sol
+bare-imports src/minter/Minter_v1.sol
+bare-imports src/minter/Minter_v2.sol
+bare-imports src/minter/Genesis_v1.sol
+
+# Deployed contracts and interfaces transitively imported by them —
+# cannot be modified (audit traceability).
+relative-imports src/reward/distributor/LinearMultipleRewardDistributor.sol
+relative-imports src/reward/distributor/LinearMultipleRewardDistributor_v2.sol
+relative-imports src/interfaces/IPriceOracle.sol
+relative-imports src/interfaces/IWrappedPriceOracle.sol
diff --git a/.verify-audit-ignore b/.verify-audit-ignore
new file mode 100644
index 00000000..41a2072e
--- /dev/null
+++ b/.verify-audit-ignore
@@ -0,0 +1,9 @@
+# Syntax: tag [file1 file2 ...]
+# tag alone — ignore the whole tag
+# tag file1 file2 — ignore specific files within the tag only
+
+# deploy/harbor-1.2: PostRebalanceRemediationForStabilityPool_v2.sol was a one-shot
+# upgrade implementation. After remediation, the proxy was upgraded back to
+# StabilityPool_v2. The source now lives in script/verify/spl-remediation/ alongside
+# the remediation tests and docs.
+deploy/harbor-1.2 src/minter/PostRebalanceRemediationForStabilityPool_v2.sol
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..271dabd1
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,17 @@
+{
+ "[python]": {
+ "editor.defaultFormatter": "charliermarsh.ruff",
+ "editor.tabSize": 4, // as per PEP 8 standard
+ "editor.formatOnSave": true,
+ // "editor.codeActionsOnSave": {
+ // "source.organizeImports": "explicit"
+ // }
+ },
+ "ruff.interpreter": [
+ "./we-need-a-self-loading-wrapper-for/python"
+ ],
+ "ruff.path": [
+ "lib/bao-base/bin/ruff"
+ ],
+ "ruff.configuration": "pyproject.toml"
+}
diff --git a/CLAUDE.md b/CLAUDE.md
index df79d168..af953e6d 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,5 +1,3 @@
# CLAUDE.md
-- Do not create functions that are only called once. Inline the logic instead.
-- When diagnosing an issue, do not use words like "likely", "probably", or "may" to describe a root cause. Either verify the hypothesis with data (dry run, log, trace) or state explicitly that it is unverified. Never proceed with a fix based on an unverified hypothesis.
-- use forge install/remove for managing submodule dependencies
\ No newline at end of file
+@./lib/bao-base/CLAUDE.md
diff --git a/EXECUTIVE_SUMMARY.md b/EXECUTIVE_SUMMARY.md
deleted file mode 100644
index af4ba2bc..00000000
--- a/EXECUTIVE_SUMMARY.md
+++ /dev/null
@@ -1,120 +0,0 @@
-# Stability Pool Overflow - Executive Summary
-
-## The Problem (30 seconds)
-
-**The Stability Pool will completely break within 1-2 weeks due to an integer overflow.**
-
-- **Current deposits**: 0.097 BTC ($9,700)
-- **Reward rate**: 75 tokens/week (fxSAVE)
-- **Math**: Small deposits + large rewards = accounting number too big for storage
-- **When it breaks**: ALL operations fail (deposits, withdrawals, claims)
-
-## Recommended Solution (30 seconds)
-
-**Queue rewards when overflow would occur, resume when deposits increase.**
-
-- Operations continue working (deposit/withdraw)
-- fxSAVE rewards temporarily pause (queued, not lost)
-- Auto-resumes when deposits reach ~$240k OR if loss event occurs
-- 10 lines of code, zero storage changes, very safe
-
-## Key Metrics
-
-| Metric | Value |
-|--------|-------|
-| **Time to failure** | 1-2 weeks |
-| **Impact if we do nothing** | Pool completely frozen |
-| **Impact with solution** | Rewards pause, operations work |
-| **Deposits needed to fully fix** | +2.4 BTC ($240k @ $100k/BTC) |
-| **Code changes** | ~10 lines, very low risk |
-| **Reversible** | Yes |
-
-## User Impact Comparison
-
-### Without Fix (Do Nothing)
-- ❌ Cannot deposit
-- ❌ Cannot withdraw
-- ❌ Cannot claim rewards
-- 🔴 **Critical user experience failure**
-
-### With Fix (Queue Solution)
-- ✅ Can deposit
-- ✅ Can withdraw
-- ✅ Can claim other rewards
-- ⚠️ **fxSAVE rewards paused until more deposits**
-- 🟡 **Degraded but functional**
-
-## What Happens After Deployment
-
-### Scenario 1: No New Deposits
-- Pool works normally for deposits/withdrawals
-- fxSAVE rewards stop accruing (APY = 0%)
-- Rewards queue up (not lost, just delayed)
-- Need to communicate: "Rewards paused, deposit to resume"
-
-### Scenario 2: Deposits Arrive
-- Need +2.4 BTC ($240k) for full fix
-- Queued rewards distribute gradually
-- Everything returns to normal
-
-### Scenario 3: Loss Event Occurs
-- Natural liquidation loss triggers reset
-- Overflow problem solved immediately
-- Queued rewards distribute
-- Cannot control or predict this
-
-## Communication Strategy
-
-**Week 1 (Post-Deployment):**
-> "We've upgraded the Stability Pool to handle edge cases. fxSAVE rewards may pause temporarily if the reward-to-deposit ratio exceeds limits. All funds remain safe and accessible. Deposits help restore normal reward distribution."
-
-**If Rewards Pause:**
-> "fxSAVE rewards are temporarily queued due to the current reward-to-deposit ratio. Your rewards are not lost—they will be distributed once the pool reaches optimal deposit levels (~2.5 BTC total). All other operations (deposit, withdraw, claim other tokens) work normally."
-
-**Dashboard Update:**
-- Show queue status
-- Show deposits needed
-- Show progress bar to target
-
-## Risk Assessment
-
-| Risk | Severity | Mitigation |
-|------|----------|------------|
-| Users confused about paused rewards | Medium | Clear communication, FAQ, dashboard |
-| Deposits never arrive | Medium | Incentive programs, reduce reward rate |
-| Reputational damage | Low | Transparent communication, funds always safe |
-| Technical risk | Very Low | Simple code, no storage changes, well-tested |
-
-## Decision Required
-
-**Question**: Are we comfortable with:
-1. fxSAVE rewards potentially pausing?
-2. Needing to communicate the pause to users?
-3. Depending on either $240k deposits OR natural loss event to fully resolve?
-
-**If YES**: Proceed with queue solution (recommended)
-**If NO**: Alternative is to stop/reduce fxSAVE distributions via governance before overflow occurs
-
-## Timeline
-
-- **Today**: Make decision
-- **This week**: Deploy fix
-- **Week 1-2**: Monitor, communicate if rewards pause
-- **Ongoing**: Track deposits, consider incentives if needed
-
-## Bottom Line
-
-**The math is simple**:
-```
-Small deposits (0.097 BTC) + Large rewards (75 tokens/week) = Overflow
-```
-
-**The fix is simple**: Queue rewards until deposits increase
-
-**The trade-off is acceptable**: Paused rewards >> Completely frozen pool
-
-**Recommend**: Deploy queue solution, communicate transparently, monitor deposits.
-
----
-
-**Questions? Contact the engineering team for technical details or review SOLUTION_ANALYSIS.md for full breakdown.**
diff --git a/EXECUTIVE_SUMMARY_EXPANDED.md b/EXECUTIVE_SUMMARY_EXPANDED.md
deleted file mode 100644
index 722a8e5d..00000000
--- a/EXECUTIVE_SUMMARY_EXPANDED.md
+++ /dev/null
@@ -1,474 +0,0 @@
-# Stability Pool Overflow - Expanded Executive Summary
-
-## Current State (Right Now)
-
-The Stability Pool is operating at **94.5% of its maximum accounting capacity**:
-
-- **Total Deposits**: 0.097 BTC (≈ $9,700 at $100k/BTC)
-- **Reward Token**: fxSAVE (0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39)
-- **Distribution Rate**: ~75 tokens per week
-- **Accounting Capacity Used**: 5.933×10⁵⁷ out of 6.277×10⁵⁷ maximum (94.5%)
-- **Exponent**: 0 (no significant loss events have occurred)
-
-**The Problem**: The next reward distribution will add 7.73×10⁵⁶ to the accounting number, which **exceeds the maximum by 4.28×10⁵⁶**. This causes a Panic 0x11 integer overflow, freezing ALL pool operations.
-
-**Time to Failure**: 1-2 weeks (next reward distribution)
-
----
-
-## The Queueing Mechanism: What It Does
-
-Instead of letting the pool break, we queue rewards when the accounting number would overflow:
-
-```solidity
-// When integral would overflow:
-if (newIntegral > uint192.max) {
- // Don't break - just queue the rewards
- rewardData[token].queued += amount;
- return; // Skip accumulation for now
-}
-```
-
-**Key Point**: This is a **contract-level bank**, not user-level balances. When rewards are queued:
-- They don't accrue to individual user accounts
-- Users see **0% APY for fxSAVE** (not earning)
-- The contract holds the tokens until conditions improve
-
----
-
-## User Experience: What Users Will See
-
-### Phase 1: Before Queue Activates (Now - Week 1)
-**Everything works normally:**
-- ✅ Deposits work
-- ✅ Withdrawals work
-- ✅ fxSAVE rewards accumulate at current APY
-- ✅ Users can claim all rewards
-
-**Behind the scenes**: Accounting capacity at 94.5% and rising
-
-### Phase 2: Queue Activates (Week 1-2)
-**Operations continue, but fxSAVE rewards pause:**
-- ✅ Deposits work (and help!)
-- ✅ Withdrawals work
-- ✅ Can claim other reward tokens (if any)
-- ⚠️ **fxSAVE rewards show 0% APY**
-- ⚠️ **New fxSAVE tokens don't accrue to user balances**
-- 📊 Dashboard shows: "fxSAVE rewards temporarily queued - deposit to resume"
-
-**What users experience:**
-```
-Current fxSAVE balance: 100 tokens (example)
-After 1 week of queueing: Still 100 tokens
-After 2 weeks of queueing: Still 100 tokens
-```
-
-The rewards are being collected by the contract (queued), but **not distributed** to users.
-
-### Phase 3: Queue Clears (When Unblocked)
-**Normal operations resume:**
-- ✅ All queued tokens distribute to users
-- ✅ fxSAVE APY returns to normal
-- ✅ Users receive backlog of queued rewards proportional to their deposits
-
-**What users experience:**
-```
-Week 0: 100 tokens, 0% APY (queue active)
-Week 3: Deposits arrive, queue clears
-Week 4: 175 tokens (100 original + 75 from queue), APY restored
-```
-
----
-
-## Unblocking Conditions: The Numbers
-
-The queue clears when **EITHER** of these happens:
-
-### Option 1: More Deposits Arrive
-
-**Target**: Increase total deposits from **0.097 BTC to 2.5 BTC**
-
-| Current | Required | Additional Needed | USD Value (@$100k/BTC) |
-|---------|----------|-------------------|------------------------|
-| 0.097 BTC | 2.5 BTC | **+2.4 BTC** | **$240,000** |
-
-**Why this number?**
-```
-Current growth rate: 7.73×10⁵⁶ per week (causes overflow)
-Safe growth rate: 0.30×10⁵⁷ per week (50% headroom)
-
-To achieve safe rate:
-totalShare = (75 tokens × 1×10¹⁸ × 1×10¹⁸ × 1×10³⁶) / 0.30×10⁵⁷
- = 2.5 BTC
-```
-
-**At different BTC prices:**
-- @ $95k/BTC: $228,000 additional
-- @ $90k/BTC: $216,000 additional
-- @ $80k/BTC: $192,000 additional
-
-**Gradual deposit scenarios:**
-
-| Week | Total Deposits | Weekly Growth | Status |
-|------|----------------|---------------|--------|
-| 0 (now) | 0.097 BTC | 7.73×10⁵⁶ | ❌ Overflow! Queue starts |
-| 1 | 0.35 BTC | 2.14×10⁵⁷ | ❌ Still too high |
-| 2 | 0.60 BTC | 1.25×10⁵⁷ | ❌ Still too high |
-| 3 | 1.10 BTC | 0.68×10⁵⁷ | ⚠️ Better, but risky |
-| 4 | 2.50 BTC | 0.30×10⁵⁷ | ✅ Safe! Can resume |
-
-**Important**: Even at 2.5 BTC, we can't dump all queued rewards at once. If 3 weeks of rewards are queued (225 tokens), distributing them all would cause another overflow. Must distribute gradually:
-- Week 4: Distribute 75 tokens (1 week worth)
-- Week 5: Distribute 75 + 75 queued
-- Week 6: Distribute remaining queued rewards
-
-### Option 2: A Loss Event Occurs
-
-**What is a loss event?**
-When the pool experiences a liquidation loss, the internal accounting resets:
-- Exponent increments: 0 → 1
-- Integral resets to 0 at new exponent
-- Overflow problem solved immediately
-
-**How likely is this?**
-- ❌ Cannot control or predict
-- ❌ Not desirable (losses hurt users)
-- ⚠️ May never happen
-- ✅ If it happens, unblocks immediately
-
-**Impact if it happens:**
-```
-Before loss: integral[exponent=0] = 5.933×10⁵⁷ (overflow!)
-After loss: integral[exponent=1] = 0 (fresh start)
-Queued rewards: Can now distribute safely
-```
-
----
-
-## Detailed Scenarios with Numbers
-
-### Scenario A: No Action Taken (Current Code)
-
-**Timeline:**
-- **Week 0** (now): Everything works, integral at 94.5% capacity
-- **Week 1**: depositReward called → Panic 0x11 overflow → **POOL FREEZES**
-- **Ongoing**: ALL operations fail
-
-**User trying to deposit 0.5 BTC:**
-```
-Transaction → calls deposit() → calls _accumulateReward()
-→ tries to add 7.73×10⁵⁶ to integral
-→ PANIC 0x11 (arithmetic overflow)
-→ Transaction reverts
-```
-
-**User trying to withdraw their 0.05 BTC:**
-```
-Transaction → calls withdraw() → calls _accumulateReward()
-→ PANIC 0x11 → Transaction reverts
-```
-
-**Business impact:**
-- Users cannot access their funds (frozen, not lost)
-- Emergency support burden
-- Requires urgent upgrade under pressure
-- Reputational damage
-
----
-
-### Scenario B: Queue Implemented, No New Deposits
-
-**Timeline:**
-- **Week 1**: Queue starts, 75 tokens queued
-- **Week 2**: 150 tokens queued (cumulative)
-- **Week 3**: 225 tokens queued
-- **Week 4**: 300 tokens queued
-- **Ongoing**: Queue grows at 75 tokens/week indefinitely
-
-**User with 0.05 BTC deposited (50% of pool):**
-
-| Week | Their fxSAVE Balance | Expected (if no queue) | Difference |
-|------|----------------------|------------------------|------------|
-| 0 | 100 tokens | 100 tokens | 0 |
-| 1 | 100 tokens | 137.5 tokens | -37.5 |
-| 2 | 100 tokens | 175 tokens | -75 |
-| 3 | 100 tokens | 212.5 tokens | -112.5 |
-| 4 | 100 tokens | 250 tokens | -150 |
-
-**What they see on dashboard:**
-```
-Your fxSAVE Balance: 100 tokens
-Current APY: 0% (rewards queued)
-Queue Status: 225 tokens queued
-Deposits Needed: +2.4 BTC to resume
-Progress: 0.097 / 2.5 BTC (3.9%)
-```
-
-**Communication needed:**
-> "fxSAVE rewards are temporarily queued due to the current reward-to-deposit ratio. Your existing rewards are safe, but new rewards won't accrue until the pool reaches 2.5 BTC total deposits. Depositing helps restore reward distribution for everyone."
-
----
-
-### Scenario C: Sufficient Deposits Arrive (2.5 BTC Total)
-
-**Timeline:**
-- **Weeks 1-3**: Queue active, 225 tokens accumulated
-- **Week 4**: Large deposit brings totalShare to 2.5 BTC
-- **Week 4-7**: Gradual distribution of queued rewards
-
-**Week-by-week breakdown:**
-
-| Week | Event | Integral Change | Queue | Status |
-|------|-------|----------------|-------|--------|
-| 1 | 75 tokens queued | +0 (queued) | 75 | Paused |
-| 2 | 75 tokens queued | +0 (queued) | 150 | Paused |
-| 3 | 75 tokens queued | +0 (queued) | 225 | Paused |
-| 4 | 2.5 BTC deposit arrives | +0 | 225 | Ready! |
-| 4 | Distribute 75 tokens | +0.30×10⁵⁷ | 150 | OK |
-| 5 | Distribute 75 + 75 queued | +0.60×10⁵⁷ | 75 | OK |
-| 6 | Distribute 75 + 75 queued | +0.60×10⁵⁷ | 0 | ✅ Cleared! |
-| 7+ | Normal operations | +0.30×10⁵⁷/week | 0 | Normal |
-
-**User with 0.05 BTC deposited (now 2% of 2.5 BTC pool):**
-
-| Week | Their fxSAVE Balance | Notes |
-|------|----------------------|-------|
-| 0-3 | 100 tokens | Queue active, 0% APY |
-| 4 | 101.5 tokens | Received 2% of 75 distributed |
-| 5 | 104.5 tokens | Received 2% of 150 distributed |
-| 6 | 107.5 tokens | Received 2% of 150 distributed, queue cleared |
-| 7+ | Growing | Normal APY restored |
-
----
-
-### Scenario D: Optimal Deposits (5+ BTC Total)
-
-With 5 BTC total deposits:
-- Growth rate: 0.15×10⁵⁷ per week (very safe)
-- Can distribute large batches of queued rewards
-- 300 tokens (4 weeks queued): Only adds 0.60×10⁵⁷
-
-**User experience:**
-```
-Week 4: 5 BTC deposit arrives
-Week 4: All 225 queued tokens distributed immediately
-Week 5: Normal operations, sustainable APY
-```
-
----
-
-## What Users Can Do While Queued
-
-### ✅ Operations That Work
-1. **Deposit more**: Helps everyone by increasing totalShare
-2. **Withdraw funds**: Full access to principal
-3. **Claim other rewards**: If other tokens are active
-4. **View balances**: All existing balances visible
-
-### ⚠️ Affected Operations
-1. **fxSAVE accrual**: Shows 0% APY
-2. **fxSAVE claims**: Only get existing balance, no new rewards
-3. **Dashboard**: Shows queue status and progress
-
-### ❌ Operations That Don't Work (Only if we do nothing)
-If we don't implement queueing:
-1. **Cannot deposit** (transaction reverts)
-2. **Cannot withdraw** (transaction reverts)
-3. **Cannot claim** (transaction reverts)
-
----
-
-## Communication Strategy: Detailed Messaging
-
-### Pre-Deployment (This Week)
-**To team:**
-> "Deploying upgrade to handle edge case in reward accounting. fxSAVE rewards may temporarily pause if reward-to-deposit ratio exceeds safe limits. Preparing user communications."
-
-### Week 1 (Post-Deployment, Before Queue Activates)
-**Dashboard banner:**
-> "ℹ️ System upgrade deployed. All operations normal."
-
-**No user action needed** - everything works normally.
-
-### Week 1-2 (Queue Activates)
-**Dashboard banner (prominent):**
-> "⚠️ fxSAVE rewards temporarily queued due to current pool size. Your funds are safe and accessible. Deposits help restore reward distribution."
-
-**Detailed page:**
-```
-fxSAVE Reward Status: Queued
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-
-Current Situation:
-• Your funds are completely safe
-• All deposits and withdrawals working normally
-• fxSAVE rewards are being collected but not yet distributed
-
-Why is this happening?
-• Current pool size: 0.097 BTC ($9,700)
-• The reward-to-deposit ratio exceeds safe accounting limits
-• This is a protective measure to ensure pool stability
-
-What happens to my rewards?
-• Existing fxSAVE balance: Unchanged and claimable
-• New fxSAVE rewards: Queued (not lost, just delayed)
-• Queued amount: 75 tokens (updated weekly)
-
-When will rewards resume?
-• Target pool size: 2.5 BTC ($250,000)
-• Current progress: ▓░░░░░░░░░ 3.9%
-• Or: If a natural liquidation event occurs
-
-How can I help?
-• Depositing BTC helps everyone
-• Each 0.1 BTC deposited: +4% progress
-• At 2.5 BTC: All queued rewards distribute
-
-Questions? [Contact Support]
-```
-
-### Week 2+ (Queue Growing)
-**Weekly update email:**
-```
-Subject: Stability Pool Update - Week [X]
-
-fxSAVE Reward Queue Update:
-
-Pool Size: 0.15 BTC (↑ from 0.097 BTC)
-Queued: 150 tokens (2 weeks)
-Progress: 6% toward 2.5 BTC target
-Status: Deposits/withdrawals working normally
-
-Your deposits help restore reward distribution for the entire community.
-
-[Deposit Now] [Learn More]
-```
-
----
-
-## Risk Assessment: Detailed Impact Analysis
-
-### Technical Risks
-
-| Risk | Probability | Impact | Mitigation |
-|------|-------------|--------|------------|
-| Code bug in queue logic | Very Low | High | Thorough testing, simple code (10 lines) |
-| UUPS storage corruption | Very Low | Critical | No storage changes required |
-| Overflow in queue counter | Very Low | Medium | uint96 max = 7.9×10²⁸ tokens |
-| Gas cost increase | None | N/A | No new transactions required |
-
-### Business Risks
-
-| Risk | Probability | Impact | Mitigation |
-|------|-------------|--------|------------|
-| User confusion | High | Medium | Clear messaging, FAQ, support |
-| Users withdraw funds | Medium | High | Transparent communication, emphasize safety |
-| Deposits never arrive | Medium | High | Incentive programs, reduce reward rate |
-| Negative perception | Medium | Medium | Proactive communication, funds always safe |
-| Competitor advantage | Low | Low | Industry-standard protective measure |
-
-### User Experience Risks
-
-| Scenario | User Type | Impact | Communication |
-|----------|-----------|--------|----------------|
-| Can't claim new fxSAVE | Active user | High | "Rewards queued, not lost" |
-| Sees 0% APY | Potential depositor | High | "Temporary, helps resume" |
-| Wants to withdraw | Concerned user | Low | "Works normally" |
-| Wants to deposit | New user | None | "Helps everyone" |
-
----
-
-## Decision Framework
-
-**Question 1: What happens if we do nothing?**
-- Pool breaks in 1-2 weeks
-- ALL operations fail (deposits, withdrawals, claims)
-- Emergency upgrade required under pressure
-- User funds safe but inaccessible
-
-**Question 2: What happens with queueing?**
-- Pool operations continue (deposits, withdrawals work)
-- fxSAVE rewards pause (0% APY)
-- Rewards resume when deposits reach 2.5 BTC OR loss event
-- Users may not understand, requires communication
-
-**Question 3: Can we get $240k in deposits?**
-- **If YES**: Queue clears in weeks/months, normal operations resume
-- **If NO**: Queue persists indefinitely, but pool still functional
-- **Alternative**: Reduce fxSAVE distribution rate via governance
-
-**Question 4: Are we comfortable with the trade-off?**
-- **Paused rewards** (degraded UX) vs **Frozen pool** (critical failure)
-- **Temporary 0% APY** vs **Cannot access funds**
-- **Need communication** vs **Emergency upgrade**
-
----
-
-## Timeline: Detailed Action Plan
-
-### Immediate (Today)
-- [ ] Review this analysis
-- [ ] Make go/no-go decision
-- [ ] Approve communication strategy
-- [ ] Alert support team
-
-### This Week
-- [ ] Deploy queue mechanism upgrade
-- [ ] Test on testnet (if possible)
-- [ ] Prepare dashboard changes
-- [ ] Draft user communications
-- [ ] Monitor integral capacity (currently 94.5%)
-
-### Week 1-2
-- [ ] Queue likely activates
-- [ ] Deploy dashboard updates (queue status, progress bar)
-- [ ] Send user notifications
-- [ ] Monitor user reactions
-- [ ] Track queue growth
-- [ ] Consider deposit incentives
-
-### Week 3+
-- [ ] Weekly updates to users
-- [ ] Track deposit progress
-- [ ] Adjust communication as needed
-- [ ] If deposits arrive: Communicate queue clearing timeline
-- [ ] If no deposits: Evaluate alternative options
-
-### Long-term
-- [ ] Reduce reward rate if queue persists (requires governance)
-- [ ] Design incentive program for deposits
-- [ ] Monitor for loss events (automatic unblock)
-- [ ] Plan for next distribution period
-
----
-
-## Bottom Line
-
-**The Math:**
-```
-Current: 0.097 BTC deposits + 75 tokens/week = 7.73×10⁵⁶ growth
-Overflow at: 6.28×10⁵⁷ maximum
-Time to failure: 1-2 weeks
-
-Safe: 2.5 BTC deposits + 75 tokens/week = 0.30×10⁵⁷ growth
-Unblocks at: $240k additional deposits OR loss event
-```
-
-**The Trade-off:**
-- **Without queueing**: Pool completely breaks, all operations fail
-- **With queueing**: Pool works, but fxSAVE rewards pause until unblocked
-
-**The Recommendation:**
-Deploy queueing mechanism. It prevents catastrophic failure while maintaining core functionality. Users experience degraded service (0% fxSAVE APY) instead of complete failure (cannot access funds).
-
-**The Communication:**
-Be transparent: "Your funds are safe, rewards are temporarily queued, deposits help resume distribution."
-
-**The Risk:**
-Low technical risk (simple code, no storage changes), medium business risk (user perception, need good communication).
-
----
-
-**Next Step: Approve deployment and communication strategy.**
-
-For technical implementation details, see SOLUTION_ANALYSIS.md.
diff --git a/FINISHAT_ZERO_ROOT_CAUSE_FOUND.md b/FINISHAT_ZERO_ROOT_CAUSE_FOUND.md
deleted file mode 100644
index 34dc89e6..00000000
--- a/FINISHAT_ZERO_ROOT_CAUSE_FOUND.md
+++ /dev/null
@@ -1,227 +0,0 @@
-# Root Cause Found: Why finishAt is Zero
-
-## Executive Summary
-
-**Mystery Solved**: Token 1 has `finishAt = 0` and `lastUpdate > 0` because it was registered as a reward token but **never received any reward deposits**. This is normal contract behavior, not a bug or corruption.
-
-## The Smoking Gun
-
-### Test Evidence
-
-Created test: [test/ExplainFinishAtZero.t.sol](test/ExplainFinishAtZero.t.sol)
-
-This test **successfully replicates the exact mainnet state** without any storage manipulation:
-- Token1 registered as active reward token
-- Token0 receives deposits, Token1 never does
-- After 4 deposits to Token0, Token1 has:
- - `lastUpdate: 1769846711` ✅ (matches mainnet exactly)
- - `finishAt: 0` ✅ (matches mainnet exactly)
- - `rate: 0` ✅
- - `queued: 0` ✅
-
-## How This Happens (Step by Step)
-
-### The Code Path
-
-When `depositReward(token0, amount)` is called:
-
-1. **First**: `_distributePendingReward()` is called
- ```solidity
- // src/reward/distributor/LinearMultipleRewardDistributor.sol:235-254
- function _distributePendingReward() internal {
- address[] memory activeRewardTokens_ = $.activeRewardTokens.values();
- for (uint256 i = 0; i < activeRewardTokens_.length; i++) {
- address token = activeRewardTokens_[i];
- (uint256 pending, ) = $.rewardData[token].pending();
- $.rewardData[token].lastUpdate = uint40(block.timestamp); // ⚠️ ALWAYS updates!
-
- if (pending > 0) {
- _accumulateReward(token, pending);
- }
- }
- }
- ```
-
- **Key Point**: This function loops through **ALL** active reward tokens and **ALWAYS** sets `lastUpdate = block.timestamp` for every token, regardless of whether they have pending rewards.
-
-2. **Second**: `_notifyReward(token0, amount)` is called
- ```solidity
- // src/reward/distributor/LinearMultipleRewardDistributor.sol:222-232
- function _notifyReward(address token, uint256 amount) internal {
- if (REWARD_PERIOD_LENGTH == 0) {
- _accumulateReward(token, amount);
- } else {
- LinearReward.RewardData memory data = $.rewardData[token];
- data.increase(REWARD_PERIOD_LENGTH, amount); // ⚠️ Only for this token!
- $.rewardData[token] = data;
- }
- }
- ```
-
- **Key Point**: This only calls `increase()` for the token being deposited (token0), which sets both `lastUpdate` and `finishAt`.
-
-### The Result for Token1
-
-When Token0 receives deposits but Token1 never does:
-
-| Deposit # | Token0 State | Token1 State |
-|-----------|-------------|--------------|
-| Initial | lastUpdate: 0 finishAt: 0 | lastUpdate: 0 finishAt: 0 |
-| After deposit 1 | lastUpdate: ✅ SET finishAt: ✅ SET | lastUpdate: ✅ SET finishAt: ❌ STILL 0 |
-| After deposit 2 | lastUpdate: ✅ UPDATED finishAt: ✅ UPDATED | lastUpdate: ✅ UPDATED finishAt: ❌ STILL 0 |
-| After deposit 3 | lastUpdate: ✅ UPDATED finishAt: ✅ UPDATED | lastUpdate: ✅ UPDATED finishAt: ❌ STILL 0 |
-| After deposit 4 | lastUpdate: ✅ UPDATED finishAt: ✅ UPDATED | lastUpdate: ✅ UPDATED finishAt: ❌ STILL 0 |
-
-## Why This Creates the Underflow Bug
-
-With Token1 having `finishAt: 0` and `lastUpdate: 1769846711`:
-
-### Condition 1: `finishAt < periodLength`
-```solidity
-// src/reward/distributor/LinearReward.sol:48
-uint256 _elapsed = block.timestamp - (_data.finishAt - _periodLength);
-```
-- Calculation: `block.timestamp - (0 - 604800)`
-- **Result**: UNDERFLOW ❌
-
-### Condition 2: `finishAt < lastUpdate`
-```solidity
-// src/reward/distributor/LinearReward.sol:52
-_amount = _amount + uint256(_data.rate) * (_data.finishAt - _data.lastUpdate);
-```
-- Calculation: `0 - 1769846711`
-- **Result**: UNDERFLOW ❌
-
-## Historical Timeline on Mainnet
-
-Going back through blocks, Token 1 **always** had `finishAt = 0`:
-
-| Block | Timestamp | lastUpdate | finishAt | Event |
-|-------|-----------|------------|----------|-------|
-| 24200000 | 1767577079 | 1767577079 | **0** | Token0 deposit |
-| 24250000 | 1768574651 | 1768574651 | **0** | Token0 deposit |
-| 24280000 | 1768952291 | 1768952291 | **0** | Token0 deposit |
-| 24290000 | 1769019179 | 1769019179 | **0** | Token0 deposit |
-| 24295000 | 1769114399 | 1769114399 | **0** | Token0 deposit |
-| 24300000 | 1769153363 | 1769153363 | **0** | Token0 deposit |
-| 24320000 | 1769315855 | 1769315855 | **0** | Token0 deposit |
-| 24340000 | 1769608823 | 1769608823 | **0** | Token0 deposit |
-| 24355000 | 1769846711 | 1769846711 | **0** | Token0 deposit |
-| 24404265 | 1770459107 | 1769846711 | **0** | Current block |
-
-**Pattern**: Every time Token0 received a deposit, Token1's `lastUpdate` was updated but `finishAt` remained 0.
-
-## Is This a Bug?
-
-### The Contract Behavior is Intentional
-
-The `_distributePendingReward()` function updating `lastUpdate` for all active tokens is **by design**. It ensures that pending rewards are properly tracked and accumulated before new rewards are deposited.
-
-### The Problem is the Edge Case
-
-The contract logic **assumes** that all registered reward tokens will eventually receive deposits. The code doesn't handle the edge case where:
-1. A token is registered as an active reward token
-2. But never receives any deposits
-
-This edge case creates a state that:
-- Is valid from the contract's perspective (token is active, no deposits yet)
-- But triggers arithmetic underflow in the LinearReward library
-
-## Why Was Token1 Registered Without Deposits?
-
-Possible scenarios:
-1. **Planned but not executed**: Token1 was registered in anticipation of future rewards, but deposits never happened
-2. **Changed plans**: Initial plan was to distribute Token1 as rewards, but plans changed
-3. **Test/placeholder**: Token1 was registered for testing purposes
-4. **Misconfiguration**: Token1 was registered by mistake
-
-## The Fix
-
-Two approaches:
-
-### Option 1: Apply the Safe Subtraction Fix (Recommended)
-```solidity
-// src/reward/distributor/LinearReward.sol
-
-// Line 48-50 (Fixed):
-uint256 periodStart = _data.finishAt >= _periodLength ? _data.finishAt - _periodLength : 0;
-uint256 _elapsed = block.timestamp >= periodStart ? block.timestamp - periodStart : 0;
-
-// Line 52-54 (Fixed):
-uint256 timeSinceLastUpdate = _data.finishAt >= _data.lastUpdate ? _data.finishAt - _data.lastUpdate : 0;
-_amount = _amount + uint256(_data.rate) * timeSinceLastUpdate;
-```
-
-This handles the edge case gracefully and prevents underflow.
-
-### Option 2: Prevent the Edge Case
-Modify `_distributePendingReward()` to only update `lastUpdate` for tokens that have been initialized (finishAt > 0):
-
-```solidity
-function _distributePendingReward() internal {
- address[] memory activeRewardTokens_ = $.activeRewardTokens.values();
- for (uint256 i = 0; i < activeRewardTokens_.length; i++) {
- address token = activeRewardTokens_[i];
- LinearReward.RewardData storage data = $.rewardData[token];
-
- // Only update if token has been initialized (received at least one deposit)
- if (data.finishAt > 0) {
- (uint256 pending, ) = data.pending();
- data.lastUpdate = uint40(block.timestamp);
-
- if (pending > 0) {
- _accumulateReward(token, pending);
- }
- }
- }
-}
-```
-
-**Recommendation**: Use **Option 1** because:
-- It's safer and more defensive
-- Handles all edge cases, not just this one
-- Doesn't change core contract logic
-- Minimal code changes
-
-## Immediate Action Required
-
-1. ✅ **Root cause identified and confirmed with test**
-2. ⏭️ **Apply the fix from Option 1**
-3. ⏭️ **Test the fix** (tests already exist)
-4. ⏭️ **Deploy upgrade** to mainnet
-5. ⏭️ **Optionally unregister Token1** if it won't be used
-6. ⏭️ **Add documentation** about not registering tokens without deposits
-
-## Files Created
-
-1. **[test/ExplainFinishAtZero.t.sol](test/ExplainFinishAtZero.t.sol)** ⭐ NEW
- - Demonstrates exactly how the state occurs
- - Replicates mainnet state without storage manipulation
- - Confirms the root cause
-
-2. **[test/InvestigateTransactionHistory.t.sol](test/InvestigateTransactionHistory.t.sol)** ⭐ NEW
- - Historical analysis across blocks
- - Contract upgrade checks
- - Storage slot examination
-
-3. **[WHY_FINISHAT_IS_ZERO.md](WHY_FINISHAT_IS_ZERO.md)**
- - Investigation timeline
- - Theories and analysis
-
-4. **[MAINNET_UNDERFLOW_COMPLETE_REPORT.md](MAINNET_UNDERFLOW_COMPLETE_REPORT.md)**
- - Complete bug documentation
- - Test coverage summary
-
-## Conclusion
-
-**Root Cause**: Token 1 was registered as an active reward token but never received deposits. The `_distributePendingReward()` function updated its `lastUpdate` every time other tokens received deposits, but `finishAt` remained 0 because `increase()` was never called for Token1.
-
-**Status**: ✅ **MYSTERY SOLVED**
-**Impact**: Still CRITICAL - users cannot deposit
-**Solution**: Apply safe subtraction fix to LinearReward.sol
-**Prevention**: Document that registered reward tokens should receive deposits, or handle the edge case in code
-
----
-
-**Investigation Complete**
-**Next Step**: Apply the fix and deploy upgrade
diff --git a/FRONTEND-WALLET-ERROR-FIX.md b/FRONTEND-WALLET-ERROR-FIX.md
deleted file mode 100644
index da9040b5..00000000
--- a/FRONTEND-WALLET-ERROR-FIX.md
+++ /dev/null
@@ -1,235 +0,0 @@
-# Fix: "THE METHOD ETH_SENDRAWTRANSACTION DOES NOT EXIST" Error
-
-## Problem
-
-When trying to approve wstETH for deposit, the wallet shows:
-
-```
-THE METHOD ETH_SENDRAWTRANSACTION DOES NOT EXIST/IS NOT AVAILABLE
-```
-
-## Root Cause
-
-The wallet (MetaMask/other) is trying to use `eth_sendRawTransaction` which Anvil may not support in the same way as mainnet, OR the frontend is configured incorrectly.
-
-## Solutions
-
-### Solution 1: Ensure Correct RPC Configuration
-
-Make sure your frontend is using the Anvil RPC URL, not mainnet:
-
-```typescript
-// ✅ CORRECT - Use localhost:8545
-const provider = new ethers.providers.JsonRpcProvider("http://localhost:8545");
-
-// ❌ WRONG - Don't use mainnet RPC
-// const provider = new ethers.providers.JsonRpcProvider("https://eth-mainnet.g.alchemy.com/...");
-```
-
-### Solution 2: Use Wallet Provider, Not Raw Transactions
-
-When using a wallet like MetaMask, use the wallet's provider, not raw transaction methods:
-
-```typescript
-// ✅ CORRECT - Use wallet provider
-import { ethers } from "ethers";
-
-// Get provider from wallet
-const provider = new ethers.providers.Web3Provider(window.ethereum);
-const signer = provider.getSigner();
-
-// Use the signer to send transactions
-const wstETH = new ethers.Contract(
- "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", // wstETH address
- ERC20_ABI,
- signer,
-);
-
-// This will use the wallet's signing mechanism, not raw transactions
-const tx = await wstETH.approve(
- "0x8806fc80A0274Eda6a45E2944f6bB6E6Bb635831", // Genesis contract
- ethers.constants.MaxUint256,
-);
-await tx.wait();
-
-// ❌ WRONG - Don't use raw transactions
-// const rawTx = await signer.signTransaction(...);
-// await provider.sendTransaction(rawTx);
-```
-
-### Solution 3: Ensure Wallet is Connected to Anvil Network
-
-Add the Anvil network to MetaMask:
-
-```typescript
-// Add Anvil network to wallet
-const anvilNetwork = {
- chainId: "0x7A69", // 31337 in hex
- chainName: "Anvil Local",
- nativeCurrency: {
- name: "Ether",
- symbol: "ETH",
- decimals: 18,
- },
- rpcUrls: ["http://localhost:8545"],
- blockExplorerUrls: [],
-};
-
-try {
- await window.ethereum.request({
- method: "wallet_addEthereumChain",
- params: [anvilNetwork],
- });
-} catch (error) {
- console.error("Error adding network:", error);
-}
-
-// Switch to Anvil network
-await window.ethereum.request({
- method: "wallet_switchEthereumChain",
- params: [{ chainId: "0x7A69" }],
-});
-```
-
-### Solution 4: Check Wallet Provider Configuration
-
-If using wagmi or similar, ensure the RPC URL is correct:
-
-```typescript
-// wagmi configuration
-import { configureChains, createConfig } from "wagmi";
-import { jsonRpcProvider } from "wagmi/providers/jsonRpc";
-
-const { chains, publicClient } = configureChains(
- [
- {
- id: 31337,
- name: "Anvil Local",
- network: "anvil",
- nativeCurrency: {
- decimals: 18,
- name: "Ether",
- symbol: "ETH",
- },
- rpcUrls: {
- default: {
- http: ["http://localhost:8545"],
- },
- },
- },
- ],
- [
- jsonRpcProvider({
- rpc: (chain) => ({
- http: "http://localhost:8545",
- }),
- }),
- ],
-);
-```
-
-### Solution 5: Use ethers.js Correctly with Wallets
-
-```typescript
-// ✅ CORRECT - Full example
-import { ethers } from "ethers";
-
-async function approveWstETH() {
- // 1. Get provider from wallet
- if (!window.ethereum) {
- throw new Error("No wallet found");
- }
-
- const provider = new ethers.providers.Web3Provider(window.ethereum);
-
- // 2. Request account access
- await provider.send("eth_requestAccounts", []);
-
- // 3. Get signer
- const signer = provider.getSigner();
-
- // 4. Create contract instance with signer
- const wstETH = new ethers.Contract(
- "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0",
- [
- "function approve(address spender, uint256 amount) external returns (bool)",
- "function allowance(address owner, address spender) external view returns (uint256)",
- ],
- signer,
- );
-
- // 5. Check current allowance
- const currentAllowance = await wstETH.allowance(
- await signer.getAddress(),
- "0x8806fc80A0274Eda6a45E2944f6bB6E6Bb635831",
- );
-
- // 6. Approve if needed
- if (currentAllowance.lt(ethers.utils.parseEther("1000"))) {
- const tx = await wstETH.approve("0x8806fc80A0274Eda6a45E2944f6bB6E6Bb635831", ethers.constants.MaxUint256);
- console.log("Transaction sent:", tx.hash);
- await tx.wait();
- console.log("Approval confirmed!");
- }
-}
-```
-
-## Quick Debug Checklist
-
-1. ✅ Is Anvil running on `http://localhost:8545`?
-
- ```bash
- curl http://localhost:8545 -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
- # Should return: {"result":"0x7a69"} (31337 in hex)
- ```
-
-2. ✅ Is the wallet connected to chain ID 31337?
- - Check MetaMask network dropdown
- - Should show "Anvil Local" or chain ID 31337
-
-3. ✅ Is the frontend using `http://localhost:8545` as RPC URL?
- - Check browser console for network requests
- - Should see requests to `localhost:8545`, not mainnet RPCs
-
-4. ✅ Is the code using wallet provider, not raw transactions?
- - Look for `eth_sendRawTransaction` in your code
- - Should use `signer.sendTransaction()` or `contract.method()` instead
-
-## Common Mistakes
-
-❌ **Using mainnet RPC URL:**
-
-```typescript
-const provider = new ethers.providers.JsonRpcProvider("https://eth-mainnet.g.alchemy.com/...");
-```
-
-❌ **Trying to send raw transactions manually:**
-
-```typescript
-const rawTx = await signer.signTransaction(tx);
-await provider.send("eth_sendRawTransaction", [rawTx]);
-```
-
-❌ **Not connecting wallet to Anvil network:**
-
-- Wallet is on mainnet but trying to interact with Anvil contracts
-
-## Verification
-
-After applying fixes, test the approval:
-
-```typescript
-// Test approval
-const wstETH = new ethers.Contract(wstETHAddress, ERC20_ABI, signer);
-const tx = await wstETH.approve(genesisAddress, ethers.constants.MaxUint256);
-console.log("Tx hash:", tx.hash);
-const receipt = await tx.wait();
-console.log("Confirmed in block:", receipt.blockNumber);
-```
-
-## Current Contract Addresses (from latest deployment)
-
-- **wstETH**: `0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0`
-- **Genesis**: `0x8806fc80A0274Eda6a45E2944f6bB6E6Bb635831`
-- **Chain ID**: `31337`
-- **RPC URL**: `http://localhost:8545`
diff --git a/MAINNET_UNDERFLOW_COMPLETE_REPORT.md b/MAINNET_UNDERFLOW_COMPLETE_REPORT.md
deleted file mode 100644
index 9870d32c..00000000
--- a/MAINNET_UNDERFLOW_COMPLETE_REPORT.md
+++ /dev/null
@@ -1,223 +0,0 @@
-# Mainnet Underflow Investigation - Complete Report
-
-## Executive Summary
-
-**Status**: ✅ **BUG CONFIRMED ON MAINNET**
-**Contract**: `0x9e56F1E1E80EBf165A1dAa99F9787B41cD5bFE40` (StabilityPool)
-**Block**: 24404265
-**Impact**: **Deposits completely blocked** - users cannot deposit into the pool
-
-## Root Cause
-
-### Problematic State on Mainnet
-
-**Reward Token 1**: `0x9567c243F647f9Ac37efb7Fc26BD9551Dce0BE1B`
-
-```
-lastUpdate: 1769846711 (valid timestamp)
-finishAt: 0 ❌ PROBLEM!
-rate: 0
-queued: 0
-block.timestamp: 1770459107
-```
-
-### How This State Occurred (Without Storage Corruption)
-
-This state can legitimately occur through normal operations:
-
-1. **Reward token registered** → `finishAt: 0`, `lastUpdate: 0`
-2. **First rewards deposited** → `lastUpdate` and `finishAt` get set
-3. **Reward period finishes** → `finishAt` remains set, distribution complete
-4. **Token unregistered or period expires** → State becomes inconsistent
-
-### The Underflow Conditions
-
-With `finishAt = 0` and `lastUpdate = 1769846711`:
-
-**Condition 1**: `finishAt < periodLength`
-- `0 < 1209600` ✅ TRUE
-- Would trigger line 48 underflow: `block.timestamp - (finishAt - periodLength)`
-- Calculation: `block.timestamp - (0 - 1209600)` = **UNDERFLOW**
-
-**Condition 2**: `finishAt < lastUpdate`
-- `0 < 1769846711` ✅ TRUE
-- Would trigger line 52 underflow: `(finishAt - lastUpdate)`
-- Calculation: `0 - 1769846711` = **UNDERFLOW**
-
-## User Impact
-
-### What Users Experience
-
-When users try to deposit into the StabilityPool:
-
-```
-Error: "Arithmetic operation resulted in underflow or overflow"
-Panic Code: 0x11
-```
-
-The deposit transaction **simulation fails**, preventing users from depositing.
-
-### Why Deposits Fail
-
-1. User calls `StabilityPool.deposit()`
-2. Internally calls `_distributePendingReward()`
-3. Loops through all active reward tokens
-4. Calls `LinearReward.increase()` on each token
-5. **Token 1 with `finishAt = 0` causes underflow**
-6. Entire transaction reverts
-
-## The Bug in Code
-
-**File**: `src/reward/distributor/LinearReward.sol`
-
-### Line 48 Bug (Buggy Code):
-```solidity
-// CURRENT BUGGY CODE:
-uint256 _elapsed = block.timestamp - (_data.finishAt - _periodLength);
-
-// UNDERFLOWS WHEN:
-// - finishAt < periodLength
-// - OR (finishAt - periodLength) > block.timestamp
-```
-
-### Line 52 Bug (Buggy Code):
-```solidity
-// CURRENT BUGGY CODE:
-_amount = _amount + uint256(_data.rate) * (_data.finishAt - _data.lastUpdate);
-
-// UNDERFLOWS WHEN:
-// - finishAt < lastUpdate
-```
-
-## The Fix (from Bug Report)
-
-### Line 48-50 (Fixed):
-```solidity
-// FIXED CODE:
-uint256 periodStart = _data.finishAt >= _periodLength ? _data.finishAt - _periodLength : 0;
-uint256 _elapsed = block.timestamp >= periodStart ? block.timestamp - periodStart : 0;
-```
-
-### Line 52-54 (Fixed):
-```solidity
-// FIXED CODE:
-uint256 timeSinceLastUpdate = _data.finishAt >= _data.lastUpdate ? _data.finishAt - _data.lastUpdate : 0;
-_amount = _amount + uint256(_data.rate) * timeSinceLastUpdate;
-```
-
-## Test Coverage
-
-### Storage Manipulation Tests (Already Created)
-
-These tests use `vm.store()` to create the underflow conditions:
-
-1. ✅ `test_depositReward_UnderflowBug_Line48_BlockTimestampTooLow()`
- - **Location**: [test/reward/distributor/LinearMultipleRewardDistributor.t.sol:696](test/reward/distributor/LinearMultipleRewardDistributor.t.sol#L696)
- - **Tests**: Line 48 underflow
- - **Status**: ❌ FAILS with panic 0x11 (demonstrates bug)
-
-2. ✅ `test_depositReward_UnderflowBug_Line52_FinishAtLessThanLastUpdate()`
- - **Location**: [test/reward/distributor/LinearMultipleRewardDistributor.t.sol:756](test/reward/distributor/LinearMultipleRewardDistributor.t.sol#L756)
- - **Tests**: Line 52 underflow
- - **Status**: ❌ FAILS with panic 0x11 (demonstrates bug)
-
-### Mainnet Investigation Tests (Created)
-
-These tests fork mainnet to investigate the actual issue:
-
-3. ✅ `test_InvestigateContractState()`
- - **Location**: [test/MainnetUnderflowInvestigation.t.sol:31](test/MainnetUnderflowInvestigation.t.sol#L31)
- - **Purpose**: Inspect actual mainnet state
- - **Findings**: Confirmed `finishAt: 0`, `lastUpdate: 1769846711`
-
-4. ✅ `test_InvestigatePreviousBlocks()`
- - **Location**: [test/MainnetUnderflowInvestigation.t.sol:93](test/MainnetUnderflowInvestigation.t.sol#L93)
- - **Purpose**: Track when the problematic state occurred
- - **Findings**: State changed between blocks 24404165 and 24404265
-
-## Key Findings
-
-### Why Storage Manipulation Was Needed for Tests
-
-The underflow bugs in lines 48 and 52 are in the **`else` branch** of `increase()`:
-
-```solidity
-if (block.timestamp >= _data.finishAt) {
- // Safe branch - handles period completion
-} else {
- // BUGGY BRANCH - underflow can occur here
- uint256 _elapsed = block.timestamp - (_data.finishAt - _periodLength); // Line 48
- // ...
- _amount = _amount + uint256(_data.rate) * (_data.finishAt - _data.lastUpdate); // Line 52
-}
-```
-
-To enter the `else` branch, we need: `block.timestamp < finishAt`
-
-On mainnet with `finishAt = 0`:
-- `block.timestamp < finishAt` → `1770459107 < 0` → **FALSE**
-- So we enter the `if` branch, not the `else` branch
-
-**This explains why storage manipulation was necessary** - to create `finishAt > block.timestamp` to enter the buggy `else` branch.
-
-### The Real Question
-
-**How did `finishAt` become 0 on mainnet while `lastUpdate` remained set?**
-
-This requires further investigation of:
-1. Transaction history between blocks 24404165-24404265
-2. Possible `unregisterRewardToken` calls
-3. Contract upgrade events
-4. Admin actions
-
-## Recommendations
-
-### Immediate Actions
-
-1. **Apply the fix** from the bug report to `src/reward/distributor/LinearReward.sol`
-2. **Test the fix** - verify the two failing tests now pass
-3. **Deploy upgrade** to the StabilityPool contract
-4. **Unregister problematic token** (0x9567c243F647f9Ac37efb7Fc26BD9551Dce0BE1B) if possible
-5. **Monitor** for any other tokens that might enter this state
-
-### Testing Checklist
-
-- [ ] Run: `forge test --match-test "test_depositReward_UnderflowBug_Line" -vv`
- - Expected: Both tests **FAIL** with panic 0x11 (before fix)
- - Expected: Both tests **PASS** (after fix)
-
-- [ ] Run mainnet fork tests:
- - `forge test --match-contract "MainnetUnderflowInvestigation" -vv`
-
-- [ ] Verify deposit works after fix applied
-
-## Files Created/Modified
-
-1. **[test/reward/distributor/LinearMultipleRewardDistributor.t.sol](test/reward/distributor/LinearMultipleRewardDistributor.t.sol)**
- - Added 6 comprehensive underflow tests (lines 689-992)
-
-2. **[test/MainnetUnderflowInvestigation.t.sol](test/MainnetUnderflowInvestigation.t.sol)** ⭐ NEW
- - Mainnet fork investigation tests
-
-3. **[test/UnderflowBugRealScenario.t.sol](test/UnderflowBugRealScenario.t.sol)** ⭐ NEW
- - Attempts to replicate without storage manipulation
-
-4. **[test/MainnetDepositFailure_RealScenario.t.sol](test/MainnetDepositFailure_RealScenario.t.sol)** ⭐ NEW
- - Tests the actual deposit failure scenario
-
-5. **[UNDERFLOW_BUG_TESTS_SUMMARY.md](UNDERFLOW_BUG_TESTS_SUMMARY.md)** ⭐ NEW
- - Comprehensive test documentation
-
-## Next Steps
-
-1. **Investigate transaction history** to understand how `finishAt` became 0
-2. **Apply the fix** to LinearReward.sol
-3. **Deploy upgrade** to mainnet
-4. **Verify fix** with mainnet fork tests
-5. **Resume user deposits**
-
----
-
-**Status**: Bug confirmed, fix identified, tests created
-**Priority**: 🔴 **CRITICAL** - User deposits completely blocked
-**Solution**: Apply safe subtraction checks from bug report
diff --git a/OVERFLOW_SCOPE_ANALYSIS.md b/OVERFLOW_SCOPE_ANALYSIS.md
deleted file mode 100644
index 01bb5fbb..00000000
--- a/OVERFLOW_SCOPE_ANALYSIS.md
+++ /dev/null
@@ -1,453 +0,0 @@
-# Overflow Issue: Scope Analysis
-
-## Executive Summary
-
-**The uint192 integral overflow issue affects ANY StabilityPool that uses the MultipleRewardCompoundingAccumulator base contract, regardless of:**
-- Number of reward tokens (1 or 2+ tokens)
-- Type of asset (BTC, ETH, or any other)
-- Which stability pool (collateral vs leveraged)
-
-**The determining factor is the RATIO**: `(reward amount × magnitude) / total deposits`
-
----
-
-## Architectural Overview
-
-### Two Stability Pools
-
-The system architecture includes **TWO** independent stability pools:
-
-1. **Collateral Stability Pool** (`stabilityPoolCollateral`)
- - Accepts collateral deposits (e.g., haBTC)
- - Used for collateral liquidations
- - Can have multiple reward tokens
-
-2. **Leveraged Stability Pool** (`stabilityPoolLeveraged`)
- - Accepts leveraged token deposits (e.g., hsBTC-fxUSD)
- - Used for leveraged position liquidations
- - Can have multiple reward tokens
-
-**Both pools use the same base contract**: `StabilityPool_v2` → `MultipleRewardCompoundingAccumulator`
-
-### Mainnet Deployment (as of block 24404265)
-
-**Primary Pool Being Tested**:
-- Address: `0x9e56F1E1E80EBf165A1dAa99F9787B41cD5bFE40`
-- Deposit Token: `haBTC` (0x25bA4A826E1A1346dcA2Ab530831dbFF9C08bEA7)
-- **Two Active Reward Tokens**:
- 1. `fxSAVE` (0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39) - **OVERFLOWING**
- 2. `hsBTC-fxUSD` (0x9567c243F647f9Ac37efb7Fc26BD9551Dce0BE1B) - Status unknown
-
----
-
-## How the Overflow Works
-
-### Per-Token Integral Storage
-
-```solidity
-// From MultipleRewardCompoundingAccumulatorStorage (line 168)
-mapping(address => mapping(uint8 => uint192)) tokenToExponentToIntegral;
-```
-
-**Key Point**: Each reward token has its **own independent integral**:
-- `tokenToExponentToIntegral[fxSAVE][0] = 5.933×10⁵⁷` (94.5% full - CRITICAL)
-- `tokenToExponentToIntegral[hsBTC-fxUSD][0] = ???` (unknown status)
-
-### The Overflow Calculation
-
-```solidity
-function _accumulateReward(address token, uint256 amount) {
- // ...
- uint256 toAdd = Math.mulDiv(amountScaled, magnitude, totalShare);
- integral += uint192(toAdd); // ← Overflow happens here
-}
-```
-
-**Formula**: `toAdd = (amount × 1e18 × magnitude) / totalShare`
-
-**For fxSAVE specifically**:
-```
-toAdd = (75×10¹⁸ × 1×10¹⁸ × 1×10³⁶) / 0.097×10¹⁸
- ≈ 7.73×10⁵⁶ per distribution
-```
-
-**Current state**:
-```
-integral[fxSAVE] = 5.933×10⁵⁷ (94.5% of uint192 max)
-Next addition = 7.73×10⁵⁶
-New value = 6.705×10⁵⁷ ← EXCEEDS 6.277×10⁵⁷ max
-Result = Panic 0x11 overflow
-```
-
----
-
-## Question 1: Does this affect pools with 1 vs 2 reward tokens?
-
-**Answer**: BOTH can be affected, but **independently per token**.
-
-### Pool with 1 Reward Token
-```solidity
-activeRewardTokens = [tokenA]
-tokenToExponentToIntegral[tokenA][0] = can overflow
-```
-
-If `tokenA` has the problematic ratio (low deposits, high rewards), it will overflow.
-
-### Pool with 2 Reward Tokens
-```solidity
-activeRewardTokens = [tokenA, tokenB]
-tokenToExponentToIntegral[tokenA][0] = can overflow
-tokenToExponentToIntegral[tokenB][0] = can overflow (independently)
-```
-
-**Each token can overflow separately**:
-- If `tokenA` (fxSAVE) has 75 tokens/week and pool has 0.097 BTC → **OVERFLOWS**
-- If `tokenB` (hsBTC-fxUSD) has 1 token/week and pool has 0.097 BTC → May be safe
-
-**Current Mainnet Situation**:
-- `fxSAVE` integral: **CRITICAL** (94.5% full, 1-2 weeks to overflow)
-- `hsBTC-fxUSD` integral: **UNKNOWN** (could be safe, could also be high)
-
-**Important**: When `depositReward()` is called for ANY token, it triggers `_accumulateReward()` for that specific token. If fxSAVE overflows, depositing fxSAVE rewards fails, but hsBTC-fxUSD rewards might still work (if not also overflowing).
-
----
-
-## Question 2: Does this only affect BTC pools?
-
-**Answer**: NO. This affects ANY pool where the deposit-to-reward ratio is unfavorable.
-
-### The Math is Asset-Agnostic
-
-The overflow depends on:
-```
-toAdd = (rewardAmount × magnitude × PRECISION²) / totalShare
-```
-
-**Variables**:
-- `rewardAmount`: Amount of reward token being distributed
-- `magnitude`: From DecrementalFloatingPoint (typically 1×10³⁶ at exponent=0)
-- `totalShare`: Total deposits in the pool
-- `PRECISION`: 1×10¹⁸
-
-**The ratio that matters**:
-```
-If (rewardAmount × magnitude) / totalShare is large → overflow risk
-```
-
-### Examples of Problematic Scenarios
-
-#### Scenario A: BTC Pool (Current Mainnet)
-```
-Total deposits: 0.097 BTC (≈ $9,700)
-Reward rate: 75 fxSAVE tokens/week
-toAdd: 7.73×10⁵⁶ per week
-Status: ❌ OVERFLOW in 1-2 weeks
-```
-
-#### Scenario B: ETH Pool (Hypothetical)
-```
-Total deposits: 0.1 ETH (≈ $400)
-Reward rate: 100 tokens/week
-toAdd: (100×10¹⁸ × 1×10³⁶) / 0.1×10¹⁸ = 1.0×10⁵⁷
-Status: ❌ OVERFLOW even faster
-```
-
-#### Scenario C: Stablecoin Pool (Hypothetical)
-```
-Total deposits: $10,000 USDC
-Reward rate: 1 token/week
-Decimals: 6 (USDC has 6 decimals!)
-totalShare: 10,000×10⁶ = 1×10¹⁰
-toAdd: (1×10¹⁸ × 1×10³⁶) / 1×10¹⁰ = 1×10⁴⁴
-Status: ❌ OVERFLOW very quickly (even worse!)
-```
-
-**Key Insight**: Assets with fewer decimals (USDC = 6, USDT = 6) are MORE susceptible because `totalShare` is smaller!
-
-#### Scenario D: Large ETH Pool (Safe)
-```
-Total deposits: 100 ETH (≈ $400,000)
-Reward rate: 100 tokens/week
-toAdd: (100×10¹⁸ × 1×10³⁶) / 100×10¹⁸ = 1×10³⁶
-Status: ✅ Safe (would take ~6×10²¹ weeks to overflow)
-```
-
----
-
-## Question 3: Does this affect both Collateral and Leveraged pools?
-
-**Answer**: YES, if deployed. Both use the same base contract.
-
-### Current Deployment Status
-
-**Known Active Pool** (from MainnetUpgradeTest.t.sol):
-- Address: `0x9e56F1E1E80EBf165A1dAa99F9787B41cD5bFE40`
-- Type: Unknown (need to verify if collateral or leveraged)
-- Deposit Token: haBTC
-- Reward Tokens: fxSAVE + hsBTC-fxUSD
-
-**Architecture Supports**:
-- `stabilityPoolCollateral` - May or may not be deployed/active
-- `stabilityPoolLeveraged` - May or may not be deployed/active
-
-### If Both Pools Are Active
-
-Each pool would have:
-- Independent `totalShare` (deposit amounts)
-- Independent reward distributions
-- Independent integral values per token
-
-**Example**:
-```
-Collateral Pool:
- - Deposits: 5 BTC ($500k)
- - Rewards: 50 tokens/week
- - toAdd: 1×10⁵⁵ per week
- - Status: ✅ Safe
-
-Leveraged Pool:
- - Deposits: 0.097 BTC ($9.7k)
- - Rewards: 75 tokens/week
- - toAdd: 7.73×10⁵⁶ per week
- - Status: ❌ OVERFLOW
-```
-
-**They would overflow independently** based on their own deposit/reward ratios.
-
----
-
-## Question 4: Are there other limitations?
-
-**Answer**: YES. Several important limitations and edge cases.
-
-### 1. Exponent-Based Fragmentation
-
-```solidity
-mapping(address => mapping(uint8 => uint192)) tokenToExponentToIntegral;
-// ↑
-// exponent changes on loss events
-```
-
-**What this means**:
-- Each loss event increments the exponent: 0 → 1 → 2 → ...
-- Each exponent has its own integral value (starts at 0)
-- **Integrals at different exponents are separate**
-
-**Implications**:
-- Overflow at exponent=0 doesn't affect exponent=1
-- A loss event "resets" the overflow problem (integral goes back to 0)
-- But you can't control when loss events occur
-- Maximum 8 exponents (limited by uint8 and SCALE_FACTOR logic)
-
-### 2. Decimal Precision Issues
-
-**Assets with Different Decimals**:
-```solidity
-// BTC (18 decimals)
-totalShare = 0.097×10¹⁸ = 9.7×10¹⁶
-
-// USDC (6 decimals)
-totalShare = 10,000×10⁶ = 1×10¹⁰ ← Much smaller denominator!
-
-// Custom token (8 decimals)
-totalShare = 100×10⁸ = 1×10¹⁰
-```
-
-**Lower decimals = Faster overflow** because `totalShare` denominator is smaller.
-
-### 3. Magnitude-Based Scaling
-
-```solidity
-uint256 magnitude = uint256(currentProd.magnitude()); // From DecrementalFloatingPoint
-uint256 toAdd = Math.mulDiv(amountScaled, magnitude, totalShare);
-```
-
-**Magnitude changes over time**:
-- Starts at 1×10³⁶ (exponent=0)
-- Decreases with loss events
-- When magnitude < 1×10²⁷, exponent increments and magnitude rescales
-
-**Implications**:
-- If magnitude decreases (losses), `toAdd` becomes smaller → slower overflow
-- If magnitude stays high (no losses), `toAdd` stays large → faster overflow
-- Current mainnet: magnitude = 1×10³⁶ (no significant losses)
-
-### 4. Reward Period Duration
-
-```solidity
-// From LinearReward.sol
-function increase(RewardData memory _data, uint256 _periodLength, uint256 _amount) {
- // ...
- _data.rate = _amount / _periodLength;
- _data.finishAt = block.timestamp + _periodLength;
-}
-```
-
-**Period length affects distribution frequency, NOT total amount**:
-- Short period (1 week): 75 tokens over 7 days
-- Long period (4 weeks): 75 tokens over 28 days
-- **Same integral growth per distribution**, just smoothed differently
-
-### 5. Queue Counter Limitations
-
-If implementing the queue solution:
-```solidity
-struct RewardData {
- uint96 queued; // ← Limited to uint96
- // ...
-}
-```
-
-**uint96 maximum**: 7.9×10²⁸ tokens
-
-**Implications**:
-- If queue grows beyond uint96 max, queue counter itself overflows
-- At 75 tokens/week: Would take 1.06×10²⁷ weeks to overflow
-- Practically unlimited, but theoretically bounded
-
-### 6. Multiple Markets
-
-```solidity
-// From deployment scripts
-function deployMinterMarket(string memory marketKey, ...) {
- // Creates separate pools per market
-}
-```
-
-**Each market has separate pools**:
-- Market A: BTC/fxUSD with 2 stability pools
-- Market B: ETH/fxETH with 2 stability pools
-- Each pool has independent integrals
-
-**Scope**: This overflow issue must be considered **for every pool in every market**.
-
-### 7. Reward Token Diversity
-
-**Different reward tokens accumulate differently**:
-```solidity
-// Token A: High distribution rate
-depositReward(tokenA, 1000 ether) // Large amounts
-→ integral[tokenA] grows quickly
-
-// Token B: Low distribution rate
-depositReward(tokenB, 1 ether) // Small amounts
-→ integral[tokenB] grows slowly
-```
-
-**Implication**: In a 2-token system, one token might overflow while the other is fine.
-
-### 8. User Distribution Limits
-
-**User reward snapshots also use uint192**:
-```solidity
-struct RewardSnapshot {
- uint64 timestamp;
- uint192 integral; // ← User's checkpoint
-}
-```
-
-**If global integral overflows**:
-- New users can't create snapshots (deposit fails)
-- Existing users can't update snapshots (withdraw/claim fails)
-- **Everyone is blocked**, not just new operations
-
----
-
-## Summary Table: What's Affected?
-
-| Factor | Affected? | Why |
-|--------|-----------|-----|
-| **Pools with 1 reward token** | ✅ Yes | Each token has own integral |
-| **Pools with 2+ reward tokens** | ✅ Yes | Each token independently |
-| **BTC pools** | ✅ Yes | Current mainnet case |
-| **ETH pools** | ✅ Yes | Same math applies |
-| **Stablecoin pools** | ✅ Yes (worse!) | Fewer decimals = faster overflow |
-| **Collateral stability pool** | ✅ Yes | Uses same base contract |
-| **Leveraged stability pool** | ✅ Yes | Uses same base contract |
-| **All markets** | ✅ Yes | Each pool independent |
-| **Low deposit pools** | ❌❌❌ CRITICAL | Small denominator → large toAdd |
-| **High reward pools** | ❌❌❌ CRITICAL | Large numerator → large toAdd |
-| **Pools at exponent > 0** | ⚠️ Depends | Resets integral, but can overflow again |
-
----
-
-## Critical Determining Factors
-
-**A pool will overflow if**:
-```
-(rewardAmount × 1e18 × magnitude) / totalShare > remaining integral headroom
-```
-
-**Vulnerable pools**:
-1. ❌ Low deposits (< $50k equivalent)
-2. ❌ High reward rates (> 10 tokens/week)
-3. ❌ No recent loss events (exponent=0, integral accumulated)
-4. ❌ Low decimal assets (USDC, USDT = 6 decimals)
-5. ❌ Long time since last overflow/reset
-
-**Safe pools**:
-1. ✅ High deposits (> $1M equivalent)
-2. ✅ Low reward rates (< 1 token/week)
-3. ✅ Recent loss events (integral recently reset)
-4. ✅ High decimal assets (18 decimals)
-
----
-
-## Recommended Actions
-
-### Immediate
-1. **Check ALL deployed pools** for current integral values:
- - Collateral pool (if active)
- - Leveraged pool (if active)
- - All reward tokens in each pool
- - All markets
-
-2. **Identify at-risk pools**: Any with integral > 80% of uint192 max
-
-3. **Prioritize by time-to-overflow**:
- - Critical: < 2 weeks
- - High: 2-8 weeks
- - Medium: 2-6 months
- - Low: > 6 months
-
-### Per Pool Analysis Needed
-
-For each pool, calculate:
-```javascript
-const currentIntegral = await pool.tokenToExponentToIntegral(rewardToken, exponent);
-const totalShare = await pool.totalAssetSupply();
-const rewardRate = await pool.rewardData(rewardToken).rate;
-const magnitude = /* from pool state */;
-
-const toAdd = (rewardRate * 1e18 * magnitude) / totalShare;
-const headroom = (6.277e57 - currentIntegral);
-const weeksToOverflow = headroom / toAdd;
-
-console.log(`Pool will overflow in ${weeksToOverflow} weeks`);
-```
-
-### Long-term Solution
-
-**The queueing mechanism works for ALL cases**:
-- Works with 1 or multiple reward tokens
-- Works with any asset type
-- Works for both collateral and leveraged pools
-- Automatically clears when deposits increase OR loss events occur
-
-**Deploy once, protects all pools**.
-
----
-
-## Conclusion
-
-**This is NOT limited to**:
-- ❌ 2-token pools (affects 1-token too)
-- ❌ BTC pools (affects ANY asset)
-- ❌ One specific pool (affects ALL pools)
-
-**This IS a systemic issue affecting**:
-- ✅ ALL StabilityPool contracts using MultipleRewardCompoundingAccumulator
-- ✅ Each reward token independently
-- ✅ Any pool with unfavorable deposit/reward ratio
-
-**Immediate priority**: Check ALL deployed pools across ALL markets for current integral values.
diff --git a/README.md b/README.md
index 6097d5da..4067934d 100644
--- a/README.md
+++ b/README.md
@@ -104,7 +104,7 @@ This provides a gas efficient and general pause mechanism for all UUPS upgradeab
### Reserve pool
-Provides discounts for collateral ration beneficial user actions.
+Provides discounts for collateral ratio beneficial user actions.
The reserve pool is funded by a portion of the fees collected, and can be filled by other mechanisms, e.g. simply transferring the collateral token to it.
## Anchored haTokens - the pegged tokens
diff --git a/SOLUTION_ANALYSIS.md b/SOLUTION_ANALYSIS.md
deleted file mode 100644
index 49131593..00000000
--- a/SOLUTION_ANALYSIS.md
+++ /dev/null
@@ -1,410 +0,0 @@
-# Stability Pool Overflow Issue - Solution Analysis
-
-## Executive Summary
-
-The Stability Pool's reward accounting system will overflow within 1-2 reward distributions due to:
-- **Root Cause**: Very small deposit base (0.097 BTC ≈ $9,700) relative to reward amounts (75 tokens/week)
-- **Immediate Impact**: All operations (deposit, withdraw, claim) will fail with Panic 0x11
-- **Recommended Solution**: Queue rewards when overflow would occur, resume when deposits increase
-
----
-
-## Current State (Mainnet)
-
-### Pool Metrics
-- **Total Deposits**: 0.097 BTC (≈ $9,700 at $100k/BTC)
-- **Reward Token**: fxSAVE (0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39)
-- **Distribution Rate**: ~75 tokens per week
-- **Integral Value**: 5.933×10⁵⁷ (94.5% of uint192 maximum)
-- **Exponent**: 0 (no significant loss events)
-
-### Critical Threshold
-- **uint192 Maximum**: 6.277×10⁵⁷
-- **Next Distribution Impact**: +7.73×10⁵⁶
-- **Result**: **Exceeds maximum by 4.28×10⁵⁶** → Panic 0x11 overflow
-
-### Time to Failure
-- **Estimated**: 1-2 reward distributions from now
-- **Once Failed**: ALL pool operations break (deposits, withdrawals, claims)
-
----
-
-## Solution 2: Queue Rewards (Recommended)
-
-### How It Works
-
-```solidity
-// When integral would overflow:
-if (newIntegral > uint192.max) {
- // Instead of reverting, queue the rewards
- rewardData[token].queued += amount;
- emit RewardQueuedDueToIntegralOverflow(token, exponent, amount);
- return; // Don't accumulate yet
-}
-```
-
-**Key Points:**
-- Rewards are **not lost**, just queued
-- Operations continue working (deposit, withdraw, claim other tokens)
-- Rewards distribute automatically once conditions improve
-
-### What Needs to Happen to Unblock
-
-The queue clears when **EITHER**:
-1. **More deposits arrive** (increases totalShare, reduces integral growth rate)
-2. **A loss event occurs** (increments exponent, resets integral to 0)
-
----
-
-## Deposit Requirements Analysis
-
-### Current Calculation
-```
-Integral Growth = (reward × 1×10¹⁸ × magnitude) / totalShare
- = (75×10¹⁸ × 1×10¹⁸ × 1×10³⁶) / 0.097×10¹⁸
- ≈ 7.73×10⁵⁶ per distribution
-```
-
-### Safe Threshold
-To leave 50% headroom (integral can grow to ~3×10⁵⁷ more):
-```
-Need: toAdd < 0.3×10⁵⁷ per distribution
-
-Required totalShare:
-totalShare = (75×10¹⁸ × 1×10¹⁸ × 1×10³⁶) / 0.3×10⁵⁷
- = 2.5×10¹⁸
- = 2.5 BTC
-```
-
-### Additional Deposits Needed
-
-| Current | Required | Additional Needed | USD Value (@$100k/BTC) |
-|---------|----------|-------------------|------------------------|
-| 0.097 BTC | 2.5 BTC | **2.4 BTC** | **$240,000** |
-
-At different BTC prices:
-- **@ $95k/BTC**: $228,000
-- **@ $90k/BTC**: $216,000
-- **@ $80k/BTC**: $192,000
-
----
-
-## Impact Scenarios
-
-### Scenario 1: No Action Taken (Current Code)
-
-**Timeline:**
-- **Week 0** (now): Operations work
-- **Week 1**: Next distribution → **OVERFLOW** → ALL OPERATIONS FAIL
-- **Ongoing**: Pool completely frozen
-
-**User Impact:**
-- ❌ Cannot deposit new funds
-- ❌ Cannot withdraw existing funds
-- ❌ Cannot claim any rewards (all tokens)
-- ⚠️ Funds are safe but inaccessible
-
-**Business Impact:**
-- Complete loss of pool functionality
-- User support burden
-- Reputational damage
-- Emergency upgrade required under pressure
-
----
-
-### Scenario 2: Implement Queue Solution (Recommended)
-
-#### 2A: No New Deposits
-
-**Timeline:**
-- **Week 1**: Queue starts, 75 tokens queued
-- **Week 2**: 150 tokens queued (cumulative)
-- **Week 3**: 225 tokens queued
-- **Ongoing**: Queue grows indefinitely
-
-**User Impact:**
-- ✅ Can deposit new funds
-- ✅ Can withdraw existing funds
-- ✅ Can claim other reward tokens (if any)
-- ⚠️ **fxSAVE rewards stop accumulating** (APY = 0% for fxSAVE)
-- 📊 Can monitor queue via events
-
-**Business Impact:**
-- Pool remains functional for deposits/withdrawals
-- Reduced APY visible to users (transparency issue)
-- Need communication about temporarily paused rewards
-- Queue can be monitored off-chain
-
-#### 2B: Moderate Deposits (1 BTC over 4 weeks)
-
-**Assumptions:**
-- Deposits increase gradually: 0.097 → 1.1 BTC
-- Distribution continues at 75 tokens/week
-
-**Analysis:**
-```
-Week 1: totalShare = 0.35 BTC
- → toAdd = 2.14×10⁵⁷ (still too high, queue 75 tokens)
-
-Week 2: totalShare = 0.60 BTC
- → toAdd = 1.25×10⁵⁷ (still too high, queue 150 tokens total)
-
-Week 3: totalShare = 0.85 BTC
- → toAdd = 0.88×10⁵⁷ (still too high, queue 225 tokens total)
-
-Week 4: totalShare = 1.10 BTC
- → toAdd = 0.68×10⁵⁷ (SAFE! but integral near max)
- → Can distribute queued + new: 300 tokens
- → Integral += 2.04×10⁵⁷ → OVERFLOW AGAIN!
-```
-
-**Conclusion**: Partial deposits delay but don't solve the problem.
-
-#### 2C: Sufficient Deposits (2.5 BTC total)
-
-**Assumptions:**
-- Large deposit brings totalShare to 2.5 BTC
-- 3 weeks of queued rewards: 225 tokens
-
-**Analysis:**
-```
-Week 4: totalShare = 2.5 BTC
- → toAdd for 75 tokens = 0.30×10⁵⁷ (SAFE!)
-
- Distribute queued + new: 300 tokens
- → integral += 1.2×10⁵⁷
- → New integral = 7.13×10⁵⁷
-
- PROBLEM: 7.13×10⁵⁷ > 6.28×10⁵⁷ max → STILL OVERFLOWS!
-```
-
-**Better approach**: Gradually distribute queued rewards
-```
-Week 4: Distribute 75 tokens (1 week worth)
- → integral += 0.30×10⁵⁷ → 6.23×10⁵⁷ (OK)
-Week 5: Distribute 75 + 75 queued
- → integral += 0.60×10⁵⁷ → 6.83×10⁵⁷ (OVERFLOW)
-```
-
-**Conclusion**: Even with 2.5 BTC, must distribute queued rewards slowly OR need even more deposits.
-
-#### 2D: Optimal Deposits (5+ BTC total)
-
-**Analysis:**
-```
-totalShare = 5 BTC
-toAdd for 75 tokens = 0.15×10⁵⁷
-
-Can safely distribute large batches:
-- 300 tokens (4 weeks queued): 0.60×10⁵⁷ → Total: 6.53×10⁵⁷ (OK!)
-- Or distribute all queued gradually over time
-```
-
----
-
-## Loss Event Alternative
-
-### What is a Loss Event?
-When the pool experiences a liquidation loss, the `magnitude` drops. If magnitude falls below 1×10²⁷, the system:
-1. Increments `exponent`: 0 → 1
-2. Resets `integral[exponent=1]` to 0
-3. Continues accumulating at new exponent
-
-### Impact if Loss Occurs
-- ✅ Integral resets to 0 at new exponent
-- ✅ Overflow problem solved immediately
-- ✅ Queued rewards can be distributed
-- ⚠️ Users at old exponent get rewards via aggregation (complex but works)
-
-### Likelihood
-- **Cannot control**: Loss events depend on external liquidations
-- **Not desirable**: Losses hurt users
-- **Cannot rely on**: May not happen in time
-
----
-
-## Comparison Table
-
-| Criteria | Do Nothing | Queue (No Deposits) | Queue (2.5 BTC) | Queue (5 BTC) |
-|----------|------------|---------------------|-----------------|---------------|
-| **Deposits Work** | ❌ Broken | ✅ Yes | ✅ Yes | ✅ Yes |
-| **Withdrawals Work** | ❌ Broken | ✅ Yes | ✅ Yes | ✅ Yes |
-| **fxSAVE Rewards** | ❌ None | ❌ Paused | ⚠️ Slow Resume | ✅ Full Resume |
-| **User Experience** | 🔴 Critical | 🟡 Degraded | 🟡 Degraded | 🟢 Good |
-| **Additional Deposits Needed** | N/A | None | $240k | $500k |
-| **Risk Level** | 🔴 High | 🟡 Medium | 🟢 Low | 🟢 Low |
-| **Implementation** | Current | Simple | Simple | Simple |
-| **Reversible** | No | Yes | Yes | Yes |
-
----
-
-## Recommended Action Plan
-
-### Phase 1: Immediate (This Upgrade)
-1. ✅ Fix finishAt=0 underflow bug (already done)
-2. ✅ Implement queue-on-overflow logic
-3. ✅ Add monitoring events
-4. ✅ Deploy upgrade
-
-**Result**: Pool continues functioning, rewards pause for fxSAVE
-
-### Phase 2: Communication (Week 1)
-1. Notify users about temporarily paused fxSAVE rewards
-2. Explain that funds are safe and accessible
-3. Encourage deposits with messaging:
- - "Depositing helps restore reward distribution"
- - "Queued rewards will be distributed once deposits reach threshold"
-4. Provide transparent queue status dashboard
-
-### Phase 3: Monitoring (Ongoing)
-1. Track queue growth via events
-2. Monitor deposit levels
-3. Watch for loss events (exponent changes)
-4. Communicate progress to users
-
-### Phase 4: If Deposits Don't Arrive
-**Options:**
-1. **Incentivize deposits** with bonus rewards
-2. **Reduce fxSAVE distribution rate** (requires governance)
-3. **Add secondary rewards** in different token
-4. **Accept paused state** until natural loss event
-
----
-
-## Risk Assessment
-
-### Queue Solution Risks
-
-**Low Risk:**
-- ✅ Existing code already has queue mechanism (line 503)
-- ✅ No storage changes required
-- ✅ UUPS-safe upgrade
-- ✅ Reversible logic
-
-**Medium Risk:**
-- ⚠️ User perception: "Why did my rewards stop?"
-- ⚠️ Requires good communication
-- ⚠️ Depends on future deposits or loss events
-
-**Mitigations:**
-- Clear communication strategy
-- Transparent queue status
-- User education about integral mechanism
-- Incentive programs for deposits
-
-### Alternative Solutions Comparison
-
-| Solution | Storage Risk | User Impact | Complexity | Success Probability |
-|----------|--------------|-------------|------------|---------------------|
-| **Queue** | 🟢 None | 🟡 Rewards pause | 🟢 Low | 🟢 High (if deposits arrive) |
-| **Epoch Reset** | 🟡 New storage | 🟢 Rewards continue | 🔴 High | 🟢 High (but you removed epochs) |
-| **Cap at Max** | 🟢 None | 🔴 Unfair distribution | 🟢 Low | 🔴 Low (unfair to users) |
-| **Graceful Revert** | 🟢 None | 🔴 Blocks operations | 🟢 Low | 🔴 Low (same as do nothing) |
-
----
-
-## Code Changes Required
-
-### Minimal Change (Solution 2)
-
-```solidity
-function _accumulateReward(address token, uint256 amount) internal virtual override {
- if (amount == 0) return;
-
- (uint128 currentProd, uint256 totalShare) = _getTotalPoolShare();
-
- if (totalShare == 0) {
- _getRewardData(token).queued += uint96(amount);
- return;
- }
-
- uint8 exponent = currentProd.exponent();
- uint256 magnitude = uint256(currentProd.magnitude());
-
- MultipleRewardCompoundingAccumulatorStorage storage $ =
- _getMultipleRewardCompoundingAccumulatorStorage();
- uint192 integral = $.tokenToExponentToIntegral[token][exponent];
-
- uint256 amountScaled = amount * _REWARD_PRECISION;
- uint256 toAdd = Math.mulDiv(amountScaled, magnitude, totalShare);
-
- // NEW: Check if adding would overflow
- uint256 newIntegral = uint256(integral) + toAdd;
-
- if (newIntegral > type(uint192).max) {
- // Queue instead of accumulating
- _getRewardData(token).queued += uint96(amount);
- emit RewardQueuedDueToIntegralOverflow(token, exponent, amount, toAdd);
- return;
- }
-
- // Safe to accumulate
- integral = uint192(newIntegral);
- $.tokenToExponentToIntegral[token][exponent] = integral;
-}
-```
-
-**Lines changed**: ~10 lines
-**New event**: 1
-**Storage changes**: 0
-**Risk**: Very low
-
----
-
-## Monitoring & Observability
-
-### Events to Add
-
-```solidity
-event RewardQueuedDueToIntegralOverflow(
- address indexed token,
- uint8 indexed exponent,
- uint256 amount,
- uint256 toAdd
-);
-```
-
-### Metrics to Track
-
-1. **Queue Size**: `rewardData[token].queued`
-2. **Total Deposits**: `totalAssetSupply.amount`
-3. **Integral Value**: `tokenToExponentToIntegral[token][exponent]`
-4. **Percentage of Max**: `(integral * 100) / type(uint192).max`
-
-### Dashboard Queries
-
-```javascript
-// Check if rewards are queued
-const queued = await stabilityPool.rewardData(fxSAVE).queued;
-
-// Check current deposits
-const totalDeposits = await stabilityPool.totalAssetSupply();
-
-// Calculate how much more needed
-const needed = calculateRequiredDeposits(queued, totalDeposits);
-```
-
----
-
-## Conclusion
-
-**Recommended**: Implement Solution 2 (Queue) with:
-- Clear user communication
-- Transparent monitoring
-- Incentive program for deposits if needed
-
-**Why**:
-- ✅ Safest for UUPS upgrade
-- ✅ Preserves pool functionality
-- ✅ Reversible if better solution found
-- ✅ Self-healing if deposits arrive
-- ✅ Works with natural loss events
-
-**Trade-off**:
-- Users experience paused fxSAVE rewards
-- Depends on future deposits or loss events
-- Requires good communication
-
-**Alternative if unacceptable**:
-- Reduce fxSAVE distribution rate via governance
-- Or accept risk and implement epoch system (complex)
diff --git a/UNDERFLOW_BUG_TESTS_SUMMARY.md b/UNDERFLOW_BUG_TESTS_SUMMARY.md
deleted file mode 100644
index 81ec49ac..00000000
--- a/UNDERFLOW_BUG_TESTS_SUMMARY.md
+++ /dev/null
@@ -1,182 +0,0 @@
-# LinearReward Arithmetic Underflow Bug - Test Suite Summary
-
-## Overview
-This document describes the test suite created to demonstrate the arithmetic underflow vulnerability in `src/reward/distributor/LinearReward.sol`.
-
-## Bug Location
-The bug exists in the `LinearReward.increase()` function at two locations:
-
-### Line 48 - periodStart Calculation Underflow
-```solidity
-// BUGGY CODE:
-uint256 _elapsed = block.timestamp - (_data.finishAt - _periodLength);
-
-// BUG CONDITION:
-// When finishAt < periodLength, (_data.finishAt - _periodLength) underflows
-// OR when (finishAt - periodLength) > block.timestamp, the outer subtraction underflows
-```
-
-### Line 52 - Time Since Last Update Underflow
-```solidity
-// BUGGY CODE:
-_amount = _amount + uint256(_data.rate) * (_data.finishAt - _data.lastUpdate);
-
-// BUG CONDITION:
-// When finishAt < lastUpdate, (_data.finishAt - _data.lastUpdate) underflows
-```
-
-## Test Suite
-
-### Tests That FAIL (Demonstrating the Bug)
-
-These tests deliberately create conditions that trigger the underflow bugs. They will **revert with panic 0x11** when run against the buggy code, and **pass** when run against the fixed code.
-
-#### 1. `test_depositReward_UnderflowBug_Line48_BlockTimestampTooLow()`
-**Location:** [test/reward/distributor/LinearMultipleRewardDistributor.t.sol](test/reward/distributor/LinearMultipleRewardDistributor.t.sol)
-
-**What it does:**
-- Creates a reward period with normal initial deposit
-- Uses `vm.store()` to manipulate `finishAt` to be unusually high: `finishAt = block.timestamp + periodLength + 10000`
-- This creates the condition: `block.timestamp < (finishAt - periodLength)`
-- Attempts another deposit, which triggers the else branch
-- The buggy line 48 tries to calculate: `block.timestamp - (finishAt - periodLength)`
-- Since `(finishAt - periodLength) = block.timestamp + 10000 > block.timestamp`, this underflows
-
-**Expected Result:**
-- ❌ **FAILS with panic 0x11** (arithmetic underflow) on buggy code
-- ✅ **PASSES** on fixed code (handles the edge case gracefully)
-
-#### 2. `test_depositReward_UnderflowBug_Line52_FinishAtLessThanLastUpdate()`
-**Location:** [test/reward/distributor/LinearMultipleRewardDistributor.t.sol](test/reward/distributor/LinearMultipleRewardDistributor.t.sol)
-
-**What it does:**
-- Creates a reward period with normal initial deposit
-- Uses `vm.store()` to manipulate `lastUpdate` to be greater than `finishAt`
-- Specifically: `lastUpdate = finishAt + 5000`
-- Warps time to be before `finishAt` to enter the else branch
-- Deposits a large amount (100,000 ether) to trigger the distribute logic
-- The buggy line 52 tries to calculate: `(_data.finishAt - _data.lastUpdate)`
-- Since `finishAt < lastUpdate`, this underflows
-
-**Expected Result:**
-- ❌ **FAILS with panic 0x11** (arithmetic underflow) on buggy code
-- ✅ **PASSES** on fixed code (handles the edge case gracefully)
-
-#### 3. `test_depositReward_UnderflowBugDemonstration()`
-**Location:** [test/reward/distributor/LinearMultipleRewardDistributor_v2.t.sol](test/reward/distributor/LinearMultipleRewardDistributor_v2.t.sol)
-
-**What it does:**
-- Similar to test #1, demonstrates the line 48 underflow
-- Uses storage manipulation to create the underflow condition
-- Comprehensive test with detailed validation
-
-**Expected Result:**
-- ❌ **FAILS with panic 0x11** (arithmetic underflow) on buggy code
-- ✅ **PASSES** on fixed code
-
-### Tests That PASS (Testing the Fix)
-
-These tests demonstrate scenarios where the fix is needed and verify that the fixed code handles them correctly.
-
-#### 4. `test_depositReward_UnderflowFix()`
-**Location:** [test/reward/distributor/LinearMultipleRewardDistributor.t.sol](test/reward/distributor/LinearMultipleRewardDistributor.t.sol)
-
-**What it does:**
-- Tests normal deposit scenario where rewards are deposited
-- Time advances (but not past finishAt)
-- Then more rewards are deposited
-- Validates that the deposit succeeds and state is updated correctly
-
-**Purpose:** Ensures basic functionality works with the fix applied
-
-#### 5. `test_depositReward_ExtremeUnderflowFix()`
-**Location:** [test/reward/distributor/LinearMultipleRewardDistributor.t.sol](test/reward/distributor/LinearMultipleRewardDistributor.t.sol)
-
-**What it does:**
-- Tests edge case where `finishAt` might be less than `periodLength`
-- Starts at a very small timestamp (half the period length)
-- Makes deposits and verifies they succeed
-- Tests the scenario that can occur in forked mainnet environments
-
-**Purpose:** Validates the fix handles extreme edge cases with small timestamps
-
-#### 6. `test_depositReward_AfterPeriodFinished()`
-**Location:** [test/reward/distributor/LinearMultipleRewardDistributor.t.sol](test/reward/distributor/LinearMultipleRewardDistributor.t.sol)
-
-**What it does:**
-- Deposits rewards, then warps 2 weeks ahead (past finishAt)
-- Deposits again to start a new period
-- Tests the `if (block.timestamp >= finishAt)` branch
-
-**Purpose:** Ensures normal period completion and new period start works correctly
-
-#### 7. `test_depositReward_AfterPeriodFinishedThenBeforeFinishAt()`
-**Location:** [test/reward/distributor/LinearMultipleRewardDistributor.t.sol](test/reward/distributor/LinearMultipleRewardDistributor.t.sol)
-
-**What it does:**
-- Comprehensive test with multiple phases:
- 1. Initial deposit
- 2. Warp past finishAt and deposit again (new period)
- 3. Warp forward but NOT past the new finishAt
- 4. Deposit again (tests the else branch with potential underflow)
-- Validates state consistency throughout
-
-**Purpose:** Comprehensive test of the full reward lifecycle with the fix
-
-## Running the Tests
-
-### Run All Underflow Bug Tests
-```bash
-forge test --match-test "test_depositReward_UnderflowBug_Line" -vv
-```
-
-**Expected Output:**
-```
-[FAIL: panic: arithmetic underflow or overflow (0x11)] test_depositReward_UnderflowBug_Line48_BlockTimestampTooLow()
-[FAIL: panic: arithmetic underflow or overflow (0x11)] test_depositReward_UnderflowBug_Line52_FinishAtLessThanLastUpdate()
-```
-
-### Run All depositReward Tests
-```bash
-forge test --match-test "test_depositReward_" --match-contract "LinearMultipleRewardDistributorTest" -vv
-```
-
-## How the Tests Demonstrate the Bug
-
-1. **Storage Manipulation:** The tests use Foundry's `vm.store()` to directly manipulate contract storage, creating edge case conditions that would be difficult to reach through normal operations but can occur in forked environments or with timestamp manipulation.
-
-2. **Specific Conditions:** Each test creates the exact conditions needed to trigger the specific underflow:
- - Test #1: Creates `block.timestamp < (finishAt - periodLength)`
- - Test #2: Creates `finishAt < lastUpdate`
-
-3. **Panic 0x11:** When the bug is triggered, Solidity reverts with `panic: arithmetic underflow or overflow (0x11)`, which is caught by the test framework.
-
-4. **Fix Validation:** After applying the fix, these same tests should pass, demonstrating that the fix handles the edge cases correctly.
-
-## The Fix
-
-The fix adds safe subtraction checks before performing arithmetic:
-
-### Line 48-50 (Fixed):
-```solidity
-// Safe periodStart calculation
-uint256 periodStart = _data.finishAt >= _periodLength ? _data.finishAt - _periodLength : 0;
-uint256 _elapsed = block.timestamp >= periodStart ? block.timestamp - periodStart : 0;
-```
-
-### Line 52-54 (Fixed):
-```solidity
-// Safe time since last update calculation
-uint256 timeSinceLastUpdate = _data.finishAt >= _data.lastUpdate ? _data.finishAt - _data.lastUpdate : 0;
-_amount = _amount + uint256(_data.rate) * timeSinceLastUpdate;
-```
-
-## Summary
-
-This test suite provides:
-- ✅ **2 tests that fail on buggy code** (demonstrating the bug exists)
-- ✅ **4 tests that validate the fix** (ensuring correct behavior)
-- ✅ **Comprehensive coverage** of edge cases
-- ✅ **Clear documentation** of what each test does
-
-When the fix is applied, all tests should pass, confirming that the underflow vulnerability has been resolved.
diff --git a/UPGRADE_TEST_SUMMARY.md b/UPGRADE_TEST_SUMMARY.md
deleted file mode 100644
index ad403483..00000000
--- a/UPGRADE_TEST_SUMMARY.md
+++ /dev/null
@@ -1,119 +0,0 @@
-# Mainnet Upgrade Test Summary
-
-## What We've Accomplished
-
-### 1. ✅ Applied the Complete Fix
-
-**File**: `src/reward/distributor/LinearReward.sol`
-
-**Changes Made**:
-- Lines 48-50: Safe subtraction for `periodStart` and `_elapsed` calculations
-- Line 54: Safe subtraction for `timeSinceLastUpdate`
-- Lines 79-80: Safe subtraction in `pending()` function's else branch
-
-**All subtractions now use ternary checks**: `a >= b ? a - b : 0`
-
-### 2. ✅ All Underflow Tests Pass
-
-- `test_depositReward_UnderflowBug_Line48_BlockTimestampTooLow()` ✅ PASS
-- `test_depositReward_UnderflowBug_Line52_FinishAtLessThanLastUpdate()` ✅ PASS
-- `test_DepositAfterCreatingProblematicState()` ✅ PASS
-- `test_pending_WithFinishAtZero()` ✅ PASS
-- `test_increase_WithFinishAtZero()` ✅ PASS
-
-### 3. ✅ Root Cause Identified
-
-**Found**: Token 1 (`0x9567c243F647f9Ac37efb7Fc26BD9551Dce0BE1B`) was registered but never received deposits.
-- Every time Token 0 received deposits, `_distributePendingReward()` updated Token 1's `lastUpdate`
-- But `increase()` was only called for Token 0, so Token 1's `finishAt` stayed 0
-- Result: `lastUpdate: 1769846711, finishAt: 0`
-
-## Issue: Mainnet Upgrade Test Fails
-
-### The Problem
-
-When we:
-1. Fork mainnet at block 24404265
-2. Deploy new StabilityPool_v2 implementation with the fix
-3. Upgrade the proxy to the new implementation
-4. Try to deposit
-
-**Result**: Still fails with panic 0x11 (arithmetic underflow)
-
-### Tests Created
-
-1. **[test/MainnetUpgradeTest.t.sol](test/MainnetUpgradeTest.t.sol)**
- - Comprehensive 7-test suite
- - Tests both user deposit and depositReward failures
- - Tests upgrade process
- - ❌ test_7_CompleteEndToEnd_Test() FAILS after upgrade
-
-2. **[test/SimpleUpgradeTest.t.sol](test/SimpleUpgradeTest.t.sol)**
- - Minimal upgrade test
- - ❌ FAILS after upgrade
-
-3. **[test/TestDepositAfterFinishAtZero.t.sol](test/TestDepositAfterFinishAtZero.t.sol)**
- - Creates problematic state in fresh deployment
- - Tests deposit after creating state
- - ✅ PASSES - deposits work fine!
-
-4. **[test/TestLinearRewardFix.t.sol](test/TestLinearRewardFix.t.sol)**
- - Direct library function tests
- - ✅ PASSES - `pending()` and `increase()` work correctly
-
-### Why This Is Confusing
-
-The fix **demonstrably works** in our tests:
-- We can create the exact mainnet state (`finishAt=0, lastUpdate>0`)
-- We can successfully deposit after creating that state
-- The library functions handle the edge cases correctly
-
-But when we **upgrade the actual mainnet proxy**, deposits still fail.
-
-### Possible Explanations
-
-1. **UUPS Upgrade Issue**: Something about how UUPS proxies delegate to new implementations
-2. **Mainnet State Difference**: There's something about the actual mainnet state we're not replicating
-3. **Compilation/Linking**: The library code isn't being properly inlined in the test environment
-4. **Hidden Underflow**: There's another subtraction somewhere we haven't found yet
-
-### What's Different?
-
-| Working Tests | Failing Mainnet Upgrade |
-|---|---|
-| Fresh MockLinearMultipleRewardDistributor deployment | Forked mainnet ERC1967 Proxy |
-| Two reward tokens (token0, token1) | Two reward tokens (same concept) |
-| Create problematic state, then deposit | State already exists, then upgrade, then deposit |
-| Uses test mocks | Uses actual mainnet contracts |
-
-## Next Steps / Recommendations
-
-### Option 1: Manual Investigation
-- Use Foundry's `forge inspect` to compare bytecode
-- Add console.log statements throughout the code path
-- Test upgrade on a local fork with more detailed tracing
-
-### Option 2: Alternative Approach
-- Apply fix and test on a testnet first
-- Deploy to production and monitor
-- Have rollback plan ready
-
-### Option 3: Simpler Fix
-- Instead of upgrading, unregister Token 1 if possible
-- This removes the problematic token from active tokens
-- Then deposits should work without upgrade
-
-## Files Modified
-
-- ✅ [src/reward/distributor/LinearReward.sol](src/reward/distributor/LinearReward.sol) - Applied fix
-- ✅ [test/MainnetUpgradeTest.t.sol](test/MainnetUpgradeTest.t.sol) - Comprehensive upgrade tests
-- ✅ [test/SimpleUpgradeTest.t.sol](test/SimpleUpgradeTest.t.sol) - Minimal upgrade test
-- ✅ [test/TestDepositAfterFinishAtZero.t.sol](test/TestDepositAfterFinishAtZero.t.sol) - Proof fix works
-- ✅ [test/TestLinearRewardFix.t.sol](test/TestLinearRewardFix.t.sol) - Library function tests
-- ✅ [FINISHAT_ZERO_ROOT_CAUSE_FOUND.md](FINISHAT_ZERO_ROOT_CAUSE_FOUND.md) - Root cause analysis
-
-## Summary
-
-**The fix is correct and works** - all our tests prove this. However, there's something about upgrading the actual mainnet proxy that causes issues we haven't been able to replicate or diagnose in the test environment.
-
-**Recommendation**: The fix should be deployed to production, but the mainnet upgrade test failure suggests we need further investigation before deploying to mainnet. Consider testing on a testnet environment that more closely matches mainnet first.
diff --git a/WHY_FINISHAT_IS_ZERO.md b/WHY_FINISHAT_IS_ZERO.md
deleted file mode 100644
index 754395c5..00000000
--- a/WHY_FINISHAT_IS_ZERO.md
+++ /dev/null
@@ -1,162 +0,0 @@
-# Why finishAt is Zero - Investigation Report
-
-## Executive Summary
-
-**Finding**: Token 1 (`0x9567c243F647f9Ac37efb7Fc26BD9551Dce0BE1B`) has `finishAt = 0` **since its inception**, spanning at least **20,000+ blocks** (block 24300000 to 24404265, ~67 hours).
-
-## Timeline of Observations
-
-### Consistent State Across All Checked Blocks
-
-| Block | Timestamp | lastUpdate | finishAt | Notes |
-|-------|-----------|------------|----------|-------|
-| 24300000 | 1769153363 | 1769153363 | **0** | Earliest checked |
-| 24320000 | 1769315855 | 1769315855 | **0** | lastUpdate updated |
-| 24340000 | 1769608823 | 1769608823 | **0** | lastUpdate updated |
-| 24350000 | 1769608823 | 1769608823 | **0** | (same) |
-| 24355000 | 1769846711 | 1769846711 | **0** | lastUpdate updated |
-| 24359000 | 1769846711 | 1769846711 | **0** | (same) |
-| 24390000 | 1770286895 | 1769846711 | **0** | 7 days after lastUpdate |
-| 24404265 | 1770459107 | 1769846711 | **0** | **Current problematic block** |
-
-### Key Observations
-
-1. ✅ **Token IS registered** - appears in active tokens list throughout
-2. ✅ **Rewards WERE deposited** - `lastUpdate` changes at least 4 times
-3. ❌ **finishAt NEVER set** - remains 0 across all blocks
-4. ✅ **`rate: 0` and `queued: 0`** - consistently throughout
-
-## The Mystery
-
-### Normal `increase()` Behavior
-
-When rewards are deposited via `LinearReward.increase()`:
-
-```solidity
-// src/reward/distributor/LinearReward.sol:34-44
-function increase(RewardData memory _data, uint256 _periodLength, uint256 _amount) internal view {
- _amount = _amount + _data.queued;
- _data.queued = 0;
-
- if (block.timestamp >= _data.finishAt) {
- // NEW PERIOD - BOTH lastUpdate AND finishAt SHOULD BE SET
- _data.rate = (_amount / _periodLength).toUint80();
- _data.queued = uint96(_amount - (_data.rate * _periodLength));
- _data.lastUpdate = uint40(block.timestamp); // ✅ Sets lastUpdate
- _data.finishAt = uint40(block.timestamp + _periodLength); // ✅ Should set finishAt!
- }
-}
-```
-
-**Expected Behavior**: When `finishAt = 0`, any deposit enters the `if` branch and sets BOTH `lastUpdate` AND `finishAt`.
-
-**Actual Behavior**: `lastUpdate` is being set, but `finishAt` remains 0.
-
-## Possible Explanations
-
-### Theory 1: Zero-Amount Deposits
-If `_amount = 0` is passed to `increase()`:
-- `rate = 0 / periodLength = 0`
-- `lastUpdate = block.timestamp` ✅
-- `finishAt = block.timestamp + periodLength` ❓
-
-**Problem**: Even with `_amount = 0`, `finishAt` should still be set to `block.timestamp + periodLength`, not 0.
-
-### Theory 2: Contract State Corruption
-- Storage slot collision
-- Upgrade issues
-- Direct storage manipulation
-
-**Evidence Against**: State is consistent across 20,000+ blocks, making random corruption unlikely.
-
-### Theory 3: Custom Implementation
-The contract might have a custom `increase()` implementation that differs from the standard library.
-
-**Need to Check**:
-- Is there an override of `_notifyReward()`?
-- Is there custom logic in `depositReward()`?
-- Are there any hooks or modifiers affecting the behavior?
-
-### Theory 4: Period Length Configuration
-If somehow this specific reward token has a different period length...
-
-**Evidence**: StabilityPool constructor uses `1 weeks` for ALL tokens:
-```solidity
-// Line 197
-constructor(...) MultipleRewardCompoundingAccumulator(_REWARD_MANAGER_ROLE, _REWARD_DEPOSITOR_ROLE, 1 weeks)
-```
-
-### Theory 5: Integer Overflow/Underflow in finishAt Calculation
-If `block.timestamp + _periodLength` overflows uint40...
-
-**Evidence Against**:
-- uint40 max = 1,099,511,627,775 (year ~36,812)
-- Current timestamp ~1,770,000,000 (year 2026)
-- Adding 604,800 (1 week) won't overflow
-
-### Theory 6: The Token Was Manually Reset
-Someone with admin privileges might have manually cleared `finishAt` while leaving `lastUpdate` intact.
-
-**Need to Check**:
-- Transaction history between blocks
-- Admin function calls
-- `unregisterRewardToken` calls
-
-## Most Likely Explanation
-
-Based on the evidence, the most probable scenarios are:
-
-**Primary Hypothesis**: Token 1 was registered but configured differently, or there's custom logic that prevents `finishAt` from being set.
-
-**Secondary Hypothesis**: The reward deposits for Token 1 are **zero-amount deposits** that update `lastUpdate` but don't set a meaningful `finishAt`.
-
-## What We Need to Investigate Next
-
-1. **Check the actual transaction** that set `lastUpdate = 1769846711`
- - Block: ~24355000-24359000
- - Look for `DepositReward` events
- - Examine the transaction input data
-
-2. **Check for contract upgrades**
- - Any UUPS upgrades around these blocks?
- - Changes to the `LinearReward` library?
-
-3. **Check for admin actions**
- - `UnregisterRewardToken` calls
- - Manual state modifications
- - Configuration changes
-
-4. **Verify the reward distribution logic**
- - Is there custom logic for Token 1?
- - Different behavior for certain token types?
-
-## Impact
-
-Regardless of HOW `finishAt` became 0, the **impact is clear**:
-
-1. **Current State**: `finishAt = 0`, `lastUpdate = 1769846711`
-2. **Underflow Conditions**: Both conditions met (finishAt < periodLength, finishAt < lastUpdate)
-3. **User Impact**: Deposits to StabilityPool fail with arithmetic underflow
-4. **Solution**: Apply the fix from the bug report to handle this edge case
-
-## Conclusion
-
-While we've confirmed:
-- ✅ Token 1 has had `finishAt = 0` for a long time
-- ✅ `lastUpdate` has been updated multiple times
-- ✅ This state causes the underflow bug
-
-We still need to understand:
-- ❓ **WHY** `finishAt` remains 0 despite deposits
-- ❓ **HOW** this state was created
-
-**Recommendation**:
-1. Apply the fix immediately to unblock user deposits
-2. Continue investigating the root cause
-3. Consider additional safeguards to prevent this state in the future
-
----
-
-**Investigation Status**: Ongoing
-**Priority**: 🔴 Critical (users blocked)
-**Next Steps**: Examine transaction history for Token 1 reward deposits
diff --git a/deployments/README.md b/deployments/README.md
new file mode 100644
index 00000000..9ff97827
--- /dev/null
+++ b/deployments/README.md
@@ -0,0 +1,163 @@
+# Harbor Deployment History
+
+All deployments use the `harbor_v1` salt prefix on Ethereum mainnet via BaoFactory CREATE3.
+
+Deployment versions are tracked with annotated git tags (`deploy/X.Y.Z`). Tags are placed on the nearest commit after the on-chain deployment, since some deployments were executed from uncommitted code.
+
+State file: `deployments/mainnet/harbor_v1.state.json`
+
+---
+
+## deploy/1.0 — Initial Deployment
+
+**Date:** 2025-12-19
+**Git tag:** `deploy/1.0` → `040cd302`
+**Commit:** "added production deployment (non-predictable price oracle addresses)"
+
+Deployed the complete harbor_v1 protocol for 4 pegs with fxUSD collateral, plus BTC with stETH collateral.
+
+### Contracts
+
+| Contract | Version | Count |
+|----------|---------|-------|
+| MintableBurnableERC20_v1 | v1 | 8 (pegged + leveraged per market) |
+| Minter_v1 | v1 | 4 |
+| StabilityPool_v1 | v1 | 8 (collateral + leveraged per market) |
+| StabilityPoolManager_v1 | v1 | 4 |
+| Genesis_v1 | v1 | 4 |
+| ReservePool_v1 | v1 | 4 |
+| TokenDistributor_v1 | v1 | 8 (minter + SPM fee receivers) |
+
+### Markets
+
+| Peg | Collateral | Pegged token |
+|-----|-----------|--------------|
+| BTC | fxSAVE (fxUSD) | haBTC |
+| BTC | wstETH (stETH) | haBTC |
+| ETH | fxSAVE (fxUSD) | haETH |
+| EUR | fxSAVE (fxUSD) | haEUR |
+| GOLD | fxSAVE (fxUSD) | haGOLD |
+
+---
+
+## deploy/1.0.1 — StabilityPool Fix
+
+**Date:** 2026-01-03
+**Git tag:** `deploy/1.0.1` → `15f6e319`
+**Commit:** "corrected stability pool deployment"
+**Fix commit:** `e05850db` ("fixed the min_asset supply for SPs")
+
+The initial 1.0 deployment had an incorrect `minTotalAssetSupply` constructor parameter in the stability pools. Since this is an immutable, fixing it required deploying new StabilityPool_v1 implementations and upgrading the proxies. The same source code was used — only the constructor parameter changed.
+
+The new min asset supply was meant to be around $1 scaled to the price of the asset in USD.
+
+Deployed via `script/deploy-new-SP` bash script.
+
+### Markets affected
+
+- BTC::fxUSD (collateral + leveraged)
+- BTC::stETH (collateral + leveraged)
+- ETH::fxUSD (collateral + leveraged)
+- GOLD::fxUSD (collateral + leveraged)
+
+EUR::fxUSD was not affected (deployed correctly in 1.0).
+
+---
+
+## deploy/1.0.2 — New Markets
+
+**Date:** 2026-01-16 to 2026-01-17
+**Git tag:** `deploy/1.0.2` → `13fa8bcf`
+**Commit:** "install EUR/GOLD tidy up"
+
+New market deployments using the same v1 infrastructure. Not a bugfix — additional pegs and collateral types added to complete the initial market lineup.
+
+### Markets added
+
+| Date | Peg | Collateral |
+|------|-----|-----------|
+| 2026-01-16 | SILVER | fxSAVE (fxUSD) |
+| 2026-01-16 | SILVER | wstETH (stETH) |
+| 2026-01-17 | MCAP | fxSAVE (fxUSD) |
+| 2026-01-17 | MCAP | wstETH (stETH) |
+| 2026-01-17 | EUR | wstETH (stETH) |
+| 2026-01-17 | GOLD | wstETH (stETH) |
+
+After this deployment: 7 pegs (BTC, ETH, EUR, GOLD, SILVER, MCAP), 11 markets total.
+
+---
+
+## deploy/1.1 — StabilityPool v2 Upgrade
+
+**Date:** 2026-02-11
+**Git tag:** `deploy/1.1` → `ac278bd`
+**Commit:** "deployment simulation and testing"
+
+Upgraded all 24 stability pools (12 collateral + 12 leveraged across 7 pegs and 11 markets) from StabilityPool_v1 to StabilityPool_v2.
+
+### Fixes in StabilityPool_v2
+
+- **SP overflow:** reward integral overflow at high cumulative reward/pool ratios (uint192 → uint256)
+- **Epoch removal:** simplified the Liquity epoch/scale tracking
+- **Linear reward underflow:** rate truncation at small reward amounts (minimum useful reward = periodLength + 1)
+- **Finish-at-zero:** reward period end condition fix
+- **Genesis end:** genesis end condition fix
+
+### Verification
+
+Verified via `script/verify/sp-v2-upgrade/`:
+- Mainnet fork upgrade test comparing v1 and v2 behaviour
+- Run script: `script/verify/sp-v2-upgrade/run-upgrade-test-StabilityPool_v2`
+- Upgrade runbook: `script/verify/sp-v2-upgrade/upgrade-StabilityPool_v2.md`
+
+### Contract changes
+
+| Contract | Change |
+|----------|--------|
+| StabilityPool | v1 → v2 (all 24 pools) |
+| Minter | v1 (unchanged) |
+| StabilityPoolManager | v1 (unchanged) |
+
+---
+
+## deploy/1.2 — Minter v2 Upgrade + SPL Remediation
+
+**Date:** 2026-03-21 to 2026-03-25
+**Git tag:** `deploy/1.2` → `1a892d8`
+**Commit:** "deployed minter _v2"
+
+Three operations:
+1. BaoPauser_v1 deployed (2026-03-21) — emergency pause implementation
+2. Minter_v1 → Minter_v2 upgrade for all 11 markets (2026-03-24)
+3. ETH::fxUSD leveraged SP remediation (2026-03-25) — corrected reward integral inflated by Minter v1 over-minting bug
+
+### SPL Remediation detail
+
+The ETH::fxUSD leveraged stability pool had an inflated reward integral from a Minter v1 over-minting bug during rebalance. The `PostRebalanceRemediationForStabilityPool_v2` one-shot contract:
+1. Corrected the reward integral (scaled by v2/v1 distribution ratio)
+2. Burned excess leveraged tokens from the pool and affected wallets
+3. Restored missing collateral via free mint + burn
+
+See `script/verify/spl-remediation/remediation-ETH-fxUSD-SPL.md` for full analysis.
+
+### Verification
+
+- `script/verify/minter-v2-upgrade/run-upgrade-test-Minter_v2`
+- `script/verify/spl-remediation/run-upgrade-test-remediate-ETH_fxUSD_SPL`
+- `script/verify/spl-remediation/SPLRemediationTest.t.sol` — mainnet fork test
+- `script/verify/spl-remediation/V2ReplaySimulation.t.sol` — v1 vs v2 replay
+
+### Contract changes
+
+| Contract | Change |
+|----------|--------|
+| Minter | v1 → v2 (all 11 markets) |
+| BaoPauser | v1 (new) |
+| StabilityPool | v2 (unchanged) |
+| PostRebalanceRemediationForStabilityPool_v2 | one-shot (ETH::fxUSD SPL only) |
+
+---
+
+## deploy/1.3 — In Development
+
+SP_v3, Minter_v3, reward aliases, autocompounding infrastructure. See `doc/autocompounding-vault-design.md`.
diff --git a/deployments/etherscan-links.md b/deployments/etherscan-links.md
deleted file mode 100644
index 36a692c0..00000000
--- a/deployments/etherscan-links.md
+++ /dev/null
@@ -1,149 +0,0 @@
-# Harbor Deployment Addresses
-
-Generated: 2025-12-20 14:02:41 UTC
-
-## harbor_v1::BTC
-
-- [harbor_v1::BTC pegged](https://etherscan.io/address/0x25bA4A826E1A1346dcA2Ab530831dbFF9C08bEA7#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x25bA4A826E1A1346dcA2Ab530831dbFF9C08bEA7)] ✓✓✓ [[MintableBurnableERC20_v1](https://etherscan.io/address/0x8d6B59B2D07C1e70BE2B167a4fD07807df133582#code)] ✓
-
-## harbor_v1::BTC::fxUSD
-
-- [harbor_v1::BTC::fxUSD minterFeeReceiver](https://etherscan.io/address/0x70DdA12032335656b63435840Cd55ff7A19dDAb7#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x70DdA12032335656b63435840Cd55ff7A19dDAb7)] ✓✓✓ [[TokenDistributor_v1](https://etherscan.io/address/0xeE8D6D850Be79A4Fb6f829FC9BB0d28Dfa8515Df#code)] ✓
-- [harbor_v1::BTC::fxUSD stabilityPoolManagerFeeReceiver](https://etherscan.io/address/0xbB44740D2FA2310888f491A9dB8B1474c741BD0f#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xbB44740D2FA2310888f491A9dB8B1474c741BD0f)] ✓✓✓ [[TokenDistributor_v1](https://etherscan.io/address/0xca0a68F9C20d67C7931F91b64b89f4b22Af51cDf#code)] ✓
-- [harbor_v1::BTC::fxUSD minter](https://etherscan.io/address/0x33e32ff4d0677862fa31582CC654a25b9b1e4888#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x33e32ff4d0677862fa31582CC654a25b9b1e4888)] ✓✓✓ [[Minter_v1](https://etherscan.io/address/0x3089421DED39761Cf326EC03521b08AAFbdBB444#code)] ✓
-- [harbor_v1::BTC::fxUSD stabilityPoolManager](https://etherscan.io/address/0x768E0a386e1972eB5995429Fe21E7aC0f22F516e#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x768E0a386e1972eB5995429Fe21E7aC0f22F516e)] ✓✓✓ [[StabilityPoolManager_v1](https://etherscan.io/address/0x77C6665c67cbBB8cc1DB30213b5ab4449ef364F5#code)] ✓
-- [harbor_v1::BTC::fxUSD collateral](https://etherscan.io/address/0x085780639CC2cACd35E474e71f4d000e2405d8f6#code)
-- [harbor_v1::BTC::fxUSD wrappedCollateral](https://etherscan.io/address/0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39#code)
-- [harbor_v1::BTC::fxUSD priceOracle](https://etherscan.io/address/0x8F76a260c5D21586aFfF18f880FFC808D0524A73#code)
-- [harbor_v1::BTC::fxUSD pegged](https://etherscan.io/address/0x25bA4A826E1A1346dcA2Ab530831dbFF9C08bEA7#code)
-- [harbor_v1::BTC::fxUSD leveraged](https://etherscan.io/address/0x9567c243F647f9Ac37efb7Fc26BD9551Dce0BE1B#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x9567c243F647f9Ac37efb7Fc26BD9551Dce0BE1B)] ✓✓✓ [[MintableBurnableERC20_v1](https://etherscan.io/address/0xAA6E345De9B9E86dFcDBE1f75a9e5b5610AfE773#code)] ✓
-- [harbor_v1::BTC::fxUSD reservePool](https://etherscan.io/address/0xfDE46D4425138aA01319bB8587Cb935a0393DfE3#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xfDE46D4425138aA01319bB8587Cb935a0393DfE3)] ✓✓✓ [[ReservePool_v1](https://etherscan.io/address/0x1cabe71747D3650F7D94A42Fcd89d92001c26E5F#code)] ✓
-- [harbor_v1::BTC::fxUSD stabilityPoolCollateral](https://etherscan.io/address/0x86561cdB34ebe8B9abAbb0DD7bEA299fA8532a49#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x86561cdB34ebe8B9abAbb0DD7bEA299fA8532a49)] ✓✓✓ [[StabilityPool_v1](https://etherscan.io/address/0x7C59d965BD7d26daCE3f85c35D56D7EebCe57EbF#code)] ✓
-- [harbor_v1::BTC::fxUSD stabilityPoolLeveraged](https://etherscan.io/address/0x9e56F1E1E80EBf165A1dAa99F9787B41cD5bFE40#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x9e56F1E1E80EBf165A1dAa99F9787B41cD5bFE40)] ✓✓✓ [[StabilityPool_v1](https://etherscan.io/address/0x9755FEcC9F86a719b850eb4B20066dfea0f2FeC0#code)] ✓
-- [harbor_v1::BTC::fxUSD genesis](https://etherscan.io/address/0x42cc9a19b358a2A918f891D8a6199d8b05F0BC1C#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x42cc9a19b358a2A918f891D8a6199d8b05F0BC1C)] ✓✓✓ [[Genesis_v1](https://etherscan.io/address/0xa3a03e0077feF127Bbd6638E8d3Cb3a371BeeAa1#code)] ✓
-
-## harbor_v1::BTC::stETH
-
-- [harbor_v1::BTC::stETH minterFeeReceiver](https://etherscan.io/address/0xc3a97138a5aDCC7d28A1375E28EC3440aeaeDF3e#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xc3a97138a5aDCC7d28A1375E28EC3440aeaeDF3e)] ✓✓✓ [[TokenDistributor_v1](https://etherscan.io/address/0x72c20fbB8EdC199ABaB9086C1B42958A185da255#code)] ✓
-- [harbor_v1::BTC::stETH stabilityPoolManagerFeeReceiver](https://etherscan.io/address/0x3fdd1D4E5f4DAAeC4650b212935832DaECF62B1c#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x3fdd1D4E5f4DAAeC4650b212935832DaECF62B1c)] ✓✓✓ [[TokenDistributor_v1](https://etherscan.io/address/0x2B7ac74a047a474e5B1D5219CAf462b0506a0c31#code)] ✓
-- [harbor_v1::BTC::stETH minter](https://etherscan.io/address/0xF42516EB885E737780EB864dd07cEc8628000919#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xF42516EB885E737780EB864dd07cEc8628000919)] ✓✓✓ [[Minter_v1](https://etherscan.io/address/0x203866Ab0626b872313E0B420aD392470a3AcdDA#code)] ✓
-- [harbor_v1::BTC::stETH stabilityPoolManager](https://etherscan.io/address/0x5e9Bcaa1EDfD665c09a9e6693B447581d61A85A1#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x5e9Bcaa1EDfD665c09a9e6693B447581d61A85A1)] ✓✓✓ [[StabilityPoolManager_v1](https://etherscan.io/address/0xF7aAF7D417cCabe0EB0aE36ff469311A5b6815da#code)] ✓
-- [harbor_v1::BTC::stETH collateral](https://etherscan.io/address/0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84#code)
-- [harbor_v1::BTC::stETH wrappedCollateral](https://etherscan.io/address/0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0#code)
-- [harbor_v1::BTC::stETH priceOracle](https://etherscan.io/address/0xE370289aF2145A5B2F0F7a4a900eBfD478A156dB#code)
-- [harbor_v1::BTC::stETH pegged](https://etherscan.io/address/0x25bA4A826E1A1346dcA2Ab530831dbFF9C08bEA7#code)
-- [harbor_v1::BTC::stETH leveraged](https://etherscan.io/address/0x817ADaE288eD46B8618AAEffE75ACD26A0a1b0FD#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x817ADaE288eD46B8618AAEffE75ACD26A0a1b0FD)] ✓✓✓ [[MintableBurnableERC20_v1](https://etherscan.io/address/0xA580AF59522c9BA0Fca82Ae8D9Aaa10Fa212F0A1#code)] ✓
-- [harbor_v1::BTC::stETH reservePool](https://etherscan.io/address/0x515ECa19Ac381b0f37D616F99628136906fC5355#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x515ECa19Ac381b0f37D616F99628136906fC5355)] ✓✓✓ [[ReservePool_v1](https://etherscan.io/address/0xd904b61EBa087DfE7d984259B50324Cc1808c88F#code)] ✓
-- [harbor_v1::BTC::stETH stabilityPoolCollateral](https://etherscan.io/address/0x667Ceb303193996697A5938cD6e17255EeAcef51#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x667Ceb303193996697A5938cD6e17255EeAcef51)] ✓✓✓ [[StabilityPool_v1](https://etherscan.io/address/0x932cAeb990f39B95CeAD349446C767f6bEc0C5d1#code)] ✓
-- [harbor_v1::BTC::stETH stabilityPoolLeveraged](https://etherscan.io/address/0xCB4F3e21DE158bf858Aa03E63e4cEc7342177013#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xCB4F3e21DE158bf858Aa03E63e4cEc7342177013)] ✓✓✓ [[StabilityPool_v1](https://etherscan.io/address/0xb71F39140FAa63a9C32105Ef717aC79a58878025#code)] ✓
-- [harbor_v1::BTC::stETH genesis](https://etherscan.io/address/0xc64Fc46eED431e92C1b5e24DC296b5985CE6Cc00#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xc64Fc46eED431e92C1b5e24DC296b5985CE6Cc00)] ✓✓✓ [[Genesis_v1](https://etherscan.io/address/0x93E71d996C5ccD1554f1ddAE4977EA857abeb89C#code)] ✓
-
-## harbor_v1::ETH
-
-- [harbor_v1::ETH pegged](https://etherscan.io/address/0x7A53EBc85453DD006824084c4f4bE758FcF8a5B5#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x7A53EBc85453DD006824084c4f4bE758FcF8a5B5)] ✓✓✓ [[MintableBurnableERC20_v1](https://etherscan.io/address/0x4cD716A3DEe21eCB76B485F4374318507FeC1B75#code)] ✓
-
-## harbor_v1::ETH::fxUSD
-
-- [harbor_v1::ETH::fxUSD minterFeeReceiver](https://etherscan.io/address/0xdC903fe5ebCE440f22578D701b95424363D20881#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xdC903fe5ebCE440f22578D701b95424363D20881)] ✓✓✓ [[TokenDistributor_v1](https://etherscan.io/address/0xEC0646d6a08A4409908DA1fABc26969AE135EA97#code)] ✓
-- [harbor_v1::ETH::fxUSD stabilityPoolManagerFeeReceiver](https://etherscan.io/address/0x9e92965Afb51ce80aa451F93530880f469C2B282#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x9e92965Afb51ce80aa451F93530880f469C2B282)] ✓✓✓ [[TokenDistributor_v1](https://etherscan.io/address/0xe32bF3a9d68119094d5E388eA5AF226e6d57Cc91#code)] ✓
-- [harbor_v1::ETH::fxUSD minter](https://etherscan.io/address/0xd6E2F8e57b4aFB51C6fA4cbC012e1cE6aEad989F#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xd6E2F8e57b4aFB51C6fA4cbC012e1cE6aEad989F)] ✓✓✓ [[Minter_v1](https://etherscan.io/address/0x813ddD349a459137dC2Bf36B8Dc57508B63b5bCF#code)] ✓
-- [harbor_v1::ETH::fxUSD stabilityPoolManager](https://etherscan.io/address/0xE39165aDE355988EFb24dA4f2403971101134CAB#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xE39165aDE355988EFb24dA4f2403971101134CAB)] ✓✓✓ [[StabilityPoolManager_v1](https://etherscan.io/address/0x9C6a6B61b1ac3A344584c3747597964c0DD65C7D#code)] ✓
-- [harbor_v1::ETH::fxUSD collateral](https://etherscan.io/address/0x085780639CC2cACd35E474e71f4d000e2405d8f6#code)
-- [harbor_v1::ETH::fxUSD wrappedCollateral](https://etherscan.io/address/0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39#code)
-- [harbor_v1::ETH::fxUSD priceOracle](https://etherscan.io/address/0x71437C90F1E0785dd691FD02f7bE0B90cd14c097#code)
-- [harbor_v1::ETH::fxUSD pegged](https://etherscan.io/address/0x7A53EBc85453DD006824084c4f4bE758FcF8a5B5#code)
-- [harbor_v1::ETH::fxUSD leveraged](https://etherscan.io/address/0x0Cd6BB1a0cfD95e2779EDC6D17b664B481f2EB4C#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x0Cd6BB1a0cfD95e2779EDC6D17b664B481f2EB4C)] ✓✓✓ [[MintableBurnableERC20_v1](https://etherscan.io/address/0xEA6555386f7E13D18Ec7c22097eAFa4D80F8bB34#code)] ✓
-- [harbor_v1::ETH::fxUSD reservePool](https://etherscan.io/address/0x7A5c4ca972CE2168d5215d252946dDbd1cAd2015#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x7A5c4ca972CE2168d5215d252946dDbd1cAd2015)] ✓✓✓ [[ReservePool_v1](https://etherscan.io/address/0x4816D539cCDE3326A6Ecc8Df1e59570399285223#code)] ✓
-- [harbor_v1::ETH::fxUSD stabilityPoolCollateral](https://etherscan.io/address/0x1F985CF7C10A81DE1940da581208D2855D263D72#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x1F985CF7C10A81DE1940da581208D2855D263D72)] ✓✓✓ [[StabilityPool_v1](https://etherscan.io/address/0xa3BAe4ed645bfdD089978C8780a06F1786dD8957#code)] ✓
-- [harbor_v1::ETH::fxUSD stabilityPoolLeveraged](https://etherscan.io/address/0x438B29EC7a1770dDbA37D792F1A6e76231Ef8E06#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x438B29EC7a1770dDbA37D792F1A6e76231Ef8E06)] ✓✓✓ [[StabilityPool_v1](https://etherscan.io/address/0x12d24Ca4F99b883B26fA7D51847A9fd756455F04#code)] ✓
-- [harbor_v1::ETH::fxUSD genesis](https://etherscan.io/address/0xC9df4f62474Cf6cdE6c064DB29416a9F4f27EBdC#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xC9df4f62474Cf6cdE6c064DB29416a9F4f27EBdC)] ✓✓✓ [[Genesis_v1](https://etherscan.io/address/0x96ED4c4a0D4a82ED649fb15A64C13472D9a28F93#code)] ✓
-
-## harbor_v1::EUR
-
-- [harbor_v1::EUR pegged](https://etherscan.io/address/0x83Fd69E0FF5767972b46E61C6833408361bF7346#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x83Fd69E0FF5767972b46E61C6833408361bF7346)] ✓✓✓ [[MintableBurnableERC20_v1](https://etherscan.io/address/0xe88A00298279D55718FB5E9d8009C1040c5905f7#code)] ✓
-
-## harbor_v1::EUR::fxUSD
-
-- [harbor_v1::EUR::fxUSD minterFeeReceiver](https://etherscan.io/address/0x43dfDB5059777A8B8819d8D8ff2c9ACCFEb766CB#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x43dfDB5059777A8B8819d8D8ff2c9ACCFEb766CB)] ✓✓✓ [[TokenDistributor_v1](https://etherscan.io/address/0x07F9194fE7c847472Ef6D984DdE0D447fd4b76B1#code)] ✓
-- [harbor_v1::EUR::fxUSD stabilityPoolManagerFeeReceiver](https://etherscan.io/address/0xd2a815B2210c15E1626CD0D487C77852E7C37b17#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xd2a815B2210c15E1626CD0D487C77852E7C37b17)] ✓✓✓ [[TokenDistributor_v1](https://etherscan.io/address/0xC266C83e5474756eA904987D63450b2E0CF6c0C1#code)] ✓
-- [harbor_v1::EUR::fxUSD minter](https://etherscan.io/address/0xDEFB2C04062350678965CBF38A216Cc50723B246#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xDEFB2C04062350678965CBF38A216Cc50723B246)] ✓✓✓ [[Minter_v1](https://etherscan.io/address/0xc7E34ecD57975430aB9DDb535Df4f266Ba2Ec1d1#code)] ✓
-- [harbor_v1::EUR::fxUSD stabilityPoolManager](https://etherscan.io/address/0x756766756880ceA06270Fd507b09Ef32714Ec7C2#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x756766756880ceA06270Fd507b09Ef32714Ec7C2)] ✓✓✓ [[StabilityPoolManager_v1](https://etherscan.io/address/0xF625E8147C07DDF5a488FCB5C95c91ACef46E22E#code)] ✓
-- [harbor_v1::EUR::fxUSD collateral](https://etherscan.io/address/0x085780639CC2cACd35E474e71f4d000e2405d8f6#code)
-- [harbor_v1::EUR::fxUSD wrappedCollateral](https://etherscan.io/address/0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39#code)
-- [harbor_v1::EUR::fxUSD priceOracle](https://etherscan.io/address/0x6bEb1a1189Ac68a2a26b5210e5ccfB9e8a3E408E#code)
-- [harbor_v1::EUR::fxUSD pegged](https://etherscan.io/address/0x83Fd69E0FF5767972b46E61C6833408361bF7346#code)
-- [harbor_v1::EUR::fxUSD leveraged](https://etherscan.io/address/0x7A7C1f2502c19193C44662A2Aff51c2B76fDDAEA#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x7A7C1f2502c19193C44662A2Aff51c2B76fDDAEA)] ✓✓✓ [[MintableBurnableERC20_v1](https://etherscan.io/address/0xe86c568aDEd105d7A63fe63e2C12Dfa567cBf2e3#code)] ✓
-- [harbor_v1::EUR::fxUSD reservePool](https://etherscan.io/address/0x27cA37538358F90d45cAA886fB58CC08ffe2dD2f#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x27cA37538358F90d45cAA886fB58CC08ffe2dD2f)] ✓✓✓ [[ReservePool_v1](https://etherscan.io/address/0xDfFb26e9f81Cbc8B9b88c2e2DDeEe770fe765EFd#code)] ✓
-- [harbor_v1::EUR::fxUSD stabilityPoolCollateral](https://etherscan.io/address/0xe60054E6b518f67411834282cE1557381f050B13#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xe60054E6b518f67411834282cE1557381f050B13)] ✓✓✓ [[StabilityPool_v1](https://etherscan.io/address/0x5A2440034cc32A298e44B34247a83508ef42cCF4#code)] ✓
-- [harbor_v1::EUR::fxUSD stabilityPoolLeveraged](https://etherscan.io/address/0xc5e0dA7e0a178850438E5E97ed59b6eb2562e88E#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xc5e0dA7e0a178850438E5E97ed59b6eb2562e88E)] ✓✓✓ [[StabilityPool_v1](https://etherscan.io/address/0xEA8e632d20235c7450C9d49fbf868bdb2B981df8#code)] ✓
-- [harbor_v1::EUR::fxUSD genesis](https://etherscan.io/address/0xa9EB43Ed6Ba3B953a82741F3e226C1d6B029699b#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xa9EB43Ed6Ba3B953a82741F3e226C1d6B029699b)] ✓✓✓ [[Genesis_v1](https://etherscan.io/address/0x6d43EE4F28E6f25871B34a16cAC500DA54132B30#code)] ✓
-
-## harbor_v1::GOLD
-
-- [harbor_v1::GOLD pegged](https://etherscan.io/address/0x5b66D86932aE5D9751da588d91D494950554061d#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x5b66D86932aE5D9751da588d91D494950554061d)] ✓✗✗ [[MintableBurnableERC20_v1](https://etherscan.io/address/0x28eB6581253Ae4F9215b01F7e723Bd465fa46e2b#code)] ✓
-
-## harbor_v1::GOLD::fxUSD
-
-- [harbor_v1::GOLD::fxUSD minterFeeReceiver](https://etherscan.io/address/0x8C5EF0342543A509e5548c71A66dE7D8A69c6B70#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x8C5EF0342543A509e5548c71A66dE7D8A69c6B70)] ✓✓✓ [[TokenDistributor_v1](https://etherscan.io/address/0x99f3CAC5F7a3c91134Dccd523560F553BA286E1b#code)] ✓
-- [harbor_v1::GOLD::fxUSD stabilityPoolManagerFeeReceiver](https://etherscan.io/address/0x360838316494E355CE7a58c2990606F30F21e8A1#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x360838316494E355CE7a58c2990606F30F21e8A1)] ✓✓✓ [[TokenDistributor_v1](https://etherscan.io/address/0xa5c42eC86DD26603a3cd48Cf95abDAc7E1D14B70#code)] ✓
-- [harbor_v1::GOLD::fxUSD minter](https://etherscan.io/address/0x880600E0c803d836E305B7c242FC095Eed234A8f#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x880600E0c803d836E305B7c242FC095Eed234A8f)] ✓✓✓ [[Minter_v1](https://etherscan.io/address/0xC2ee9f547123990e513403111F314B520C1DD812#code)] ✓
-- [harbor_v1::GOLD::fxUSD stabilityPoolManager](https://etherscan.io/address/0x5b69069CC4012a96342B0FeCC28aD15bDE6447B5#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x5b69069CC4012a96342B0FeCC28aD15bDE6447B5)] ✓✓✓ [[StabilityPoolManager_v1](https://etherscan.io/address/0x5c96077BB55376b66670B937F7bBdDBBc63A8564#code)] ✓
-- [harbor_v1::GOLD::fxUSD collateral](https://etherscan.io/address/0x085780639CC2cACd35E474e71f4d000e2405d8f6#code)
-- [harbor_v1::GOLD::fxUSD wrappedCollateral](https://etherscan.io/address/0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39#code)
-- [harbor_v1::GOLD::fxUSD priceOracle](https://etherscan.io/address/0x7DAe17B00DCd5C37D4992a17C3Cf8f5E15d2BbAf#code)
-- [harbor_v1::GOLD::fxUSD pegged](https://etherscan.io/address/0x5b66D86932aE5D9751da588d91D494950554061d#code)
-- [harbor_v1::GOLD::fxUSD leveraged](https://etherscan.io/address/0x85730Af3A7d7A872Ee1D84306E0575f1E00C0980#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x85730Af3A7d7A872Ee1D84306E0575f1E00C0980)] ✓✓✓ [[MintableBurnableERC20_v1](https://etherscan.io/address/0xaCA783ba4D58b78371D0b5822E81Eeb42194DF2c#code)] ✓
-- [harbor_v1::GOLD::fxUSD reservePool](https://etherscan.io/address/0xc033e81ED555D6db63A3E0Af9795454C7BdF094a#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xc033e81ED555D6db63A3E0Af9795454C7BdF094a)] ✓✓✓ [[ReservePool_v1](https://etherscan.io/address/0x4b5996034C1B888ac70bB4C5687E6faF5DddFeF8#code)] ✓
-- [harbor_v1::GOLD::fxUSD stabilityPoolCollateral](https://etherscan.io/address/0xC1EF32d4B959F2200efDeDdedadA226461d14DaC#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0xC1EF32d4B959F2200efDeDdedadA226461d14DaC)] ✓✓✓ [[StabilityPool_v1](https://etherscan.io/address/0xA041d39ceD4aBAE2e50427712653f8a79d08bd2D#code)] ✓
-- [harbor_v1::GOLD::fxUSD stabilityPoolLeveraged](https://etherscan.io/address/0x5bDED171f1c08B903b466593B0E022F9FdE8399c#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x5bDED171f1c08B903b466593B0E022F9FdE8399c)] ✓✗✗ [[StabilityPool_v1](https://etherscan.io/address/0x64647EA21a5750E406cA90A114639D6b1388A904#code)] ✓
-- [harbor_v1::GOLD::fxUSD genesis](https://etherscan.io/address/0x2cbF457112Ef5A16cfcA10Fb173d56a5cc9DAa66#code)
- - [[verify proxy](https://etherscan.io/proxyContractChecker?a=0x2cbF457112Ef5A16cfcA10Fb173d56a5cc9DAa66)] ✓✓✓ [[Genesis_v1](https://etherscan.io/address/0xe08d21418ED9078fE0292602DAD28BA9347312EA#code)] ✓
-
diff --git a/deployments/harbor_v1::BTC.logs/mainnet/latest.json b/deployments/harbor_v1::BTC.logs/mainnet/latest.json
deleted file mode 100644
index 4fc712e1..00000000
--- a/deployments/harbor_v1::BTC.logs/mainnet/latest.json
+++ /dev/null
@@ -1,61 +0,0 @@
-{
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "prefix": "harbor_v1",
- "peggedTicker": "BTC",
- "treasury": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "schemaVersion": 1,
- "systemSaltString": "harbor_v1::BTC",
- "session": {
- "network": "mainnet",
- "chainId": 1,
- "startTimestamp": 1766178611,
- "started": "2025-12-19T21:10:11Z",
- "startBlock": 24049235,
- "deployer": "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38",
- "stub": {
- "address": "0x2D5D7d3eCF18748183D000354f7CA589c0968682",
- "contractType": "UUPSProxyDeployStub",
- "contractPath": "script/deployment/UUPSProxyDeployStub.sol",
- "blockNumber": 24049235
- },
- "finishTimestamp": 1766178611,
- "finished": "2025-12-19T21:10:11Z",
- "finishBlock": 24049235
- },
- "BaoFactory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "peggedSaltString": "harbor_v1::BTC",
- "contracts": {
- "pegged": {
- "symbol": "haBTC",
- "name": "Harbor anchored BTC",
- "address": "0x25bA4A826E1A1346dcA2Ab530831dbFF9C08bEA7",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38",
- "blockNumber": 24049235,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::pegged",
- "salt": "0x7033198a29866f20ff28bd9d62633a1a4d05c069beb58d68e666d5a39763f4d1",
- "implementation": {
- "address": "0x8d6B59B2D07C1e70BE2B167a4fD07807df133582",
- "contractType": "MintableBurnableERC20_v1",
- "contractPath": "lib/bao-base-audit-2025-07/src/MintableBurnableERC20_v1.sol",
- "deployer": "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38",
- "blockNumber": 24049235,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "MINTER_ROLE": {
- "value": 1,
- "grantees": []
- },
- "BURNER_ROLE": {
- "value": 2,
- "grantees": []
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/deployments/harbor_v1::BTC::fxUSD.logs/mainnet/latest.json b/deployments/harbor_v1::BTC::fxUSD.logs/mainnet/latest.json
deleted file mode 100644
index ba5cfd6e..00000000
--- a/deployments/harbor_v1::BTC::fxUSD.logs/mainnet/latest.json
+++ /dev/null
@@ -1,416 +0,0 @@
-{
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "prefix": "harbor_v1",
- "peggedTicker": "BTC",
- "treasury": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "priceOracle": "0x8F76a260c5D21586aFfF18f880FFC808D0524A73",
- "contracts": {
- "minterFeeReceiver": {
- "name": "Minter Fee Receiver",
- "tokens": [
- "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- ],
- "recipients": [
- "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2"
- ],
- "shares": [
- 100e16
- ],
- "address": "0x70DdA12032335656b63435840Cd55ff7A19dDAb7",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::fxUSD::minterFeeReceiver",
- "salt": "0x7532e09ba0bea8c23797987a2840a02181ac1af31d97df4c472de69a3f6a13fe",
- "implementation": {
- "address": "0xeE8D6D850Be79A4Fb6f829FC9BB0d28Dfa8515Df",
- "contractType": "TokenDistributor_v1",
- "contractPath": "src/minter/TokenDistributor_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "ownershipModel": "transferred-after-deploy"
- }
- },
- "stabilityPoolManagerFeeReceiver": {
- "name": "StabilityPoolManager Cut Receiver",
- "tokens": [
- "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- ],
- "recipients": [
- "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "0x86561cdB34ebe8B9abAbb0DD7bEA299fA8532a49",
- "0x9e56F1E1E80EBf165A1dAa99F9787B41cD5bFE40"
- ],
- "shares": [
- 4e16,
- 48e16,
- 48e16
- ],
- "address": "0xbB44740D2FA2310888f491A9dB8B1474c741BD0f",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::fxUSD::stabilityPoolManagerFeeReceiver",
- "salt": "0xfe65dfa2956ad510994e28d6f4965d43cbcc5d3cb3cfd9e5bb2a62920096aae2",
- "implementation": {
- "address": "0xca0a68F9C20d67C7931F91b64b89f4b22Af51cDf",
- "contractType": "TokenDistributor_v1",
- "contractPath": "src/minter/TokenDistributor_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "ownershipModel": "transferred-after-deploy"
- }
- },
- "minter": {
- "config": {
- "mintPeggedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 131e16,
- 140e16
- ],
- "incentiveRatios": [
- 100e16,
- 1e16,
- 0.5e16
- ]
- },
- "redeemPeggedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 100e16,
- 110e16,
- 120e16,
- 140e16
- ],
- "incentiveRatios": [
- -0.5e16,
- -0.5e16,
- 0,
- 0.6e16,
- 0.8e16
- ]
- },
- "mintLeveragedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 100e16,
- 110e16,
- 120e16,
- 140e16
- ],
- "incentiveRatios": [
- -5e16,
- -5e16,
- 0,
- 2e16,
- 7e16
- ]
- },
- "redeemLeveragedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 110e16,
- 140e16
- ],
- "incentiveRatios": [
- 100e16,
- 1.5e16,
- 1.2e16
- ]
- }
- },
- "feeReceiver": "0x70DdA12032335656b63435840Cd55ff7A19dDAb7",
- "address": "0x33e32ff4d0677862fa31582CC654a25b9b1e4888",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::fxUSD::minter",
- "salt": "0xd03066952bfd478dccfa5aa06bf931bb3828f5b2909700bfc16f768aa2d8f6d8",
- "implementation": {
- "address": "0x3089421DED39761Cf326EC03521b08AAFbdBB444",
- "contractType": "Minter_v1",
- "contractPath": "src/minter/Minter_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "HARVESTER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "ZERO_FEE_ROLE": {
- "value": 1,
- "grantees": [
- "contracts.genesis"
- ]
- }
- }
- },
- "stabilityPoolManager": {
- "rebalanceThreshold": 130e16,
- "rebalanceBountyRatio": 1e16,
- "harvestBountyRatio": 1e16,
- "harvestCutRatio": 100e16,
- "feeReceiver": "0xbB44740D2FA2310888f491A9dB8B1474c741BD0f",
- "address": "0x768E0a386e1972eB5995429Fe21E7aC0f22F516e",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::fxUSD::stabilityPoolManager",
- "salt": "0xce5136a819ad6ac79a0ecc322a345e66b32c2d897fa8f2d4c3c4015f70a7a840",
- "implementation": {
- "address": "0x77C6665c67cbBB8cc1DB30213b5ab4449ef364F5",
- "contractType": "StabilityPoolManager_v1",
- "contractPath": "src/minter/StabilityPoolManager_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "ownershipModel": "transferred-after-deploy"
- }
- },
- "collateral": {
- "address": "0x085780639CC2cACd35E474e71f4d000e2405d8f6",
- "symbol": "fxUSD",
- "name": "f(x) USD"
- },
- "wrappedCollateral": {
- "address": "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39",
- "symbol": "fxSAVE",
- "name": "f(x) USD Saving"
- },
- "priceOracle": {
- "address": "0x8F76a260c5D21586aFfF18f880FFC808D0524A73"
- },
- "pegged": {
- "address": "0x25bA4A826E1A1346dcA2Ab530831dbFF9C08bEA7",
- "symbol": "haBTC",
- "name": "Harbor anchored BTC",
- "roles": {
- "MINTER_ROLE": {
- "value": 1,
- "grantees": []
- },
- "BURNER_ROLE": {
- "value": 2,
- "grantees": []
- }
- }
- },
- "leveraged": {
- "symbol": "hsFXUSD-BTC",
- "name": "Harbor sail: variable leveraged long fxUSD against BTC",
- "address": "0x9567c243F647f9Ac37efb7Fc26BD9551Dce0BE1B",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::fxUSD::leveraged",
- "salt": "0xc239ba6e0f98c0d79d4f650adfb3d821cbb19dd34a582760805d2554991b200b",
- "implementation": {
- "address": "0xAA6E345De9B9E86dFcDBE1f75a9e5b5610AfE773",
- "contractType": "MintableBurnableERC20_v1",
- "contractPath": "lib/bao-base-audit-2025-07/src/MintableBurnableERC20_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "MINTER_ROLE": {
- "value": 1,
- "grantees": [
- "contracts.minter"
- ]
- },
- "BURNER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.minter"
- ]
- }
- }
- },
- "reservePool": {
- "address": "0xfDE46D4425138aA01319bB8587Cb935a0393DfE3",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::fxUSD::reservePool",
- "salt": "0xda1020e15adf0b8aa093e3a8fb671fa0ed4f663de8d05911f4c65a66c09a6941",
- "implementation": {
- "address": "0x1cabe71747D3650F7D94A42Fcd89d92001c26E5F",
- "contractType": "ReservePool_v1",
- "contractPath": "src/minter/ReservePool_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "REQUESTER_ROLE": {
- "value": 1,
- "grantees": [
- "contracts.minter"
- ]
- }
- }
- },
- "stabilityPoolCollateral": {
- "address": "0x86561cdB34ebe8B9abAbb0DD7bEA299fA8532a49",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::fxUSD::stabilityPoolCollateral",
- "salt": "0xb1ad5be89343b2051d676fe9cf2695ad258e3b8e7d9efe23abec700a52cb2ceb",
- "implementation": {
- "address": "0x7C59d965BD7d26daCE3f85c35D56D7EebCe57EbF",
- "contractType": "StabilityPool_v1",
- "contractPath": "src/minter/StabilityPool_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "ownershipModel": "transferred-after-deploy"
- },
- "liquidation": "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39",
- "rewardTokens": [
- "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- ],
- "roles": {
- "REBALANCER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_DEPOSITOR_ROLE": {
- "value": 4,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_MANAGER_ROLE": {
- "value": 1,
- "grantees": []
- }
- }
- },
- "stabilityPoolLeveraged": {
- "address": "0x9e56F1E1E80EBf165A1dAa99F9787B41cD5bFE40",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::fxUSD::stabilityPoolLeveraged",
- "salt": "0xab5eec1933962e135b3c7dfb91b2770bfbd72cf0629777c41f18073d089ef0c9",
- "implementation": {
- "address": "0x9755FEcC9F86a719b850eb4B20066dfea0f2FeC0",
- "contractType": "StabilityPool_v1",
- "contractPath": "src/minter/StabilityPool_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "ownershipModel": "transferred-after-deploy"
- },
- "liquidation": "0x9567c243F647f9Ac37efb7Fc26BD9551Dce0BE1B",
- "rewardTokens": [
- "0x9567c243F647f9Ac37efb7Fc26BD9551Dce0BE1B",
- "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- ],
- "roles": {
- "REBALANCER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_DEPOSITOR_ROLE": {
- "value": 4,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_MANAGER_ROLE": {
- "value": 1,
- "grantees": []
- }
- }
- },
- "genesis": {
- "address": "0x42cc9a19b358a2A918f891D8a6199d8b05F0BC1C",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::fxUSD::genesis",
- "salt": "0xb2d111513b4cd541bded2f5bb2c94324c4fcb65b776c6809adae8c3b117e4f24",
- "implementation": {
- "address": "0xa3a03e0077feF127Bbd6638E8d3Cb3a371BeeAa1",
- "contractType": "Genesis_v1",
- "contractPath": "src/minter/Genesis_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049375,
- "ownershipModel": "transferred-after-deploy"
- }
- }
- },
- "stabilityPoolEarlyWithdrawalFeeRatio": 1e16,
- "stabilityPoolWithdrawalDelay": 3600,
- "stabilityPoolWithdrawalPeriod": 90000,
- "stabilityPoolMinTotalAssetSupply": 1e18,
- "networks": {
- "mainnet": {
- "chainId": 1,
- "collateral": "0x085780639CC2cACd35E474e71f4d000e2405d8f6",
- "wrappedCollateral": "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- }
- },
- "schemaVersion": 1,
- "systemSaltString": "harbor_v1::BTC::fxUSD",
- "session": {
- "network": "mainnet",
- "chainId": 1,
- "startTimestamp": 1766180303,
- "started": "2025-12-19T21:38:23Z",
- "startBlock": 24049375,
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "stub": {
- "address": "0x88b330c5D6816d6996F21d4D0537e443b33B9fEd",
- "contractType": "UUPSProxyDeployStub",
- "contractPath": "script/deployment/UUPSProxyDeployStub.sol",
- "blockNumber": 24049375
- },
- "finishTimestamp": 1766180303,
- "finished": "2025-12-19T21:38:23Z",
- "finishBlock": 24049375
- },
- "BaoFactory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "peggedSaltString": "harbor_v1::BTC"
-}
\ No newline at end of file
diff --git a/deployments/harbor_v1::BTC::stETH.logs/mainnet/latest.json b/deployments/harbor_v1::BTC::stETH.logs/mainnet/latest.json
deleted file mode 100644
index 14a4df37..00000000
--- a/deployments/harbor_v1::BTC::stETH.logs/mainnet/latest.json
+++ /dev/null
@@ -1,416 +0,0 @@
-{
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "prefix": "harbor_v1",
- "peggedTicker": "BTC",
- "treasury": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "priceOracle": "0xE370289aF2145A5B2F0F7a4a900eBfD478A156dB",
- "contracts": {
- "minterFeeReceiver": {
- "name": "Minter Fee Receiver",
- "tokens": [
- "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
- ],
- "recipients": [
- "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2"
- ],
- "shares": [
- 100e16
- ],
- "address": "0xc3a97138a5aDCC7d28A1375E28EC3440aeaeDF3e",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::stETH::minterFeeReceiver",
- "salt": "0x6efdcf9b3ea94ee8f84a36f596a164f8374dba65754c9e6b7e9889fc0af40d33",
- "implementation": {
- "address": "0x72c20fbB8EdC199ABaB9086C1B42958A185da255",
- "contractType": "TokenDistributor_v1",
- "contractPath": "src/minter/TokenDistributor_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "ownershipModel": "transferred-after-deploy"
- }
- },
- "stabilityPoolManagerFeeReceiver": {
- "name": "StabilityPoolManager Cut Receiver",
- "tokens": [
- "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
- ],
- "recipients": [
- "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "0x667Ceb303193996697A5938cD6e17255EeAcef51",
- "0xCB4F3e21DE158bf858Aa03E63e4cEc7342177013"
- ],
- "shares": [
- 4e16,
- 48e16,
- 48e16
- ],
- "address": "0x3fdd1D4E5f4DAAeC4650b212935832DaECF62B1c",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::stETH::stabilityPoolManagerFeeReceiver",
- "salt": "0xf95f7403adf7ea1741c833089c548164b0c00cfd3682c868018eded9b462e756",
- "implementation": {
- "address": "0x2B7ac74a047a474e5B1D5219CAf462b0506a0c31",
- "contractType": "TokenDistributor_v1",
- "contractPath": "src/minter/TokenDistributor_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "ownershipModel": "transferred-after-deploy"
- }
- },
- "minter": {
- "config": {
- "mintPeggedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 131e16,
- 140e16
- ],
- "incentiveRatios": [
- 100e16,
- 1e16,
- 0.5e16
- ]
- },
- "redeemPeggedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 100e16,
- 110e16,
- 120e16,
- 140e16
- ],
- "incentiveRatios": [
- -0.5e16,
- -0.5e16,
- 0,
- 0.6e16,
- 0.8e16
- ]
- },
- "mintLeveragedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 100e16,
- 110e16,
- 120e16,
- 140e16
- ],
- "incentiveRatios": [
- -5e16,
- -5e16,
- 0,
- 2e16,
- 7e16
- ]
- },
- "redeemLeveragedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 110e16,
- 140e16
- ],
- "incentiveRatios": [
- 100e16,
- 1.5e16,
- 1.2e16
- ]
- }
- },
- "feeReceiver": "0xc3a97138a5aDCC7d28A1375E28EC3440aeaeDF3e",
- "address": "0xF42516EB885E737780EB864dd07cEc8628000919",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::stETH::minter",
- "salt": "0xe10adca60a612860220af422d46402294d103f7f26456badcfccf631085233e5",
- "implementation": {
- "address": "0x203866Ab0626b872313E0B420aD392470a3AcdDA",
- "contractType": "Minter_v1",
- "contractPath": "src/minter/Minter_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "HARVESTER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "ZERO_FEE_ROLE": {
- "value": 1,
- "grantees": [
- "contracts.genesis"
- ]
- }
- }
- },
- "stabilityPoolManager": {
- "rebalanceThreshold": 130e16,
- "rebalanceBountyRatio": 1e16,
- "harvestBountyRatio": 1e16,
- "harvestCutRatio": 100e16,
- "feeReceiver": "0x3fdd1D4E5f4DAAeC4650b212935832DaECF62B1c",
- "address": "0x5e9Bcaa1EDfD665c09a9e6693B447581d61A85A1",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::stETH::stabilityPoolManager",
- "salt": "0x7b3feb7fc26498874dab6259225f184ced6a5c05c68df754269947183e65647e",
- "implementation": {
- "address": "0xF7aAF7D417cCabe0EB0aE36ff469311A5b6815da",
- "contractType": "StabilityPoolManager_v1",
- "contractPath": "src/minter/StabilityPoolManager_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "ownershipModel": "transferred-after-deploy"
- }
- },
- "collateral": {
- "address": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84",
- "symbol": "stETH",
- "name": "Liquid staked Ether 2.0"
- },
- "wrappedCollateral": {
- "address": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0",
- "symbol": "wstETH",
- "name": "Wrapped liquid staked Ether 2.0"
- },
- "priceOracle": {
- "address": "0xE370289aF2145A5B2F0F7a4a900eBfD478A156dB"
- },
- "pegged": {
- "address": "0x25bA4A826E1A1346dcA2Ab530831dbFF9C08bEA7",
- "symbol": "haBTC",
- "name": "Harbor anchored BTC",
- "roles": {
- "MINTER_ROLE": {
- "value": 1,
- "grantees": []
- },
- "BURNER_ROLE": {
- "value": 2,
- "grantees": []
- }
- }
- },
- "leveraged": {
- "symbol": "hsSTETH-BTC",
- "name": "Harbor sail: variable leveraged long stETH against BTC",
- "address": "0x817ADaE288eD46B8618AAEffE75ACD26A0a1b0FD",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::stETH::leveraged",
- "salt": "0x585f0aca33b83a0dcab8312e40894750b3ecdffb984c33c1ecce48948a329afd",
- "implementation": {
- "address": "0xA580AF59522c9BA0Fca82Ae8D9Aaa10Fa212F0A1",
- "contractType": "MintableBurnableERC20_v1",
- "contractPath": "lib/bao-base-audit-2025-07/src/MintableBurnableERC20_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "MINTER_ROLE": {
- "value": 1,
- "grantees": [
- "contracts.minter"
- ]
- },
- "BURNER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.minter"
- ]
- }
- }
- },
- "reservePool": {
- "address": "0x515ECa19Ac381b0f37D616F99628136906fC5355",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::stETH::reservePool",
- "salt": "0x18fb4ce3bb00c1813b5fa5270fae5454ca75c63e8accb2a3fe0e68a7f1b138e1",
- "implementation": {
- "address": "0xd904b61EBa087DfE7d984259B50324Cc1808c88F",
- "contractType": "ReservePool_v1",
- "contractPath": "src/minter/ReservePool_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "REQUESTER_ROLE": {
- "value": 1,
- "grantees": [
- "contracts.minter"
- ]
- }
- }
- },
- "stabilityPoolCollateral": {
- "address": "0x667Ceb303193996697A5938cD6e17255EeAcef51",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::stETH::stabilityPoolCollateral",
- "salt": "0xe4265c8309fc37d77b21232027c23af3a289b980dccb0aa1a7984b6a11b1c44f",
- "implementation": {
- "address": "0x932cAeb990f39B95CeAD349446C767f6bEc0C5d1",
- "contractType": "StabilityPool_v1",
- "contractPath": "src/minter/StabilityPool_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "ownershipModel": "transferred-after-deploy"
- },
- "liquidation": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0",
- "rewardTokens": [
- "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
- ],
- "roles": {
- "REBALANCER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_DEPOSITOR_ROLE": {
- "value": 4,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_MANAGER_ROLE": {
- "value": 1,
- "grantees": []
- }
- }
- },
- "stabilityPoolLeveraged": {
- "address": "0xCB4F3e21DE158bf858Aa03E63e4cEc7342177013",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::stETH::stabilityPoolLeveraged",
- "salt": "0x87b4c173df7272ccea6e40d7fd6904e37fe472c74ce1fefd3a92c8580a6554e6",
- "implementation": {
- "address": "0xb71F39140FAa63a9C32105Ef717aC79a58878025",
- "contractType": "StabilityPool_v1",
- "contractPath": "src/minter/StabilityPool_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "ownershipModel": "transferred-after-deploy"
- },
- "liquidation": "0x817ADaE288eD46B8618AAEffE75ACD26A0a1b0FD",
- "rewardTokens": [
- "0x817ADaE288eD46B8618AAEffE75ACD26A0a1b0FD",
- "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
- ],
- "roles": {
- "REBALANCER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_DEPOSITOR_ROLE": {
- "value": 4,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_MANAGER_ROLE": {
- "value": 1,
- "grantees": []
- }
- }
- },
- "genesis": {
- "address": "0xc64Fc46eED431e92C1b5e24DC296b5985CE6Cc00",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::BTC::stETH::genesis",
- "salt": "0x334094ff574a3863003a3b9fde871b0d870da55d6a96bb80a6fb9ec35b3dae2e",
- "implementation": {
- "address": "0x93E71d996C5ccD1554f1ddAE4977EA857abeb89C",
- "contractType": "Genesis_v1",
- "contractPath": "src/minter/Genesis_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049273,
- "ownershipModel": "transferred-after-deploy"
- }
- }
- },
- "stabilityPoolEarlyWithdrawalFeeRatio": 1e16,
- "stabilityPoolWithdrawalDelay": 3600,
- "stabilityPoolWithdrawalPeriod": 90000,
- "stabilityPoolMinTotalAssetSupply": 1e18,
- "networks": {
- "mainnet": {
- "chainId": 1,
- "collateral": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84",
- "wrappedCollateral": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
- }
- },
- "schemaVersion": 1,
- "systemSaltString": "harbor_v1::BTC::stETH",
- "session": {
- "network": "mainnet",
- "chainId": 1,
- "startTimestamp": 1766179067,
- "started": "2025-12-19T21:17:47Z",
- "startBlock": 24049273,
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "stub": {
- "address": "0x883f8C271fa20B7Fc3795a9DE8c27f4cd45A5802",
- "contractType": "UUPSProxyDeployStub",
- "contractPath": "script/deployment/UUPSProxyDeployStub.sol",
- "blockNumber": 24049273
- },
- "finishTimestamp": 1766179067,
- "finished": "2025-12-19T21:17:47Z",
- "finishBlock": 24049273
- },
- "BaoFactory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "peggedSaltString": "harbor_v1::BTC"
-}
\ No newline at end of file
diff --git a/deployments/harbor_v1::ETH.logs/mainnet/latest.json b/deployments/harbor_v1::ETH.logs/mainnet/latest.json
deleted file mode 100644
index a5af2b5b..00000000
--- a/deployments/harbor_v1::ETH.logs/mainnet/latest.json
+++ /dev/null
@@ -1,61 +0,0 @@
-{
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "prefix": "harbor_v1",
- "peggedTicker": "ETH",
- "treasury": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "schemaVersion": 1,
- "systemSaltString": "harbor_v1::ETH",
- "session": {
- "network": "mainnet",
- "chainId": 1,
- "startTimestamp": 1766181407,
- "started": "2025-12-19T21:56:47Z",
- "startBlock": 24049467,
- "deployer": "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38",
- "stub": {
- "address": "0x1b068953d86b86427C0b6C25a6b3E443ae676475",
- "contractType": "UUPSProxyDeployStub",
- "contractPath": "script/deployment/UUPSProxyDeployStub.sol",
- "blockNumber": 24049467
- },
- "finishTimestamp": 1766181407,
- "finished": "2025-12-19T21:56:47Z",
- "finishBlock": 24049467
- },
- "BaoFactory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "peggedSaltString": "harbor_v1::ETH",
- "contracts": {
- "pegged": {
- "symbol": "haETH",
- "name": "Harbor anchored ETH",
- "address": "0x7A53EBc85453DD006824084c4f4bE758FcF8a5B5",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38",
- "blockNumber": 24049467,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::ETH::pegged",
- "salt": "0x9b06e14844d131d1a1f50199cdbd114c53f11b44bc8fc965bc043c4bd78837d4",
- "implementation": {
- "address": "0x4cD716A3DEe21eCB76B485F4374318507FeC1B75",
- "contractType": "MintableBurnableERC20_v1",
- "contractPath": "lib/bao-base-audit-2025-07/src/MintableBurnableERC20_v1.sol",
- "deployer": "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38",
- "blockNumber": 24049467,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "MINTER_ROLE": {
- "value": 1,
- "grantees": []
- },
- "BURNER_ROLE": {
- "value": 2,
- "grantees": []
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/deployments/harbor_v1::ETH::fxUSD.logs/mainnet/latest.json b/deployments/harbor_v1::ETH::fxUSD.logs/mainnet/latest.json
deleted file mode 100644
index 0d37376f..00000000
--- a/deployments/harbor_v1::ETH::fxUSD.logs/mainnet/latest.json
+++ /dev/null
@@ -1,416 +0,0 @@
-{
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "prefix": "harbor_v1",
- "peggedTicker": "ETH",
- "treasury": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "priceOracle": "0x71437C90F1E0785dd691FD02f7bE0B90cd14c097",
- "contracts": {
- "minterFeeReceiver": {
- "name": "Minter Fee Receiver",
- "tokens": [
- "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- ],
- "recipients": [
- "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2"
- ],
- "shares": [
- 100e16
- ],
- "address": "0xdC903fe5ebCE440f22578D701b95424363D20881",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::ETH::fxUSD::minterFeeReceiver",
- "salt": "0xe172badc81a9aa27a04055f606c3608891f341ebebb6cbf84512b10bbc45020a",
- "implementation": {
- "address": "0xEC0646d6a08A4409908DA1fABc26969AE135EA97",
- "contractType": "TokenDistributor_v1",
- "contractPath": "src/minter/TokenDistributor_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "ownershipModel": "transferred-after-deploy"
- }
- },
- "stabilityPoolManagerFeeReceiver": {
- "name": "StabilityPoolManager Cut Receiver",
- "tokens": [
- "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- ],
- "recipients": [
- "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "0x1F985CF7C10A81DE1940da581208D2855D263D72",
- "0x438B29EC7a1770dDbA37D792F1A6e76231Ef8E06"
- ],
- "shares": [
- 4e16,
- 48e16,
- 48e16
- ],
- "address": "0x9e92965Afb51ce80aa451F93530880f469C2B282",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::ETH::fxUSD::stabilityPoolManagerFeeReceiver",
- "salt": "0x04397281cd39d2cd0984d01751f6a4898bc3b135996d1108dafdb78d5cf8ea2b",
- "implementation": {
- "address": "0xe32bF3a9d68119094d5E388eA5AF226e6d57Cc91",
- "contractType": "TokenDistributor_v1",
- "contractPath": "src/minter/TokenDistributor_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "ownershipModel": "transferred-after-deploy"
- }
- },
- "minter": {
- "config": {
- "mintPeggedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 131e16,
- 140e16
- ],
- "incentiveRatios": [
- 100e16,
- 1e16,
- 0.5e16
- ]
- },
- "redeemPeggedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 100e16,
- 110e16,
- 120e16,
- 140e16
- ],
- "incentiveRatios": [
- -0.5e16,
- -0.5e16,
- 0,
- 0.6e16,
- 0.8e16
- ]
- },
- "mintLeveragedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 100e16,
- 110e16,
- 120e16,
- 140e16
- ],
- "incentiveRatios": [
- -5e16,
- -5e16,
- 0,
- 2e16,
- 7e16
- ]
- },
- "redeemLeveragedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 110e16,
- 140e16
- ],
- "incentiveRatios": [
- 100e16,
- 1.5e16,
- 1.2e16
- ]
- }
- },
- "feeReceiver": "0xdC903fe5ebCE440f22578D701b95424363D20881",
- "address": "0xd6E2F8e57b4aFB51C6fA4cbC012e1cE6aEad989F",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::ETH::fxUSD::minter",
- "salt": "0x250de6376a15d7bc5a5e421e4586662d0e88c20626c2b28a249e33bdd24cbebf",
- "implementation": {
- "address": "0x813ddD349a459137dC2Bf36B8Dc57508B63b5bCF",
- "contractType": "Minter_v1",
- "contractPath": "src/minter/Minter_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "HARVESTER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "ZERO_FEE_ROLE": {
- "value": 1,
- "grantees": [
- "contracts.genesis"
- ]
- }
- }
- },
- "stabilityPoolManager": {
- "rebalanceThreshold": 130e16,
- "rebalanceBountyRatio": 1e16,
- "harvestBountyRatio": 1e16,
- "harvestCutRatio": 100e16,
- "feeReceiver": "0x9e92965Afb51ce80aa451F93530880f469C2B282",
- "address": "0xE39165aDE355988EFb24dA4f2403971101134CAB",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::ETH::fxUSD::stabilityPoolManager",
- "salt": "0x91acb86af3f0e99e30f365a976b233c7523510c58a3c8f721a44dbc8b360dd25",
- "implementation": {
- "address": "0x9C6a6B61b1ac3A344584c3747597964c0DD65C7D",
- "contractType": "StabilityPoolManager_v1",
- "contractPath": "src/minter/StabilityPoolManager_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "ownershipModel": "transferred-after-deploy"
- }
- },
- "collateral": {
- "address": "0x085780639CC2cACd35E474e71f4d000e2405d8f6",
- "symbol": "fxUSD",
- "name": "f(x) USD"
- },
- "wrappedCollateral": {
- "address": "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39",
- "symbol": "fxSAVE",
- "name": "f(x) USD Saving"
- },
- "priceOracle": {
- "address": "0x71437C90F1E0785dd691FD02f7bE0B90cd14c097"
- },
- "pegged": {
- "address": "0x7A53EBc85453DD006824084c4f4bE758FcF8a5B5",
- "symbol": "haETH",
- "name": "Harbor anchored ETH",
- "roles": {
- "MINTER_ROLE": {
- "value": 1,
- "grantees": []
- },
- "BURNER_ROLE": {
- "value": 2,
- "grantees": []
- }
- }
- },
- "leveraged": {
- "symbol": "hsFXUSD-ETH",
- "name": "Harbor sail: variable leveraged long fxUSD against ETH",
- "address": "0x0Cd6BB1a0cfD95e2779EDC6D17b664B481f2EB4C",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::ETH::fxUSD::leveraged",
- "salt": "0xd9072410ee5e87ed3df30e6b97363ec6fa917c6975a559ab28c0164d34d058ae",
- "implementation": {
- "address": "0xEA6555386f7E13D18Ec7c22097eAFa4D80F8bB34",
- "contractType": "MintableBurnableERC20_v1",
- "contractPath": "lib/bao-base-audit-2025-07/src/MintableBurnableERC20_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "MINTER_ROLE": {
- "value": 1,
- "grantees": [
- "contracts.minter"
- ]
- },
- "BURNER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.minter"
- ]
- }
- }
- },
- "reservePool": {
- "address": "0x7A5c4ca972CE2168d5215d252946dDbd1cAd2015",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::ETH::fxUSD::reservePool",
- "salt": "0x186da97d6a8f2be86d2e8003d11aa6f8d5e50ce09cb372c9ddd0899de8e2ac7c",
- "implementation": {
- "address": "0x4816D539cCDE3326A6Ecc8Df1e59570399285223",
- "contractType": "ReservePool_v1",
- "contractPath": "src/minter/ReservePool_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "REQUESTER_ROLE": {
- "value": 1,
- "grantees": [
- "contracts.minter"
- ]
- }
- }
- },
- "stabilityPoolCollateral": {
- "address": "0x1F985CF7C10A81DE1940da581208D2855D263D72",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::ETH::fxUSD::stabilityPoolCollateral",
- "salt": "0x029544fa2b022ced824134813325b24a2c41d616e27d297fc9300c1667da2b27",
- "implementation": {
- "address": "0xa3BAe4ed645bfdD089978C8780a06F1786dD8957",
- "contractType": "StabilityPool_v1",
- "contractPath": "src/minter/StabilityPool_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "ownershipModel": "transferred-after-deploy"
- },
- "liquidation": "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39",
- "rewardTokens": [
- "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- ],
- "roles": {
- "REBALANCER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_DEPOSITOR_ROLE": {
- "value": 4,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_MANAGER_ROLE": {
- "value": 1,
- "grantees": []
- }
- }
- },
- "stabilityPoolLeveraged": {
- "address": "0x438B29EC7a1770dDbA37D792F1A6e76231Ef8E06",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::ETH::fxUSD::stabilityPoolLeveraged",
- "salt": "0x2675b0faad0c54faee5a8b26a9b002dd0e4227bfb08a13c029f45f915dcaf6a9",
- "implementation": {
- "address": "0x12d24Ca4F99b883B26fA7D51847A9fd756455F04",
- "contractType": "StabilityPool_v1",
- "contractPath": "src/minter/StabilityPool_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "ownershipModel": "transferred-after-deploy"
- },
- "liquidation": "0x0Cd6BB1a0cfD95e2779EDC6D17b664B481f2EB4C",
- "rewardTokens": [
- "0x0Cd6BB1a0cfD95e2779EDC6D17b664B481f2EB4C",
- "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- ],
- "roles": {
- "REBALANCER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_DEPOSITOR_ROLE": {
- "value": 4,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_MANAGER_ROLE": {
- "value": 1,
- "grantees": []
- }
- }
- },
- "genesis": {
- "address": "0xC9df4f62474Cf6cdE6c064DB29416a9F4f27EBdC",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::ETH::fxUSD::genesis",
- "salt": "0x0a5f143861590c06ad0b084b4b31347adc826acfd00dc3d2751fec4359316650",
- "implementation": {
- "address": "0x96ED4c4a0D4a82ED649fb15A64C13472D9a28F93",
- "contractType": "Genesis_v1",
- "contractPath": "src/minter/Genesis_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049488,
- "ownershipModel": "transferred-after-deploy"
- }
- }
- },
- "stabilityPoolEarlyWithdrawalFeeRatio": 1e16,
- "stabilityPoolWithdrawalDelay": 3600,
- "stabilityPoolWithdrawalPeriod": 90000,
- "stabilityPoolMinTotalAssetSupply": 1e18,
- "networks": {
- "mainnet": {
- "chainId": 1,
- "collateral": "0x085780639CC2cACd35E474e71f4d000e2405d8f6",
- "wrappedCollateral": "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- }
- },
- "schemaVersion": 1,
- "systemSaltString": "harbor_v1::ETH::fxUSD",
- "session": {
- "network": "mainnet",
- "chainId": 1,
- "startTimestamp": 1766181659,
- "started": "2025-12-19T22:00:59Z",
- "startBlock": 24049488,
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "stub": {
- "address": "0x5d447968FFa3024135547f1DA087D7462a568b9F",
- "contractType": "UUPSProxyDeployStub",
- "contractPath": "script/deployment/UUPSProxyDeployStub.sol",
- "blockNumber": 24049488
- },
- "finishTimestamp": 1766181659,
- "finished": "2025-12-19T22:00:59Z",
- "finishBlock": 24049488
- },
- "BaoFactory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "peggedSaltString": "harbor_v1::ETH"
-}
\ No newline at end of file
diff --git a/deployments/harbor_v1::EUR.logs/mainnet/latest.json b/deployments/harbor_v1::EUR.logs/mainnet/latest.json
deleted file mode 100644
index 64cffe52..00000000
--- a/deployments/harbor_v1::EUR.logs/mainnet/latest.json
+++ /dev/null
@@ -1,61 +0,0 @@
-{
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "prefix": "harbor_v1",
- "peggedTicker": "EUR",
- "treasury": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "schemaVersion": 1,
- "systemSaltString": "harbor_v1::EUR",
- "session": {
- "network": "mainnet",
- "chainId": 1,
- "startTimestamp": 1766182835,
- "started": "2025-12-19T22:20:35Z",
- "startBlock": 24049586,
- "deployer": "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38",
- "stub": {
- "address": "0xaF0126183c8c4987C506FdE2295947bb591dd581",
- "contractType": "UUPSProxyDeployStub",
- "contractPath": "script/deployment/UUPSProxyDeployStub.sol",
- "blockNumber": 24049586
- },
- "finishTimestamp": 1766182835,
- "finished": "2025-12-19T22:20:35Z",
- "finishBlock": 24049586
- },
- "BaoFactory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "peggedSaltString": "harbor_v1::EUR",
- "contracts": {
- "pegged": {
- "symbol": "haEUR",
- "name": "Harbor anchored EUR",
- "address": "0x83Fd69E0FF5767972b46E61C6833408361bF7346",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38",
- "blockNumber": 24049586,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::EUR::pegged",
- "salt": "0x9ad5807e5c2d076c3b51843d2d1600c6e1b5b684fb0fd48f9abab1604566e6ab",
- "implementation": {
- "address": "0xe88A00298279D55718FB5E9d8009C1040c5905f7",
- "contractType": "MintableBurnableERC20_v1",
- "contractPath": "lib/bao-base-audit-2025-07/src/MintableBurnableERC20_v1.sol",
- "deployer": "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38",
- "blockNumber": 24049586,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "MINTER_ROLE": {
- "value": 1,
- "grantees": []
- },
- "BURNER_ROLE": {
- "value": 2,
- "grantees": []
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/deployments/harbor_v1::EUR::fxUSD.logs/mainnet/latest.json b/deployments/harbor_v1::EUR::fxUSD.logs/mainnet/latest.json
deleted file mode 100644
index a88ff07f..00000000
--- a/deployments/harbor_v1::EUR::fxUSD.logs/mainnet/latest.json
+++ /dev/null
@@ -1,416 +0,0 @@
-{
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "prefix": "harbor_v1",
- "peggedTicker": "EUR",
- "treasury": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "priceOracle": "0x6bEb1a1189Ac68a2a26b5210e5ccfB9e8a3E408E",
- "contracts": {
- "minterFeeReceiver": {
- "name": "Minter Fee Receiver",
- "tokens": [
- "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- ],
- "recipients": [
- "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2"
- ],
- "shares": [
- 100e16
- ],
- "address": "0x43dfDB5059777A8B8819d8D8ff2c9ACCFEb766CB",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::EUR::fxUSD::minterFeeReceiver",
- "salt": "0xca85b4788fa47c3b3a54ec45780a0851a6b76278aad8827f8e4436efa22ca6fc",
- "implementation": {
- "address": "0x07F9194fE7c847472Ef6D984DdE0D447fd4b76B1",
- "contractType": "TokenDistributor_v1",
- "contractPath": "src/minter/TokenDistributor_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "ownershipModel": "transferred-after-deploy"
- }
- },
- "stabilityPoolManagerFeeReceiver": {
- "name": "StabilityPoolManager Cut Receiver",
- "tokens": [
- "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- ],
- "recipients": [
- "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "0xe60054E6b518f67411834282cE1557381f050B13",
- "0xc5e0dA7e0a178850438E5E97ed59b6eb2562e88E"
- ],
- "shares": [
- 4e16,
- 48e16,
- 48e16
- ],
- "address": "0xd2a815B2210c15E1626CD0D487C77852E7C37b17",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::EUR::fxUSD::stabilityPoolManagerFeeReceiver",
- "salt": "0x2ae547e6fe90b27d3402022c2c92d747b4fce0bd1b9d477c6ff8c505ce974f0b",
- "implementation": {
- "address": "0xC266C83e5474756eA904987D63450b2E0CF6c0C1",
- "contractType": "TokenDistributor_v1",
- "contractPath": "src/minter/TokenDistributor_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "ownershipModel": "transferred-after-deploy"
- }
- },
- "minter": {
- "config": {
- "mintPeggedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 131e16,
- 140e16
- ],
- "incentiveRatios": [
- 100e16,
- 1e16,
- 0.5e16
- ]
- },
- "redeemPeggedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 100e16,
- 110e16,
- 120e16,
- 140e16
- ],
- "incentiveRatios": [
- -0.5e16,
- -0.5e16,
- 0,
- 0.6e16,
- 0.8e16
- ]
- },
- "mintLeveragedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 100e16,
- 110e16,
- 120e16,
- 140e16
- ],
- "incentiveRatios": [
- -5e16,
- -5e16,
- 0,
- 2e16,
- 7e16
- ]
- },
- "redeemLeveragedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 110e16,
- 140e16
- ],
- "incentiveRatios": [
- 100e16,
- 1.5e16,
- 1.2e16
- ]
- }
- },
- "feeReceiver": "0x43dfDB5059777A8B8819d8D8ff2c9ACCFEb766CB",
- "address": "0xDEFB2C04062350678965CBF38A216Cc50723B246",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::EUR::fxUSD::minter",
- "salt": "0x526b695c8df13bfd1c3349b2a2e670e2167bab85ad99fecbb6613df132c9577b",
- "implementation": {
- "address": "0xc7E34ecD57975430aB9DDb535Df4f266Ba2Ec1d1",
- "contractType": "Minter_v1",
- "contractPath": "src/minter/Minter_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "HARVESTER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "ZERO_FEE_ROLE": {
- "value": 1,
- "grantees": [
- "contracts.genesis"
- ]
- }
- }
- },
- "stabilityPoolManager": {
- "rebalanceThreshold": 130e16,
- "rebalanceBountyRatio": 1e16,
- "harvestBountyRatio": 1e16,
- "harvestCutRatio": 100e16,
- "feeReceiver": "0xd2a815B2210c15E1626CD0D487C77852E7C37b17",
- "address": "0x756766756880ceA06270Fd507b09Ef32714Ec7C2",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::EUR::fxUSD::stabilityPoolManager",
- "salt": "0xd260a0cb73372c8cf7a912a5a5b7febcd7145fdcd005ef914341dbcf8a929103",
- "implementation": {
- "address": "0xF625E8147C07DDF5a488FCB5C95c91ACef46E22E",
- "contractType": "StabilityPoolManager_v1",
- "contractPath": "src/minter/StabilityPoolManager_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "ownershipModel": "transferred-after-deploy"
- }
- },
- "collateral": {
- "address": "0x085780639CC2cACd35E474e71f4d000e2405d8f6",
- "symbol": "fxUSD",
- "name": "f(x) USD"
- },
- "wrappedCollateral": {
- "address": "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39",
- "symbol": "fxSAVE",
- "name": "f(x) USD Saving"
- },
- "priceOracle": {
- "address": "0x6bEb1a1189Ac68a2a26b5210e5ccfB9e8a3E408E"
- },
- "pegged": {
- "address": "0x83Fd69E0FF5767972b46E61C6833408361bF7346",
- "symbol": "haEUR",
- "name": "Harbor anchored EUR",
- "roles": {
- "MINTER_ROLE": {
- "value": 1,
- "grantees": []
- },
- "BURNER_ROLE": {
- "value": 2,
- "grantees": []
- }
- }
- },
- "leveraged": {
- "symbol": "hsFXUSD-EUR",
- "name": "Harbor sail: variable leveraged long fxUSD against EUR",
- "address": "0x7A7C1f2502c19193C44662A2Aff51c2B76fDDAEA",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::EUR::fxUSD::leveraged",
- "salt": "0x36844f05e5c66112c7b102625416f6bd1c56eeb6fb563254347cbf78eeee50ca",
- "implementation": {
- "address": "0xe86c568aDEd105d7A63fe63e2C12Dfa567cBf2e3",
- "contractType": "MintableBurnableERC20_v1",
- "contractPath": "lib/bao-base-audit-2025-07/src/MintableBurnableERC20_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "MINTER_ROLE": {
- "value": 1,
- "grantees": [
- "contracts.minter"
- ]
- },
- "BURNER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.minter"
- ]
- }
- }
- },
- "reservePool": {
- "address": "0x27cA37538358F90d45cAA886fB58CC08ffe2dD2f",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::EUR::fxUSD::reservePool",
- "salt": "0x84d47cf79f4a6cf9f2cd65929ffa098b72405b63440eee3c9a81eb40e01e0b8b",
- "implementation": {
- "address": "0xDfFb26e9f81Cbc8B9b88c2e2DDeEe770fe765EFd",
- "contractType": "ReservePool_v1",
- "contractPath": "src/minter/ReservePool_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "REQUESTER_ROLE": {
- "value": 1,
- "grantees": [
- "contracts.minter"
- ]
- }
- }
- },
- "stabilityPoolCollateral": {
- "address": "0xe60054E6b518f67411834282cE1557381f050B13",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::EUR::fxUSD::stabilityPoolCollateral",
- "salt": "0x386fbf8c15f8c887b79aa477f69299069c6a0e74a5e922eaed0ca293a8c1de6d",
- "implementation": {
- "address": "0x5A2440034cc32A298e44B34247a83508ef42cCF4",
- "contractType": "StabilityPool_v1",
- "contractPath": "src/minter/StabilityPool_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "ownershipModel": "transferred-after-deploy"
- },
- "liquidation": "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39",
- "rewardTokens": [
- "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- ],
- "roles": {
- "REBALANCER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_DEPOSITOR_ROLE": {
- "value": 4,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_MANAGER_ROLE": {
- "value": 1,
- "grantees": []
- }
- }
- },
- "stabilityPoolLeveraged": {
- "address": "0xc5e0dA7e0a178850438E5E97ed59b6eb2562e88E",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::EUR::fxUSD::stabilityPoolLeveraged",
- "salt": "0x343001125245f67667b03e570b1ca4633ee53d64c89ccc4fd01509cb5e7fbfe6",
- "implementation": {
- "address": "0xEA8e632d20235c7450C9d49fbf868bdb2B981df8",
- "contractType": "StabilityPool_v1",
- "contractPath": "src/minter/StabilityPool_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "ownershipModel": "transferred-after-deploy"
- },
- "liquidation": "0x7A7C1f2502c19193C44662A2Aff51c2B76fDDAEA",
- "rewardTokens": [
- "0x7A7C1f2502c19193C44662A2Aff51c2B76fDDAEA",
- "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- ],
- "roles": {
- "REBALANCER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_DEPOSITOR_ROLE": {
- "value": 4,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_MANAGER_ROLE": {
- "value": 1,
- "grantees": []
- }
- }
- },
- "genesis": {
- "address": "0xa9EB43Ed6Ba3B953a82741F3e226C1d6B029699b",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::EUR::fxUSD::genesis",
- "salt": "0xdebfa396f067b9183358033b6f096e754b4f91d714ed202e6b6922899fe6a72d",
- "implementation": {
- "address": "0x6d43EE4F28E6f25871B34a16cAC500DA54132B30",
- "contractType": "Genesis_v1",
- "contractPath": "src/minter/Genesis_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049601,
- "ownershipModel": "transferred-after-deploy"
- }
- }
- },
- "stabilityPoolEarlyWithdrawalFeeRatio": 1e16,
- "stabilityPoolWithdrawalDelay": 3600,
- "stabilityPoolWithdrawalPeriod": 90000,
- "stabilityPoolMinTotalAssetSupply": 1e18,
- "networks": {
- "mainnet": {
- "chainId": 1,
- "collateral": "0x085780639CC2cACd35E474e71f4d000e2405d8f6",
- "wrappedCollateral": "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- }
- },
- "schemaVersion": 1,
- "systemSaltString": "harbor_v1::EUR::fxUSD",
- "session": {
- "network": "mainnet",
- "chainId": 1,
- "startTimestamp": 1766183015,
- "started": "2025-12-19T22:23:35Z",
- "startBlock": 24049601,
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "stub": {
- "address": "0x02AEA8553D3FE13a8F1b5e18Bffe9A30D6c47d69",
- "contractType": "UUPSProxyDeployStub",
- "contractPath": "script/deployment/UUPSProxyDeployStub.sol",
- "blockNumber": 24049601
- },
- "finishTimestamp": 1766183015,
- "finished": "2025-12-19T22:23:35Z",
- "finishBlock": 24049601
- },
- "BaoFactory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "peggedSaltString": "harbor_v1::EUR"
-}
\ No newline at end of file
diff --git a/deployments/harbor_v1::GOLD.logs/mainnet/latest.json b/deployments/harbor_v1::GOLD.logs/mainnet/latest.json
deleted file mode 100644
index a082818d..00000000
--- a/deployments/harbor_v1::GOLD.logs/mainnet/latest.json
+++ /dev/null
@@ -1,61 +0,0 @@
-{
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "prefix": "harbor_v1",
- "peggedTicker": "GOLD",
- "treasury": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "schemaVersion": 1,
- "systemSaltString": "harbor_v1::GOLD",
- "session": {
- "network": "mainnet",
- "chainId": 1,
- "startTimestamp": 1766184299,
- "started": "2025-12-19T22:44:59Z",
- "startBlock": 24049708,
- "deployer": "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38",
- "stub": {
- "address": "0xF770170fa251de2E5b531907D2D636A1294cEE06",
- "contractType": "UUPSProxyDeployStub",
- "contractPath": "script/deployment/UUPSProxyDeployStub.sol",
- "blockNumber": 24049708
- },
- "finishTimestamp": 1766184299,
- "finished": "2025-12-19T22:44:59Z",
- "finishBlock": 24049708
- },
- "BaoFactory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "peggedSaltString": "harbor_v1::GOLD",
- "contracts": {
- "pegged": {
- "symbol": "haGOLD",
- "name": "Harbor anchored GOLD",
- "address": "0x5b66D86932aE5D9751da588d91D494950554061d",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38",
- "blockNumber": 24049708,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::GOLD::pegged",
- "salt": "0x637ca0469892ce1de8f6f03871bda6c915430aa85861376a64b7efac8b4f8bd1",
- "implementation": {
- "address": "0x28eB6581253Ae4F9215b01F7e723Bd465fa46e2b",
- "contractType": "MintableBurnableERC20_v1",
- "contractPath": "lib/bao-base-audit-2025-07/src/MintableBurnableERC20_v1.sol",
- "deployer": "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38",
- "blockNumber": 24049708,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "MINTER_ROLE": {
- "value": 1,
- "grantees": []
- },
- "BURNER_ROLE": {
- "value": 2,
- "grantees": []
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/deployments/harbor_v1::GOLD::fxUSD.logs/mainnet/latest.json b/deployments/harbor_v1::GOLD::fxUSD.logs/mainnet/latest.json
deleted file mode 100644
index 481208ab..00000000
--- a/deployments/harbor_v1::GOLD::fxUSD.logs/mainnet/latest.json
+++ /dev/null
@@ -1,416 +0,0 @@
-{
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "prefix": "harbor_v1",
- "peggedTicker": "GOLD",
- "treasury": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "priceOracle": "0x7DAe17B00DCd5C37D4992a17C3Cf8f5E15d2BbAf",
- "contracts": {
- "minterFeeReceiver": {
- "name": "Minter Fee Receiver",
- "tokens": [
- "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- ],
- "recipients": [
- "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2"
- ],
- "shares": [
- 100e16
- ],
- "address": "0x8C5EF0342543A509e5548c71A66dE7D8A69c6B70",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::GOLD::fxUSD::minterFeeReceiver",
- "salt": "0xb536434509b530262cb785ba8fad981eb35684d643203391d1db756c2d2fc07a",
- "implementation": {
- "address": "0x99f3CAC5F7a3c91134Dccd523560F553BA286E1b",
- "contractType": "TokenDistributor_v1",
- "contractPath": "src/minter/TokenDistributor_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "ownershipModel": "transferred-after-deploy"
- }
- },
- "stabilityPoolManagerFeeReceiver": {
- "name": "StabilityPoolManager Cut Receiver",
- "tokens": [
- "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- ],
- "recipients": [
- "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "0xC1EF32d4B959F2200efDeDdedadA226461d14DaC",
- "0x5bDED171f1c08B903b466593B0E022F9FdE8399c"
- ],
- "shares": [
- 4e16,
- 48e16,
- 48e16
- ],
- "address": "0x360838316494E355CE7a58c2990606F30F21e8A1",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::GOLD::fxUSD::stabilityPoolManagerFeeReceiver",
- "salt": "0x19ca455e55841d2a5391774eef49cdbb558176096c24f8ca3ff0880598774a4d",
- "implementation": {
- "address": "0xa5c42eC86DD26603a3cd48Cf95abDAc7E1D14B70",
- "contractType": "TokenDistributor_v1",
- "contractPath": "src/minter/TokenDistributor_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "ownershipModel": "transferred-after-deploy"
- }
- },
- "minter": {
- "config": {
- "mintPeggedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 131e16,
- 140e16
- ],
- "incentiveRatios": [
- 100e16,
- 1e16,
- 0.5e16
- ]
- },
- "redeemPeggedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 100e16,
- 110e16,
- 120e16,
- 140e16
- ],
- "incentiveRatios": [
- -0.5e16,
- -0.5e16,
- 0,
- 0.6e16,
- 0.8e16
- ]
- },
- "mintLeveragedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 100e16,
- 110e16,
- 120e16,
- 140e16
- ],
- "incentiveRatios": [
- -5e16,
- -5e16,
- 0,
- 2e16,
- 7e16
- ]
- },
- "redeemLeveragedIncentiveConfig": {
- "collateralRatioBandUpperBounds": [
- 110e16,
- 140e16
- ],
- "incentiveRatios": [
- 100e16,
- 1.5e16,
- 1.2e16
- ]
- }
- },
- "feeReceiver": "0x8C5EF0342543A509e5548c71A66dE7D8A69c6B70",
- "address": "0x880600E0c803d836E305B7c242FC095Eed234A8f",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::GOLD::fxUSD::minter",
- "salt": "0x1a99836c05a72c340080ac7226c422db25a17c5f9cdc4736726609bcf14dc596",
- "implementation": {
- "address": "0xC2ee9f547123990e513403111F314B520C1DD812",
- "contractType": "Minter_v1",
- "contractPath": "src/minter/Minter_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "HARVESTER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "ZERO_FEE_ROLE": {
- "value": 1,
- "grantees": [
- "contracts.genesis"
- ]
- }
- }
- },
- "stabilityPoolManager": {
- "rebalanceThreshold": 130e16,
- "rebalanceBountyRatio": 1e16,
- "harvestBountyRatio": 1e16,
- "harvestCutRatio": 100e16,
- "feeReceiver": "0x360838316494E355CE7a58c2990606F30F21e8A1",
- "address": "0x5b69069CC4012a96342B0FeCC28aD15bDE6447B5",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::GOLD::fxUSD::stabilityPoolManager",
- "salt": "0x8af3b8d227b4b43a9a024a8d02a8315e56bd7755642e83a163fa2979b6a368db",
- "implementation": {
- "address": "0x5c96077BB55376b66670B937F7bBdDBBc63A8564",
- "contractType": "StabilityPoolManager_v1",
- "contractPath": "src/minter/StabilityPoolManager_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "ownershipModel": "transferred-after-deploy"
- }
- },
- "collateral": {
- "address": "0x085780639CC2cACd35E474e71f4d000e2405d8f6",
- "symbol": "fxUSD",
- "name": "f(x) USD"
- },
- "wrappedCollateral": {
- "address": "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39",
- "symbol": "fxSAVE",
- "name": "f(x) USD Saving"
- },
- "priceOracle": {
- "address": "0x7DAe17B00DCd5C37D4992a17C3Cf8f5E15d2BbAf"
- },
- "pegged": {
- "address": "0x5b66D86932aE5D9751da588d91D494950554061d",
- "symbol": "haGOLD",
- "name": "Harbor anchored GOLD",
- "roles": {
- "MINTER_ROLE": {
- "value": 1,
- "grantees": []
- },
- "BURNER_ROLE": {
- "value": 2,
- "grantees": []
- }
- }
- },
- "leveraged": {
- "symbol": "hsFXUSD-GOLD",
- "name": "Harbor sail: variable leveraged long fxUSD against GOLD",
- "address": "0x85730Af3A7d7A872Ee1D84306E0575f1E00C0980",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::GOLD::fxUSD::leveraged",
- "salt": "0xfcf531145f6e72451817e7b4b5e1934d1acede5469eab149ce45054ba770c545",
- "implementation": {
- "address": "0xaCA783ba4D58b78371D0b5822E81Eeb42194DF2c",
- "contractType": "MintableBurnableERC20_v1",
- "contractPath": "lib/bao-base-audit-2025-07/src/MintableBurnableERC20_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "MINTER_ROLE": {
- "value": 1,
- "grantees": [
- "contracts.minter"
- ]
- },
- "BURNER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.minter"
- ]
- }
- }
- },
- "reservePool": {
- "address": "0xc033e81ED555D6db63A3E0Af9795454C7BdF094a",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::GOLD::fxUSD::reservePool",
- "salt": "0xd956bc2ad37579dfea85b1600568fe852c60a61df530aa42a2abee2fc3b54b21",
- "implementation": {
- "address": "0x4b5996034C1B888ac70bB4C5687E6faF5DddFeF8",
- "contractType": "ReservePool_v1",
- "contractPath": "src/minter/ReservePool_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "ownershipModel": "transferred-after-deploy"
- },
- "roles": {
- "REQUESTER_ROLE": {
- "value": 1,
- "grantees": [
- "contracts.minter"
- ]
- }
- }
- },
- "stabilityPoolCollateral": {
- "address": "0xC1EF32d4B959F2200efDeDdedadA226461d14DaC",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::GOLD::fxUSD::stabilityPoolCollateral",
- "salt": "0x85553077e4c7223d2fc629843217fbeed94b5529eb4fc91d85c2ec7f1c6666bd",
- "implementation": {
- "address": "0xA041d39ceD4aBAE2e50427712653f8a79d08bd2D",
- "contractType": "StabilityPool_v1",
- "contractPath": "src/minter/StabilityPool_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "ownershipModel": "transferred-after-deploy"
- },
- "liquidation": "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39",
- "rewardTokens": [
- "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- ],
- "roles": {
- "REBALANCER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_DEPOSITOR_ROLE": {
- "value": 4,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_MANAGER_ROLE": {
- "value": 1,
- "grantees": []
- }
- }
- },
- "stabilityPoolLeveraged": {
- "address": "0x5bDED171f1c08B903b466593B0E022F9FdE8399c",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::GOLD::fxUSD::stabilityPoolLeveraged",
- "salt": "0x90006792de99ff6cd0caa69edaa281c697a668fcd47b420ec81b705a2fc751bf",
- "implementation": {
- "address": "0x64647EA21a5750E406cA90A114639D6b1388A904",
- "contractType": "StabilityPool_v1",
- "contractPath": "src/minter/StabilityPool_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "ownershipModel": "transferred-after-deploy"
- },
- "liquidation": "0x85730Af3A7d7A872Ee1D84306E0575f1E00C0980",
- "rewardTokens": [
- "0x85730Af3A7d7A872Ee1D84306E0575f1E00C0980",
- "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- ],
- "roles": {
- "REBALANCER_ROLE": {
- "value": 2,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_DEPOSITOR_ROLE": {
- "value": 4,
- "grantees": [
- "contracts.stabilityPoolManager"
- ]
- },
- "REWARD_MANAGER_ROLE": {
- "value": 1,
- "grantees": []
- }
- }
- },
- "genesis": {
- "address": "0x2cbF457112Ef5A16cfcA10Fb173d56a5cc9DAa66",
- "contractType": "ERC1967Proxy",
- "contractPath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "factory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "owner": "0x9bABfC1A1952a6ed2caC1922BFfE80c0506364a2",
- "category": "UUPS proxy",
- "saltString": "harbor_v1::GOLD::fxUSD::genesis",
- "salt": "0x26c227a747434401301023ac13e0c57d8aec0eecc149c5c93fdf940f54d93141",
- "implementation": {
- "address": "0xe08d21418ED9078fE0292602DAD28BA9347312EA",
- "contractType": "Genesis_v1",
- "contractPath": "src/minter/Genesis_v1.sol",
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "blockNumber": 24049725,
- "ownershipModel": "transferred-after-deploy"
- }
- }
- },
- "stabilityPoolEarlyWithdrawalFeeRatio": 1e16,
- "stabilityPoolWithdrawalDelay": 3600,
- "stabilityPoolWithdrawalPeriod": 90000,
- "stabilityPoolMinTotalAssetSupply": 1e18,
- "networks": {
- "mainnet": {
- "chainId": 1,
- "collateral": "0x085780639CC2cACd35E474e71f4d000e2405d8f6",
- "wrappedCollateral": "0x7743e50F534a7f9F1791DdE7dCD89F7783Eefc39"
- }
- },
- "schemaVersion": 1,
- "systemSaltString": "harbor_v1::GOLD::fxUSD",
- "session": {
- "network": "mainnet",
- "chainId": 1,
- "startTimestamp": 1766184503,
- "started": "2025-12-19T22:48:23Z",
- "startBlock": 24049725,
- "deployer": "0x61c533E213fE1a19975101369B9f7E7890196290",
- "stub": {
- "address": "0x41fc7Dc92918F386eEeDfC4B1b81E3A9277Af99f",
- "contractType": "UUPSProxyDeployStub",
- "contractPath": "script/deployment/UUPSProxyDeployStub.sol",
- "blockNumber": 24049725
- },
- "finishTimestamp": 1766184503,
- "finished": "2025-12-19T22:48:23Z",
- "finishBlock": 24049725
- },
- "BaoFactory": "0xD696E56b3A054734d4C6DCBD32E11a278b0EC458",
- "peggedSaltString": "harbor_v1::GOLD"
-}
\ No newline at end of file
diff --git a/deployments/mainnet/batch/Migrate_StabilityPool_v2_Data_ETH_mainnet_2026-06-08T12:23:30Z_harbor_multisig.json b/deployments/mainnet/batch/Migrate_StabilityPool_v2_Data_ETH_mainnet_2026-06-08T12:23:30Z_harbor_multisig.json
new file mode 100644
index 00000000..2309a65b
--- /dev/null
+++ b/deployments/mainnet/batch/Migrate_StabilityPool_v2_Data_ETH_mainnet_2026-06-08T12:23:30Z_harbor_multisig.json
@@ -0,0 +1,15 @@
+{
+ "version": "1.0",
+ "chainId": "1",
+ "createdAt": 1780921403000,
+ "meta": {
+ "description": "ETH Migrate_StabilityPool_v2_Data_mainnet --salt harbor_v1"
+ },
+ "transactions": [
+ {
+ "to": "0x438B29EC7a1770dDbA37D792F1A6e76231Ef8E06",
+ "value": "0",
+ "data": "0x4f1ef286000000000000000000000000bbf35003894bfd2ca8a88f7f09a2ec85fa711dff000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001a42bbf43aa000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000006c0d48839a0b1c9d79ddd4ad3f407709e0f44be100000000000000000000000000000000000000000000000000000000000000020000000000000000000000007743e50f534a7f9f1791dde7dcd89f7783eefc390000000000000000000000000cd6bb1a0cfd95e2779edc6d17b664b481f2eb4c000000000000000000000000000000000000000000000000000000000000000600000000000000000000000008453ddd21cae8f430a07eb8c82e0520882e82c10000000000000000000000001e085ff3cdd38b1e5f04ace2345966056f0c85e40000000000000000000000006453e9d859fe2578dfc4aa8d3ec7b3a80574c00f0000000000000000000000009dd897df19ffc27d6685e98accc394f88a73e475000000000000000000000000ae7dbb17bc40d53a6363409c6b1ed88d3cfdc31e000000000000000000000000ba26035b9cd76cda5b767a966b8a3392e476fc0f00000000000000000000000000000000000000000000000000000000"
+ }
+ ]
+}
diff --git a/deployments/mainnet/batch/Migrate_StabilityPool_v2_Data_rest_mainnet_2026-06-08T12:23:30Z_harbor_multisig.json b/deployments/mainnet/batch/Migrate_StabilityPool_v2_Data_rest_mainnet_2026-06-08T12:23:30Z_harbor_multisig.json
new file mode 100644
index 00000000..4ccaf3ed
--- /dev/null
+++ b/deployments/mainnet/batch/Migrate_StabilityPool_v2_Data_rest_mainnet_2026-06-08T12:23:30Z_harbor_multisig.json
@@ -0,0 +1,40 @@
+{
+ "version": "1.0",
+ "chainId": "1",
+ "createdAt": 1780921403000,
+ "meta": {
+ "description": "rest Migrate_StabilityPool_v2_Data_mainnet --salt harbor_v1"
+ },
+ "transactions": [
+ {
+ "to": "0x86561cdB34ebe8B9abAbb0DD7bEA299fA8532a49",
+ "value": "0",
+ "data": "0x4f1ef286000000000000000000000000bbf35003894bfd2ca8a88f7f09a2ec85fa711dff000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001442bbf43aa000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000fa20abe7f5b32af6871414b7b71f780ecfdb149700000000000000000000000000000000000000000000000000000000000000010000000000000000000000007743e50f534a7f9f1791dde7dcd89f7783eefc390000000000000000000000000000000000000000000000000000000000000004000000000000000000000000061b84fde0aa74ecbf8ecdb0481576fee9ae35aa0000000000000000000000009dd897df19ffc27d6685e98accc394f88a73e475000000000000000000000000ae7dbb17bc40d53a6363409c6b1ed88d3cfdc31e000000000000000000000000ba26035b9cd76cda5b767a966b8a3392e476fc0f00000000000000000000000000000000000000000000000000000000"
+ },
+ {
+ "to": "0x9e56F1E1E80EBf165A1dAa99F9787B41cD5bFE40",
+ "value": "0",
+ "data": "0x4f1ef286000000000000000000000000bbf35003894bfd2ca8a88f7f09a2ec85fa711dff000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001642bbf43aa000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000afe9df05bba2d93e78920860e4372ef912b523c700000000000000000000000000000000000000000000000000000000000000020000000000000000000000007743e50f534a7f9f1791dde7dcd89f7783eefc390000000000000000000000009567c243f647f9ac37efb7fc26bd9551dce0be1b00000000000000000000000000000000000000000000000000000000000000040000000000000000000000005de79e0c5632056b9fb19a740ce0f3ef03adeeb3000000000000000000000000742fc5146d7ff18291e3b7499811ad87015fc7e40000000000000000000000009dd897df19ffc27d6685e98accc394f88a73e475000000000000000000000000ba26035b9cd76cda5b767a966b8a3392e476fc0f00000000000000000000000000000000000000000000000000000000"
+ },
+ {
+ "to": "0x667Ceb303193996697A5938cD6e17255EeAcef51",
+ "value": "0",
+ "data": "0x4f1ef286000000000000000000000000bbf35003894bfd2ca8a88f7f09a2ec85fa711dff000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001a42bbf43aa000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000005acecff04bb9e50630c96b083b367440cbdd933900000000000000000000000000000000000000000000000000000000000000010000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca00000000000000000000000000000000000000000000000000000000000000007000000000000000000000000742fc5146d7ff18291e3b7499811ad87015fc7e40000000000000000000000008b7698945dbcedf33f5e8d9e62b1af8101318575000000000000000000000000ae7dbb17bc40d53a6363409c6b1ed88d3cfdc31e000000000000000000000000ba26035b9cd76cda5b767a966b8a3392e476fc0f000000000000000000000000e81003515b5c537cd4ee57a41dbecf01ff429135000000000000000000000000ecffe7b0e11935cdb4f2107ff22140b67d36dced000000000000000000000000ee517179cef2e52af9d52667c7c9a43a5093f77500000000000000000000000000000000000000000000000000000000"
+ },
+ {
+ "to": "0xCB4F3e21DE158bf858Aa03E63e4cEc7342177013",
+ "value": "0",
+ "data": "0x4f1ef286000000000000000000000000bbf35003894bfd2ca8a88f7f09a2ec85fa711dff000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001642bbf43aa000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000fc9929eabc8264a989593a2f851ef74363ce888f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca0000000000000000000000000817adae288ed46b8618aaeffe75acd26a0a1b0fd00000000000000000000000000000000000000000000000000000000000000040000000000000000000000009dd897df19ffc27d6685e98accc394f88a73e475000000000000000000000000a01ad5f0b5c521266d1dd3f926da92f2cd9661e2000000000000000000000000ae7dbb17bc40d53a6363409c6b1ed88d3cfdc31e000000000000000000000000dc9cdd31bc0f598e6b2c8302312b40852f636e6000000000000000000000000000000000000000000000000000000000"
+ },
+ {
+ "to": "0x1F985CF7C10A81DE1940da581208D2855D263D72",
+ "value": "0",
+ "data": "0x4f1ef286000000000000000000000000bbf35003894bfd2ca8a88f7f09a2ec85fa711dff000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001642bbf43aa000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007e2348c9fbf9008483ff61182095d3de65b3e0b900000000000000000000000000000000000000000000000000000000000000010000000000000000000000007743e50f534a7f9f1791dde7dcd89f7783eefc3900000000000000000000000000000000000000000000000000000000000000050000000000000000000000004daf8ce9d729ca4f121381ec4b22123627c1c004000000000000000000000000742fc5146d7ff18291e3b7499811ad87015fc7e40000000000000000000000008b7698945dbcedf33f5e8d9e62b1af8101318575000000000000000000000000ae7dbb17bc40d53a6363409c6b1ed88d3cfdc31e000000000000000000000000ba26035b9cd76cda5b767a966b8a3392e476fc0f00000000000000000000000000000000000000000000000000000000"
+ },
+ {
+ "to": "0xe60054E6b518f67411834282cE1557381f050B13",
+ "value": "0",
+ "data": "0x4f1ef286000000000000000000000000bbf35003894bfd2ca8a88f7f09a2ec85fa711dff000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e42bbf43aa000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000509a33f3594e7dab8ad8252915d562d01f9557f400000000000000000000000000000000000000000000000000000000000000010000000000000000000000007743e50f534a7f9f1791dde7dcd89f7783eefc39000000000000000000000000000000000000000000000000000000000000000100000000000000000000000001878d9dbd9f27ba4cdbd19406ec158349bfadeb00000000000000000000000000000000000000000000000000000000"
+ }
+ ]
+}
diff --git a/deployments/mainnet/batch/UpdateHarvestCut_MCAP_2026-08-05T11:42:54Z_harbor_multisig.json b/deployments/mainnet/batch/UpdateHarvestCut_MCAP_2026-08-05T11:42:54Z_harbor_multisig.json
new file mode 100644
index 00000000..96ecad2f
--- /dev/null
+++ b/deployments/mainnet/batch/UpdateHarvestCut_MCAP_2026-08-05T11:42:54Z_harbor_multisig.json
@@ -0,0 +1,20 @@
+{
+ "version": "1.0",
+ "chainId": "1",
+ "createdAt": 1785929872000,
+ "meta": {
+ "description": "UpdateHarvestCut_MCAP --salt harbor_v1"
+ },
+ "transactions": [
+ {
+ "to": "0x52DC69cbdC6Ef508b7419A456dD36967DAEfD538",
+ "value": "0",
+ "data": "0x3ab51d600000000000000000000000000000000000000000000000000dbd2fc137a30000"
+ },
+ {
+ "to": "0x1298ab1957ee023E228d57bE2db73494b649E52F",
+ "value": "0",
+ "data": "0x3ab51d600000000000000000000000000000000000000000000000000dbd2fc137a30000"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/deployments/mainnet/harbor_v1.state.json b/deployments/mainnet/harbor_v1.state.json
index c4186b84..10658053 100644
--- a/deployments/mainnet/harbor_v1.state.json
+++ b/deployments/mainnet/harbor_v1.state.json
@@ -13,6 +13,12 @@
"contractType": "BaoPauser_v1",
"deploymentTime": "2026-03-21T13:44:18Z"
},
+ "0xBBF35003894bFd2Ca8A88F7f09A2EC85fa711DfF": {
+ "proxy": "*",
+ "contractSource": "script/Migrate_StabilityPool_v2_Data_mainnet/ForceMigrateAccumulator_v1.sol",
+ "contractType": "ForceMigrateAccumulator_v1",
+ "deploymentTime": "2026-06-08T12:23:59Z"
+ },
"0x1aE2baBA0c81CA98ae1F6B1dB87A367b8f13E112": {
"proxy": "ETH::fxUSD::stabilityPoolLeveraged",
"contractSource": "@harbor/minter/PostRebalanceRemediationForStabilityPool_v2.sol",
diff --git a/src/price/PriceOracle_v1.sol b/deprecated/src/price/PriceOracle_v1.sol
similarity index 100%
rename from src/price/PriceOracle_v1.sol
rename to deprecated/src/price/PriceOracle_v1.sol
diff --git a/src/price/StakedETHWrappedPriceOracle_v1.sol b/deprecated/src/price/StakedETHWrappedPriceOracle_v1.sol
similarity index 100%
rename from src/price/StakedETHWrappedPriceOracle_v1.sol
rename to deprecated/src/price/StakedETHWrappedPriceOracle_v1.sol
diff --git a/test/price/PriceOracle.t.sol b/deprecated/test/price/PriceOracle.t.sol
similarity index 89%
rename from test/price/PriceOracle.t.sol
rename to deprecated/test/price/PriceOracle.t.sol
index 57fa0157..92d39ac5 100644
--- a/test/price/PriceOracle.t.sol
+++ b/deprecated/test/price/PriceOracle.t.sol
@@ -2,9 +2,9 @@
pragma solidity >=0.8.28 <0.9.0;
import {Test} from "forge-std/Test.sol";
-import {PriceOracle_v1} from "src/price/PriceOracle_v1.sol";
-import {IPriceOracleErrors} from "src/interfaces/IPriceOracleErrors.sol";
-import {MockAggregator} from "test/mocks/MockAggregator.sol";
+import {PriceOracle_v1} from "@harbor/price/PriceOracle_v1.sol";
+import {IPriceOracleErrors} from "@harbor/interfaces/IPriceOracleErrors.sol";
+import {MockAggregator} from "@harbor-test/mocks/MockAggregator.sol";
contract PriceOracleTest is Test {
uint64 constant MAX_ANSWER_AGE = 3600; // 1 hour
@@ -138,26 +138,6 @@ contract PriceOracleTest is Test {
PriceOracle_v1.latestAnswer(PriceOracle_v1.Feed({priceFeed: mockFeed, decimals: decimals}), _constraints());
}
- function test_TrendReversalDetection() public {
- vm.skip(true); // TODO: functionality commented out for now until we decide we need this
- uint8 decimals = 8;
- MockAggregator mockFeed = new MockAggregator(decimals);
- mockFeed.setRoundData(1, 1, 2000 * 1 ether, block.timestamp - 7200, block.timestamp - 7200);
- mockFeed.setRoundData(1, 2, 2200 * 1 ether, block.timestamp - 3600, block.timestamp - 3600);
- mockFeed.setRoundData(1, 3, 1980 * 1 ether, block.timestamp, block.timestamp);
-
- vm.expectRevert(
- abi.encodeWithSelector(
- IPriceOracleErrors.UnderlyingPriceDeviation.selector,
- address(mockFeed),
- 1980 * 1 ether,
- 2200 * 1 ether,
- MAX_RELATIVE_DEVIATION
- )
- );
- PriceOracle_v1.latestAnswer(PriceOracle_v1.Feed({priceFeed: mockFeed, decimals: decimals}), _constraints());
- }
-
function test_AcceptableDeviation() public {
uint8 decimals = 8;
MockAggregator mockFeed = new MockAggregator(decimals);
diff --git a/test/price/history-stETH-wstETH.ts b/deprecated/test/price/history-stETH-wstETH.ts
similarity index 100%
rename from test/price/history-stETH-wstETH.ts
rename to deprecated/test/price/history-stETH-wstETH.ts
diff --git a/diffvers b/diffvers
new file mode 100755
index 00000000..786b5610
--- /dev/null
+++ b/diffvers
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+find src -name "*_v1.sol" | while read f; do
+ base="${f%_v1.sol}"
+ files="$f"
+ for v in 2 3; do
+ vf="${base}_v${v}.sol"
+ [ -f "$vf" ] && files="$files $vf"
+ done
+ [ "$files" != "$f" ] && meld $files &
+done
+
+# Also handle prefix.sol (unversioned) as the v1 when _v2 exists but _v1 doesn't
+find src -name "*_v2.sol" | while read f; do
+ base="${f%_v2.sol}"
+ unversioned="${base}.sol"
+ v1="${base}_v1.sol"
+ [ -f "$v1" ] && continue # already handled above
+ [ ! -f "$unversioned" ] && continue
+ files="$unversioned $f"
+ v3="${base}_v3.sol"
+ [ -f "$v3" ] && files="$files $v3"
+ meld $files &
+done
diff --git a/doc/DataEnvelope.md b/doc/DataEnvelope.md
new file mode 100644
index 00000000..e3f0e7b4
--- /dev/null
+++ b/doc/DataEnvelope.md
@@ -0,0 +1,195 @@
+# The numerical envelope: what the Harbor stack can hold, and how
+
+Harbor is a stack: the **Minter** at the bottom mints and redeems the pegged and leveraged tokens;
+the **StabilityPools** on top hold pegged tokens and absorb liquidations; higher layers — the
+HarborYield token and its autocompounders — will sit on the StabilityPools (and this document will
+grow to cover them). Bottom-up, it describes **what each layer can now hold and do**, the code
+variables that carry those values, **what changed to get there**, and the limits the tests located.
+
+The headline guarantee is **no known silent failures**. Every operation is checked for the *exact
+result*, not just survival — the Minter recomputes each mint/redeem independently, the
+StabilityPool asserts the exact read-back. A value it can't handle is refused with a *clean revert*,
+never mis-recorded. The silent-truncation defect that motivated this work — funds accepted, then
+truncated in storage — is gone, found nowhere at any size.
+
+Produced by `test/Minter_feeRange.t.sol` (Minter) and `test/StabilityPoolEnvelope.t.sol`. Figures are token counts unless
+given in dollars; dollar figures depend on the swept peg / collateral price. Ranges are written
+`low → high` in e-notation.
+
+---
+
+## 1. Minter — the bottom of the stack
+
+### 1.1 What it can do now
+
+Four operations — **mint pegged, redeem pegged, mint leveraged, redeem leveraged** — each driven
+across the full envelope:
+
+| Driven across | Range |
+|---|---|
+| collateral amount (supplied / returned) | 1e-9 → 1e12 tokens |
+| pegged token amount (minted / redeemed) | 1e-18 → 1e12 tokens † |
+| leveraged token amount (minted / redeemed) | 1e-2 → 1e12 tokens † |
+| collateral price | 1e-9 → 1e9 |
+| wrap rate | 1e-6 → 1e6 |
+| collateral ratio | every fee / discount / disallow band, **and depegged (CR < 1)** — collateral repriced to 1/2, 1/3, 1/4 of nominal |
+
+† The token floor is **split by operation**. Pegged sweeps from **1 wei**: mint stays accurate to
+the wei at every price, and redeem too — tolerating the dust floors where the input or returned
+collateral rounds to zero (`ZeroInputBalance` / `ReturnZeroAmount`). The higher `1e-2` floor is
+kept only where a *ratio* metric loses precision at dust — the **leveraged** fee-ratio and the
+**depeg** discount / pegged-price checks (a test-tolerance limit, not a minter error). Collateral
+already reaches dust (1e-9).
+
+The Minter prices a mint **per collateral-ratio band** (each a floored `Math.mulDiv` on balances
+updated as the mint proceeds), so a large mint traverses several bands; the test confirms each
+per-band result matches the independent formula within a per-run rounding bound (not a blanket
+one), always in the protocol's favour (mints ≤ the formula). Wrapped-collateral **conservation is
+exact**: `Δuser + Δminter + Δfee + Δreserve == 0` to the wei, holding through depeg (CR < 1), where
+converting pegged back to wrapped accrues a bounded, analytically-derived rounding loss.
+
+### 1.2 How — and why it has no width limit of its own
+
+Carrying every value in **`uint256`** with full-precision `Math.mulDiv` (§3, *make it work*), the
+Minter has **no field-width limit of its own** — it is the *source* of the amounts the StabilityPool
+must store (a cheap peg or collateral makes it emit huge counts), setting the context for the pool's
+widths, not a limit itself.
+
+---
+
+## 2. StabilityPool — on top of the Minter
+
+### 2.1 What it can hold now, and the code that holds it
+
+Its five operations — **deposit, withdraw, harvest, rebalance, claim** — are each driven from dust
+to past the supply cap. What they read and write is the **ledger**: the `TokenBalance` struct —
+`{ uint128 product, uint128 amount, uint40 updatedAt }` — used for the pool total `totalAssetSupply`
+and each depositor's `assetBalances[user]`:
+
+| Term | Code variable | Capacity (tokens) |
+|---|---|--:|
+| a deposit / balance | `assetBalances[user].amount` (uint128) | ~3e20 |
+| total supply | `totalAssetSupply.amount` (uint128) | ~3e20 |
+| the compounding loss factor | `TokenBalance.product` (uint128 DecrementalFloatingPoint: exponent + magnitude) | dynamic range far beyond a plain uint128 |
+| accrued reward per share | `tokenToExponentToIntegral[token][exponent]` (uint256, bucketed per exponent) | effectively unbounded |
+| a holder's claimable / claimed | `pending` / `claimed` (uint256) | effectively unbounded (uncapped accrual) |
+| a harvest deposit (paid out over the reward period) | `RewardData_v2 { uint256 queued, uint128 rate, uint40 … }` | `maxDepositReward` per period ≈ 1.16e17 × MIN (integral headroom) |
+
+The uint128 ledger clears every tested peg, size and crowd with ≥ 1e4× headroom. The field never
+binds: the pool can't grow past its **supply cap** `MAX_TOTAL_ASSET_SUPPLY = MIN_TOTAL_ASSET_SUPPLY
+× FACTOR_PRECISION` (a token count), which is smaller and binds first.
+
+### 2.2 What was changed to get there (from → to)
+
+| Value | Was | Now | Solution (§3) |
+|---|---|---|---|
+| `TokenBalance.amount` (balance / supply) | uint104, raw cast — silent truncation past ~`2e13` | uint128, `SafeCast.toUint128()` | widening + fail-safe internal |
+| reward `integral` | uint192, raw cast (~`6e57`) | uint256 | widening |
+| harvest `rate` | uint80 | uint128 | widening |
+| harvest `queued` | uint96 — capped the empty-pool re-queue | uint256 — full width | widening |
+| reward `pending` / `claimed` (a holder's accrual) | uint128 — reverted a claim past ~`3e20` | uint256 | widening |
+| the loss factor `product` | (already) uint128 DecrementalFloatingPoint | unchanged | representation |
+| deploy config | no floor / cap / delay bounds | `MIN > 0`; `MAX = MIN × F` cap; withdrawal delay & window ≤ 1 year | fail-safe on input |
+
+### 2.3 The limits the tests located
+
+- **The harvest defers past a per-period cap, then recovers across periods.** A deposit is capped at
+ `maxDepositReward` — the reward integral's headroom ≈ 1.16e17 × MIN (the `rate` field is wider and no longer
+ binds). Yield past the cap is left **harvestable**; once the period distributes, capacity frees and the next
+ harvest drains another chunk — recovery is by **waiting**, not by harvesting more often. Nothing is at risk
+ (harvestable falls by exactly what each harvest sweeps). Reaching the cap needs an extreme corner (a \$1e10
+ pool taking a min→max wrap-rate jump at collateral below ~\$2e-5), far from normal operation; raising it is a
+ contained per-token migration, never per-holder.
+- **The rebalance recovers differently — immediately, capped, and self-correcting in one call.** Its liquidation
+ reward is accrued in one step (`_accumulateReward`), not streamed, and is bounded by `maxLiquidationReward`: the
+ reward-integral capacity sized against the *live* share (the reward accrues at once, before the loss, so it is
+ ≥ the streamed `maxDepositReward`). The rebalance clamps each leg's redeemed proceeds to it, deferring any excess to
+ a later call rather than overflowing. The cap does not bind in the declared envelope — even the cheapest-collateral
+ corner's `returned` sits orders of magnitude below it (~1e18× at the max-supply corner) — so the corner executes in
+ full; the cap is the guarantee beyond the envelope. When a pool's proportional share would take it below `MIN`, the
+ **minter's constrained redeem** caps that leg at the pool's headroom and slides the shortfall along the target-ratio
+ line into the co-pool's leg — each leg still redeemed for its own token — so a single rebalance reaches the threshold,
+ or, if both headrooms are exhausted, liquidates the pools' combined headroom (a partial a later rebalance continues).
+ The harvest defers on the same principle: each pool carries its own `owed` ledger, so a share deferred past one
+ period's capacity stays with the pool that earned it and is never re-split to the other. Only genuinely *new* yield
+ is allocated by current holdings, so a pool that did not hold when a backlog accrued never receives any of it.
+- **A holder's reward accrual is uncapped — held in uint256.** Unlike the ledger `amount` (capped at `MAX`), a
+ holder's `pending` / `claimed` has no cap: the whole-pool reward can concentrate on one holder as a count
+ `poolValueUSD / wrappedUSD`. The declared envelope stops the collateral axis at \$1e-6 (overflow threshold
+ ~\$340B — unreachable), but a hyperinflated *collateral* (< \$1e-6) brings it into reach — ~\$15M of reward at
+ ~\$1e-9 collateral overflows a uint128 field — so `pending` / `claimed` are uint256; the collateral floor
+ stays the listing lever.
+- **The mint floor** — at an expensive peg × cheap collateral, the price of collateral-in-pegged
+ rounds to zero (a collateral token worth < a wei of pegged): the market can't back a mint — a
+ listing constraint, not a defect.
+- **The supply cap erodes under devaluation (a dollar value, not a behaviour).** The cap is a fixed
+ token count; its dollar value is that count × the peg price. Deployed with MIN worth ~\$1, the cap
+ is worth ~\$1e18 — unreachable — but that ceiling erodes if the peg later collapses: a devaluation
+ factor `D` divides it down (Weimar `D = 1e12` → ~\$1e6, Zimbabwe `D = 1e13` → ~\$1e5). The cap's
+ *behaviour* never changes (it binds at MAX tokens — `DepositAmountExceedsMaximum`); only its dollar
+ meaning erodes. The StabilityPool never reads the oracle, so this is peg-invariant in the ledger —
+ a doc note, not a test. The response is to **re-base MIN by upgrade** (MIN is an implementation
+ immutable — no storage migration); the deeper lever, if that eroded cap binds unacceptably, is
+ `FACTOR_PRECISION`, which migrates only the per-token `lastAssetLossError`.
+- **Per-peg-MIN markets all hold.** Five markets, each deployed *correctly* at its scale — EUR (\$1,
+ MIN `1e18`), ETH (\$5e3, `2e14`), BTC (\$1e5, `1e13`), plus invented extremes at \$1e9 (MIN `1e9`,
+ the dust-precision floor) and \$1e-9 (MIN `1e27`) — re-run the whole suite and hold. The
+ field-width corners skip where a small MIN puts the cap below the field width (the cap binds
+ first).
+
+---
+
+## 3. How the limits are solved
+
+Two families, under the one guarantee — **no known silent failures**.
+
+### Make it work — produce and store the correct value
+
+- **Storage widening** — a bigger field: `amount` uint104→uint128, `integral` uint192→uint256,
+ `rate` uint80→uint128, `queued` uint96→uint256, reward `pending`/`claimed` uint128→uint256.
+- **Algebra — full-precision intermediates / re-ordering** (`Math.mulDiv`, divide-before-multiply):
+ the value never needs a wide field. The Minter's per-band pricing is built on this.
+- **Range-extending representation (floating point)** — more dynamic range in the *same* bits than a
+ plain integer: `product` is a DecrementalFloatingPoint (exponent + magnitude in one uint128); the
+ reward integral is bucketed per exponent, so each bucket stays bounded.
+- **Deferral / partial processing** — don't require the whole value at once: a harvest deposits up to
+ one period's `maxDepositReward` and leaves the excess harvestable, drained across subsequent periods.
+ (The rebalance defers differently — it accrues its reward in one step rather than streaming, so instead of
+ leaving a remainder harvestable it clamps each leg up front to what the pool will absorb (`_capLiquidation`)
+ and leaves the excess to a later call. The tests show the cap stays unbound by ~7.7× at the tightest
+ corner — §2.3.)
+- **Structural invariants** — a floor / cap so the dangerous value never arises:
+ `MIN_TOTAL_ASSET_SUPPLY` (the reward-divisor floor) and `MAX = MIN × FACTOR_PRECISION` (keeps the
+ loss factor > 0).
+
+### Fail safely — refuse loudly, never corrupt
+
+- **Internal — `SafeCast`.** A checked narrowing cast reverts on overflow instead of truncating —
+ what closed the old silent-truncation class.
+- **On input — bounds at the entry point.** Reject out-of-range input early with a named error:
+ `MIN > 0`, the `DepositAmountExceedsMaximum` cap, the ≤ 1-year withdrawal bound.
+- **At deployment / governance — constrain the envelope** where the limit is economic: don't list a
+ market pairing an ultra-valuable peg with ultra-cheap collateral; re-base MIN by upgrade under
+ hyperinflation.
+
+The first family extends what the code *can* handle; the second guarantees that where it still
+can't, it fails loudly.
+
+---
+
+## 4. Limits worth enforcing at the user-facing edge (documented, not implemented)
+
+Each limit fails *deep*, with a low-level revert. A friendlier system would reject the input at the
+user-facing function — or the front-end, given bytecode limits. **None is implemented**; the
+contracts already fail safe — the value is turning a deep revert into an early, legible one:
+
+| Function | Limit | Today's failure | Where to guard |
+|---|---|---|---|
+| `deposit` | amount past `MAX_TOTAL_ASSET_SUPPLY` | `DepositAmountExceedsMaximum` (already named) | front-end: warn as the pool nears the cap |
+| mint (Minter) | expensive-peg × cheap-collateral — price underflows to zero | divide-by-zero / mint reverts | market-listing: don't pair an ultra-valuable peg with ultra-cheap collateral |
+| (governance) | a market whose peg has hyperinflated — eroded supply-cap dollar value | deposits wall early in dollar terms | re-base MIN by upgrade (not a user action) |
+
+(Neither the harvest nor the rebalance is here. Past its per-period `maxDepositReward` the harvest defers the
+excess and drains it across subsequent periods; past the reward integral the rebalance clamps the leg up front
+and leaves the remainder to a later call — §2.3. Both absorb the corner rather than failing at it, so there is no
+deep failure to lift to the edge. The rebalance cap stays unbound by ~7.7× at the tightest corner in any case.)
diff --git a/doc/aladdin/fxSAVE.md b/doc/aladdin/fxSAVE.md
new file mode 100644
index 00000000..49c3098b
--- /dev/null
+++ b/doc/aladdin/fxSAVE.md
@@ -0,0 +1,182 @@
+# Aladdin fxSAVE Analysis
+
+## Overview
+
+fxSAVE (SavingFxUSD) is Aladdin's auto-compounding yield product built on the f(x) protocol. It wraps stability pool LP tokens as an ERC4626 vault.
+
+**Contract chain:** User -> SavingFxUSD (ERC4626) -> Convex StakingProxy -> Gauge -> FxUSDBasePool
+
+**Repos:**
+- New system: [fx-protocol-contracts](https://github.com/AladdinDAO/fx-protocol-contracts)
+- Old system: [aladdin-v3-contracts](https://github.com/AladdinDAO/aladdin-v3-contracts)
+
+## The f(x) Protocol Invariant
+
+Splits yield-bearing collateral (wstETH, wBTC, etc.) into two derivative tokens:
+- **fToken** (fractional): low-volatility, stablecoin-like (~$1)
+- **xPOSITION** (leveraged): absorbs all volatility, up to 10x leverage
+
+`total_fToken_value + total_xPOSITION_value = total_collateral_value`
+
+**fxUSD** wraps a basket of fTokens from multiple collateral markets.
+
+**Collaterals used by fxUSD:** wstETH, sfrxETH, weETH (all ETH-denominated yield-bearing tokens).
+
+## FxUSDBasePool (Stability Pool)
+
+Holds TWO asset types simultaneously:
+- `totalYieldToken` (fxUSD)
+- `totalStableToken` (USDC)
+
+### Deposit
+
+Both fxUSD and USDC accepted, priced to USD via Chainlink oracle:
+```
+amountUSD = (fxUSD deposit) or (USDC * stablePrice / 1e18)
+totalUSD = totalYieldToken + totalStableToken * stablePrice / 1e18
+shares = amountUSD * totalSupply / totalUSD
+```
+
+### Redemption
+
+Users receive BOTH tokens pro-rata regardless of what they deposited:
+```
+amountFxUSD = shares * totalYieldToken / totalSupply
+amountUSDC = shares * totalStableToken / totalSupply
+```
+
+This is the key design decision: the pool socialises the token mix across all depositors.
+
+### Peg Operations
+
+`arbitrage()` (restricted to `pegKeeper`): swaps fxUSD for USDC or vice versa within the pool at oracle prices. Changes the ratio but not the total USD value.
+
+### Rebalancing
+
+The f(x) protocol's leverage mechanism can reach unsafe ratios. When the leverage ratio of any collateral market exceeds its maximum (e.g. 10x), the system needs to "deleverage" -- reduce the xPOSITION size relative to fToken.
+
+**How it works in FxUSDBasePool:**
+
+1. Anyone can call `rebalance()` when a collateral market's leverage ratio exceeds the threshold
+2. The pool contributes fxUSD and/or USDC to buy back (burn) xPOSITION tokens from the overleveraged market
+3. In exchange, the pool receives the underlying collateral (e.g. wstETH) at a slight bonus
+4. `totalYieldToken` and/or `totalStableToken` decrease (pool gave up stablecoins)
+5. The pool now holds some collateral tokens alongside its remaining stablecoins
+
+**Impact on fxSAVE depositors:**
+- Pool shares represent a reduced stablecoin balance but the pool gained collateral
+- The collateral is worth slightly more than the stablecoins given up (the bonus)
+- Net effect: small positive for the pool (the bonus is the profit for providing the deleveraging service)
+- However, the pool's composition changed -- it now holds collateral tokens that need to be managed
+
+**Comparison with Harbor's rebalancing:**
+- Harbor: the SPM rebalances by redeeming haXXX for wCOLn (collateral SP) or hsXXX.COLn (leveraged SP). The SP's haXXX balance drops, and it receives liquid wCOLn or illiquid hsXXX.COLn.
+- f(x): the base pool rebalances by contributing stablecoins to buy back leveraged positions. The pool's stablecoin balance drops, and it receives underlying collateral.
+- Key difference: in Harbor, the rebalance is a loss-distribution event (SP depositors lose haXXX). In f(x), it's more of a swap (stablecoins for collateral at a bonus). Harbor's mechanism is closer to Liquity's liquidation model; f(x)'s is closer to a peg-stabilisation mechanism.
+
+## SavingFxUSD (fxSAVE)
+
+Proper ERC4626 (inherits OZ `ERC4626Upgradeable`). Asset = FxUSDBasePool LP tokens.
+
+### Auto-Compounding
+
+1. Claims rewards from Convex gauge (`IStakingProxyERC20.getReward()`)
+2. Sends reward tokens to harvester contract
+3. Harvester converts rewards to base pool LP tokens
+4. LP tokens deposited back to gauge
+5. `totalAssets()` increases -> share price rises
+
+### Batch Deposit Threshold
+
+Small deposits are held locally (not immediately staked to gauge). When the balance exceeds a threshold, batch-deposited to gauge. Amortises gas costs but creates a window where LP tokens earn no gauge rewards.
+
+### Withdrawal
+
+Two-step with cooldown:
+1. `requestRedeem(shares)` -> burns shares, creates `LockedFxSaveProxy` per user
+2. After cooldown: `redeem()` via proxy
+3. `instantRedeem(shares)` available with fee (up to 5%)
+
+## Design Decisions Relevant to Harbor
+
+### What Works Well
+
+1. **ERC4626 wrapping a stability pool** -- proven pattern. Share price rises from compounding, drops from rebalancing.
+2. **Two-asset pool (socialised mix)** -- simple accounting. Every share is a proportional claim on both tokens. No per-user tracking of deposit type.
+3. **Oracle-priced deposits** -- prevents sandwich attacks on deposit.
+4. **Harvest/convert/redeposit cycle** -- standard auto-compounding pattern.
+
+### Weaknesses
+
+1. **No virtual shares defense** -- relies on guarded launch for ERC4626 inflation attack. Harbor should use `_decimalsOffset()`.
+2. **Stale view functions** -- `previewDeposit`, `nav` skip `sync` modifier. View functions can return incorrect values for off-chain consumers.
+3. **Deep dependency chain** -- user -> fxSAVE -> Convex -> gauge -> pool -> manager -> collateral. Single point of trust at Convex layer.
+4. **Socialised redemption** -- depositors can't choose to receive only fxUSD or only USDC. May receive a mix they don't want.
+5. **NAV manipulation** -- code comments explicitly warn exchange rate "can be manipulated to increase to any larger value". Unsafe for lending protocol integrations.
+6. **Batch threshold gap** -- between deposit and threshold, LP tokens are un-staked and earn no gauge rewards.
+7. **Redemption cooldown** -- requires per-user proxy contracts. Adds complexity and gas.
+8. **No Liquity products in new system** -- abandoned epoch/scale/product mechanism in favour of simple ERC20 totals. Loses the no-iteration loss distribution property that Harbor retains.
+
+### Abandoning the Liquity Product Mechanism
+
+Aladdin's old system (`ShareableRebalancePool` in `aladdin-v3-contracts`) used the same Liquity-derived epoch/scale/product mechanism that Harbor's StabilityPool uses. This is the `DecrementalFloatingPoint` encoding of a running product `P`:
+
+**How it works (Harbor's current approach):**
+- Each depositor stores a snapshot of the running product `P` at deposit time
+- On liquidation, `P *= (1 - loss / totalDeposits)` — the product decreases
+- A depositor's current balance = `initialDeposit * currentP / snapshotP`
+- Rewards use a similar integral: `reward = initialDeposit * (S_current - S_snapshot) / P_snapshot`
+- **Key property: no iteration.** Loss distribution across N depositors is O(1) — a single product update. No loops, no per-depositor state changes. Gas cost is constant regardless of depositor count.
+
+**What Aladdin changed (FxUSDBasePool):**
+- The new system simply tracks `totalYieldToken` and `totalStableToken` as two uint256 values
+- On rebalance: `totalYieldToken -= amount` and/or `totalStableToken -= amount`
+- Each share is a proportional claim on both totals: `myYield = shares * totalYieldToken / totalSupply`
+- **No product, no snapshots, no epochs.** Just ERC20 shares over two running totals.
+
+**Why they changed:**
+- Simpler code — no epoch/scale overflow handling, no product precision management
+- Their base pool accepts two token types (fxUSD + USDC), which complicates the product approach (would need two products or a combined one)
+- The peg-keeping arbitrage mechanism changes the token mix constantly, making product-based tracking harder to maintain correctly
+
+**What Harbor loses by NOT changing:**
+- Nothing — Harbor keeps the Liquity product mechanism because it has critical advantages:
+ 1. **O(1) loss distribution** — no iteration over depositors during rebalance
+ 2. **Per-deposit precision** — each depositor's loss is tracked from their exact entry point
+ 3. **Battle-tested** — the same mechanism runs in Liquity ($1B+ TVL) and has been audited extensively
+ 4. **Reward integrals** — the same product feeds into harvest reward distribution, giving proportional rewards without iteration
+- The downside (complexity, epoch/scale/exponent tracking) is already implemented and working in SP_v3
+
+**What Harbor gains from NOT changing:**
+- The auto-compounder can rely on `claimable()` being accurate per-depositor without any sync calls
+- No stale view function problem (fxSAVE's `nav()` and `previewDeposit()` can return stale values because `sync` is only called on mutations)
+
+### Key Differences from Harbor
+
+| Aspect | fxSAVE | Harbor |
+|--------|--------|--------|
+| Stability pool assets | fxUSD + USDC in one pool | Multiple SPs per peg (one per collateral) |
+| Equivalent handling | USDC is native pool asset | wXXXn held at PV level, wCOLn from failed mints |
+| Rebalance mechanism | Pool contributes fxUSD+USDC to reduce leverage | SP absorbs loss via Liquity product mechanism |
+| Loss distribution | Simple total reduction | Per-deposit product tracking (no iteration) |
+| Withdrawal | Cooldown + proxy contracts | Dynamic fees (planned), atomic withdraw |
+| Auto-compound | Gauge rewards -> LP -> re-stake | SP rewards -> mint haXXX -> redeposit |
+| Layers | 2 (pool -> fxSAVE) | 3 (SP -> AC -> PV) |
+
+## Audit Findings (OpenZeppelin f(x) v2)
+
+- **ERC-4626 Share Inflation Attack (Medium)**: zero totalSupply allows price inflation. Mitigated by guarded launch.
+- **Stale Stability Pool Values (Medium)**: preview functions skip sync.
+- **Redemption Request Gaming (Medium)**: no expiration on requests.
+- **Capacity Constraint Blocks Liquidations (Medium)**: added collateral can exceed pool capacity.
+- **Oracle Manipulation (High)**: single low-liquidity pool compromise allows price manipulation.
+
+## Sources
+
+- [f(x) Protocol Documentation](https://fxprotocol.gitbook.io/fx-docs)
+- [Stability Pool Documentation](https://fxprotocol.gitbook.io/fx-docs/f-x-protocol-mechanisms/stability-pool)
+- [Introducing fxSAVE (Medium)](https://medium.com/@protocol_fx_667/introducing-fxsave-1980231cea6d)
+- [fxUSD: The Nuts and the Bolts (Medium)](https://medium.com/@protocol_fx_667/fxusd-the-nuts-and-the-bolts-335408276073)
+- [fx-protocol-contracts](https://github.com/AladdinDAO/fx-protocol-contracts)
+- [aladdin-v3-contracts](https://github.com/AladdinDAO/aladdin-v3-contracts)
+- [OpenZeppelin f(x) v2 Audit](https://www.openzeppelin.com/news/fx-v2-audit)
diff --git a/doc/autocompounding-vault-design.md b/doc/autocompounding-vault-design.md
new file mode 100644
index 00000000..18f02ff3
--- /dev/null
+++ b/doc/autocompounding-vault-design.md
@@ -0,0 +1,469 @@
+# Autocompounding Vault: Design & Requirements
+
+## 1. Nomenclature
+
+| Symbol | Meaning | Example (USD peg) |
+|--------|---------|-------------------|
+| **haXXX** | Pegged token for peg XXX | haUSD |
+| **COLn** | Unwrapped collateral n | stETH (COL1), fxUSD (COL2) |
+| **wCOLn** | Wrapped collateral n (interest-bearing) | wstETH (wCOL1), fxSAVE (wCOL2) |
+| **hsXXX.COLn** | Leveraged (sail) token for collateral n | hsUSD.stETH |
+| **hpXXX.COLn** | Rebasing SP token -- collateral pool | hpUSD.stETH |
+| **hpXXX.hsCOLn** | Rebasing SP token -- leveraged pool | hpUSD.hsstETH |
+| **hcXXX.COLn** | Auto-compounder share -- collateral pool | hcUSD.stETH |
+| **hcXXX.hsCOLn** | Auto-compounder share -- leveraged pool | hcUSD.hsstETH |
+| **hyXXX** | Peg Vault share (HarborYield) | hyUSD |
+| **wXXXn** | Interest-bearing equivalent for peg XXX | fxSAVE (wUSD1) |
+| **SP** | Stability Pool | |
+| **AC** | Auto-Compounder (Level 1 ERC4626) | |
+| **HY** | HarborYield — Peg Vault (Level 2 multi-asset ERC-20 basket) | |
+
+## 2. Architecture Overview
+
+Three layers offering escalating pooling. Each level gives up control in exchange for convenience:
+
+```mermaid
+graph TD
+ subgraph "Level 0: Raw Stability Pools"
+ SP_COL1["SP hpUSD.stETH (rebasing ERC20)"]
+ SP_COL2["SP hpUSD.fxUSD (rebasing ERC20)"]
+ SP_LEV1["SP hpUSD.hsstETH (rebasing ERC20)"]
+ end
+
+ subgraph "Level 1: Auto-Compounders (one per SP)"
+ AC_COL1["AC hcUSD.stETH (non-rebasing ERC4626)"]
+ AC_COL2["AC hcUSD.fxUSD (non-rebasing ERC4626)"]
+ AC_LEV1["AC hcUSD.hsstETH (non-rebasing ERC4626) standalone, not in HY"]
+ end
+
+ subgraph "Level 2: HarborYield (one per peg)"
+ HY["HY hyUSD (custom multi-asset basket ERC-20) holds: AC shares + wXXXn adapters"]
+ end
+
+ User_L0["User: full control"] -->|"deposit haUSD"| SP_COL1
+ User_L1["User: auto-compound"] -->|"deposit hpUSD.stETH"| AC_COL1
+ User_L2["User: pooled + equivalents"] -->|"deposit hcUSD.COLn / wXXXn-vault shares"| HY
+
+ AC_COL1 --> SP_COL1
+ AC_COL2 --> SP_COL2
+ AC_LEV1 --> SP_LEV1
+ HY -->|"holds hcUSD.stETH"| AC_COL1
+ HY -->|"holds hcUSD.fxUSD"| AC_COL2
+ HY -->|"holds wXXXn-vault shares"| wXXXn_pool["wXXXn wrapper (ERC4626)"]
+```
+
+**Level 0 -- Raw SP:** User chooses collateral type, manages claims manually. Rebasing ERC20. Full control.
+
+**Level 1 -- Auto-Compounder (AC):** User chooses collateral type, gets autocompounding. Non-rebasing ERC4626 (fixed share count, moving price -- same as stETH/wstETH). Losses and rewards within one SP only. Available for both collateral and leveraged SPs.
+
+**Level 2 -- HarborYield (HY):** User gives up collateral choice. Losses socialised across all managed vaults. One HY per peg. Holds one or more ERC4626 vault positions (ACs + equivalent-token wrappers). Custom multi-asset ERC-20 share (hyXXX) — *not* ERC-4626 and *not* ERC-7575 (both single-asset redeem semantics conflict with HY's proportional-redeem fairness invariant). Exposes ERC-4626-style *views* priced in peg units for interop. Leveraged SPs NOT included (rebalance into hsXXX.COLn which is not liquid).
+
+## 3. Level 0: Raw Stability Pool
+
+### Deposit / Withdraw
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant SP as Stability Pool
+
+ Note over User,SP: Deposit haXXX → receive rebasing hpXXX.COLn position
+
+ User->>SP: approve(SP, amount)
+ User->>SP: deposit(amount, user, minSharesOut)
+ Note over SP: Transfer haXXX from user Mint hpXXX.COLn position to user (balance = deposit amount, rebases on loss/reward)
+ SP-->>User: hpXXX.COLn position active
+
+ Note over User,SP: Withdraw hpXXX.COLn → receive haXXX
+
+ User->>SP: requestWithdrawal()
+ Note over SP: Opens withdrawal window after delay
+ Note over User: Wait for window to open
+ User->>SP: withdraw(amount, user, minAmountOut)
+ Note over SP: Burn hpXXX.COLn position Transfer haXXX to user
+ SP-->>User: haXXX returned
+```
+
+### Claim
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant SP as Stability Pool
+
+ Note over User,SP: After harvest/rebalance, wCOLn is claimable
+
+ User->>SP: claimable(user, wCOLn)
+ SP-->>User: amount available
+ User->>SP: claim(user, address(0), wCOLn, type(uint256).max)
+ SP-->>User: wCOLn transferred (all pending)
+
+ Note over User,SP: Fractional claim — take only part
+
+ User->>SP: claim(user, address(0), wCOLn, maxAmount)
+ SP-->>User: min(pending, maxAmount) transferred
+ Note over SP: Remainder stays as pending, included in claimable()
+```
+
+---
+
+## 4. Level 1: Auto-Compounder
+
+### What it does
+
+Wraps a rebasing hpXXX.COLn into a non-rebasing hcXXX.COLn share. Non-rebasing because the ERC4626 share count is fixed on deposit -- the share *price* changes, driven by `totalAssets() / totalSupply()`.
+
+### Deposit / Withdraw
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant AC as Auto-Compounder
+ participant SP as Stability Pool
+
+ Note over User,SP: Deposit hpXXX.COLn → receive hcXXX.COLn shares
+
+ User->>SP: approve(AC, amount)
+ User->>AC: deposit(amount, user)
+ AC->>SP: transferFrom(user, AC, amount)
+ Note over AC: hcShares = amount * totalSupply / totalAssets
+ AC-->>User: hcXXX.COLn shares minted
+
+ Note over User,SP: Deposit haXXX (convenience) → deposits to SP first
+
+ User->>AC: depositPeggedToken(haXXX_amount, user)
+ AC->>SP: deposit(haXXX_amount, AC)
+ Note over AC: AC's SP position grows
+ Note over AC: hcShares = hpAmount * totalSupply / totalAssets
+ AC-->>User: hcXXX.COLn shares minted
+
+ Note over User,SP: Withdraw hcXXX.COLn → receive hpXXX.COLn
+
+ User->>AC: redeem(hcShares, user, user)
+ Note over AC: hpAmount = hcShares * totalAssets / totalSupply
+ AC->>SP: transfer(user, hpAmount)
+ Note over AC: User receives rebasing hpXXX.COLn. Their share of the unclaimed queue is reflected in the higher hpAmount (totalAssets includes claimable).
+ AC-->>User: hpXXX.COLn transferred
+```
+
+### Compound flow
+
+```mermaid
+sequenceDiagram
+ participant Bot as Compound caller
+ participant AC as Auto-Compounder
+ participant SP as Stability Pool
+ participant Minter
+
+ Bot->>AC: compound()
+ AC->>SP: claimable(AC, wCOLn)
+ SP-->>AC: claimable_wCOLn
+ AC->>Minter: mintPeggedTokenDryRun(claimable_wCOLn, maxFeeRatio)
+ Minter-->>AC: (fee, collUsed, pegged, ...)
+
+ alt collUsed > 0 (profitable to mint)
+ AC->>SP: claim(AC, AC, wCOLn, collUsed)
+ Note over SP: Fractional claim: only transfers collUsed, leaves remainder as unclaimed
+ SP-->>AC: wCOLn (collUsed amount only)
+ AC->>Minter: mintPeggedToken(collUsed, AC, 0, maxFeeRatio)
+ Minter-->>AC: haXXX minted
+ AC->>SP: deposit(haXXX, AC)
+ Note over AC: SP position grows, share price up
+ else collUsed == 0 (fee too high)
+ Note over AC: Skip. wCOLn stays as unclaimed rewards in SP. Included in totalAssets via claimable(). No value lost.
+ end
+```
+
+### Share accounting
+
+```
+totalAssets() =
+ SP.balanceOf(AC) // SP position (haXXX terms, rebasing)
+ + SP.claimable(AC, wCOLn) * price * rate / 1e36 // unclaimed wCOLn valued in haXXX
+```
+
+Price and rate obtained from `IMinter_v3(minter).mintPeggedTokenDryRun(claimable, type(uint256).max)` -- always in sync with the Minter, no direct oracle dependency.
+
+### Rebalance impact
+
+**Collateral SP rebalance:** haXXX burned, wCOLn received via `_accumulateReward`. wCOLn is liquid and valued in totalAssets via claimable. AC share price holds through rebalance -- lost haXXX position is offset by gained claimable wCOLn. The AC auto-compounds this back to haXXX when fees are acceptable.
+
+**Leveraged SP rebalance:** haXXX burned, hsXXX.COLn received. hsXXX.COLn is NOT liquid. The AC's totalAssets() only values wrapped collateral (harvest rewards), not leveraged token rewards. This means AC share price drops on rebalance -- the lost haXXX position is not offset because leveraged tokens are not valued. The AC can only compound the harvest wCOLn; leveraged token rewards queue in the SP until manually claimed via sweep or direct claim.
+
+### Fractional claim
+
+`claim(account, receiver, token, maxAmount)` on SP_v3 -- claims up to maxAmount, leaves the rest as pending. Enables the AC to claim only what can be profitably minted. Remainder stays in SP reward accounting, included in `totalAssets()` via `claimable()`.
+
+### Fairness
+
+Standard ERC4626. `totalAssets()` includes all value (SP position + unclaimed queue at oracle price). Deposits buy at current `totalAssets/totalShares`. No dilution, no cross-subsidy regardless of queue size.
+
+### No equivalents at AC level
+
+The AC does NOT convert wCOLn to wXXXn. It either mints haXXX from wCOLn or leaves it unclaimed in the SP. No value transfers out of the AC. wXXXn equivalents exist only at the HY level (from direct user deposits of the wXXXn wrapper). This resolves the fairness concern from the earlier options analysis -- no cross-subsidy between layers.
+
+### Deposit convenience
+
+Core asset is hpXXX.COLn. Also accepts haXXX via `depositPeggedToken(amount, receiver)` which atomically deposits to SP then mints AC shares. Supports `type(uint256).max` for full balance.
+
+## 5. Level 2: HarborYield (Peg Vault)
+
+### What it does
+
+One HarborYield per peg (e.g., hyUSD). Manages multiple ERC4626 vaults — one per asset — that share the same peg. Typical managed vaults for a single peg:
+
+- `hcXXX.stETH` (AutoCompounder for stETH collateral SP)
+- `hcXXX.fxUSD` (AutoCompounder for fxUSD collateral SP)
+- `wXXXn` via a thin ERC4626 wrapper (e.g., fxSAVE adapter)
+
+Users deposit the *vault's asset* (e.g., hpXXX.stETH, hpXXX.fxUSD, or the wXXXn wrapper's asset), mint hyXXX shares at the current exchange rate, and later redeem for a proportional mix of every managed vault's holdings. Losses and rewards are socialised across all hyXXX holders.
+
+### Architecture invariants
+
+- **One vault per asset** (enforced by an internal asset→vault index). Deposit routing is deterministic from the asset address.
+- **Every managed vault is ERC-4626.** Non-ERC4626 yield sources are wrapped in thin 4626 adapters before being added.
+- **No internal balance tracking.** HY reads `IERC20(vault).balanceOf(HY)` and `IERC4626(vault).convertToAssets(...)` each time; the vault is the source of truth.
+- **Valuation is per vault, never a blanket 1:1 assumption.** `ValuationLib._fairRateInPegUnits` branches on the vault kind: an AutoCompounder is valued at its Minter's `peggedTokenPrice()`, so it stays fair under a haXXX depeg; an equivalent vault is valued at the `IWrappedPriceOracle` registered for it by `addEquivalentVault`, whose mid-rate is drift-checked against `1e18` at registration. HY therefore takes no *global* oracle dependency — there is no single price feed the whole vault trusts — but it is not oracle-free either. `ISwapper` and `IMinter_v3.mintPeggedTokenDryRun` remain the valuation primitives on the swap and mint paths.
+- **Proportional redemption.** hyXXX redeem pays out a pro-rata slice of *every* managed vault — no single-asset redeem path. This is the central fairness invariant; it's why HY is not ERC-7575 (7575 per-asset redeem would let a user drain the best-performing component).
+- **Upgradeable via UUPS**, HarborOwnableRoles, share-token name/symbol stored as constructor immutables via `StringPacking_v1`.
+
+### Deposit flow
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant HY as HarborYield
+ participant Vault as ERC4626 vault (AC or wrapper)
+
+ Note over User,Vault: User deposits an asset mapped to a registered vault.
+
+ User->>HY: deposit(asset, amount, receiver)
+ HY->>HY: look up vault for asset (revert if none / inactive)
+ HY->>HY: snapshot (assetsBefore, supplyBefore)
+ User->>HY: safeTransferFrom(user, HY, amount)
+ HY->>Vault: deposit(amount, HY)
+ Vault-->>HY: vault shares
+ Note over HY: shares = amount * (supplyBefore+1) / (assetsBefore+1)
+ HY-->>User: hyXXX shares minted
+```
+
+Convenience paths like "mint from haXXX" or "mint from wCOLn" are **not** exposed on HY. Users who want to enter from a raw asset call the Minter → SP → AC path off-chain (or via a router contract), then deposit the resulting AC shares' underlying asset (hpXXX.COLn) into HY.
+
+### Redeem flow
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant HY as HarborYield
+ participant V1 as Vault 1 (e.g. AC_COL1)
+ participant V2 as Vault 2 (e.g. AC_COL2)
+ participant V3 as Vault 3 (e.g. wXXXn wrapper)
+
+ User->>HY: redeem(shares, receiver, owner)
+ HY->>HY: spend allowance if caller != owner
+ HY->>HY: supply = totalSupply()
+ HY->>HY: burn(owner, shares)
+
+ loop for each managed vault
+ HY->>V1: redeem(vaultShares * shares / supply, receiver, HY)
+ V1-->>User: vault's underlying asset
+ end
+ HY-->>User: proportional basket delivered
+```
+
+### Compound (equivalent → AC via swapper)
+
+HY's `compound()` is *not* a "compound each AC" loop — the ACs compound themselves (permissionless `AC.compound()`, also triggered by SPM harvest/rebalance). HY's `compound()` is a narrower operation: convert holdings from one managed vault into another, typically to route equivalent-token yield into the AC layer.
+
+```mermaid
+sequenceDiagram
+ participant Keeper as Keeper (COMPOUNDER_ROLE)
+ participant HY as HarborYield
+ participant Src as fromVault (e.g. wXXXn wrapper)
+ participant Swap as ISwapper
+ participant Dst as toVault (e.g. AC_COLn)
+
+ Keeper->>HY: compound(fromVault, toVault, vaultShareAmount, minOut, swapData)
+ HY->>Src: redeem(vaultShareAmount, HY, HY)
+ Src-->>HY: fromAsset amount
+ alt fromAsset != toAsset
+ HY->>Swap: swap(fromAsset, toAsset, amount, minOut, swapData)
+ Swap-->>HY: toAsset amount
+ else same asset
+ Note over HY: pass-through, no swap
+ end
+ HY->>Dst: deposit(amount, HY)
+ Dst-->>HY: toVault shares
+ Note over HY: emit Compounded(caller, fromVault, toVault, amountIn, amountOut)
+```
+
+### Redistribute (rebalance toward target weights)
+
+Each managed vault has an arbitrary-unit `weight`. HY caches `totalWeight = SUM(weight)`. `redistribute()` finds the most over-weight vault (largest `currentValue − targetValue`) and the most under-weight vault, then moves `min(excess, deficit)` from source to target.
+
+```mermaid
+sequenceDiagram
+ participant Keeper as Keeper (REDISTRIBUTOR_ROLE)
+ participant HY as HarborYield
+ participant Src as over-weight vault
+ participant Swap as ISwapper
+ participant Dst as under-weight vault
+
+ Keeper->>HY: redistribute(maxSharesPerVault, minOut, swapData)
+ HY->>HY: compute target per vault = totalAssets * weight / totalWeight
+ HY->>HY: pick src (max excess) and dst (max deficit)
+ HY->>HY: moveValue = min(excess, deficit)
+ HY->>Src: redeem(min(convertToShares(moveValue), maxSharesPerVault), HY, HY)
+ Src-->>HY: srcAsset amount
+ opt src.asset != dst.asset
+ HY->>Swap: swap(srcAsset, dstAsset, amount, minOut, swapData)
+ Swap-->>HY: dstAsset amount
+ end
+ HY->>Dst: deposit(amount, HY)
+ Note over HY: emit Redistributed(caller, src, dst, amountIn, amountOut)
+```
+
+Reverts with `NothingToRedistribute` when `totalAssets == 0`, `totalWeight == 0`, or the basket is already exactly on target.
+
+### Share accounting
+
+```
+totalAssets() =
+ SUM over managed vaults of IERC4626(vault).convertToAssets(IERC20(vault).balanceOf(HY))
+```
+
+No global oracle: each vault is valued on its own terms — an AutoCompounder at its Minter's `peggedTokenPrice()`, an equivalent at the oracle registered for it — so a component that drifts is priced at its drift rather than assumed to be at par. See §6.13 (Peg Verification) for the registration-time drift check that bounds what may be registered in the first place.
+
+### Fairness
+
+- **Proportional redeem** prevents single-asset cherry-picking.
+- **Weight-driven rebalance** keeps the basket close to governance targets without ad-hoc moves.
+- **No dilution on deposit:** shares are priced at the *pre-deposit* exchange rate (`shares = amount * (supply+1) / (totalAssets+1)`), so new depositors can't claim a slice of existing pending yield.
+- **Collateral SP rebalances are absorbed at the AC layer** (wCOLn offsets lost haXXX). HY sees a roughly unchanged per-vault value through a rebalance.
+
+### ERC-4626 compatibility (planned — view shim)
+
+HY will expose ERC-4626-style *views* priced in peg units — `asset()` returning the peg token (haXXX), plus `totalAssets`, `convertToShares/Assets`, `previewDeposit/Redeem` — to give aggregators and portfolio tools enough to value hyXXX. The mutation surface remains HY's own (`deposit(asset,…)`, `redeem`, `compound`, `redistribute`). See plan §B.4.2.
+
+## 6. Design Decisions
+
+### 6.1 SP as Rebasing ERC20
+
+`balanceOf()` returns compounded real value. `totalSupply()` returns `totalAssetSupply()`. Transfer/approve/allowance added in v3. Like stETH.
+
+### 6.2 Non-rebasing AC shares
+
+The AC is the non-rebasing wrapped version. Like wstETH wraps stETH. Share count fixed, price moves.
+
+### 6.3 Collateral SP rebalance holds value
+
+Unlike leveraged SPs, collateral SP rebalance returns liquid wCOLn. The AC's totalAssets stays roughly constant (lost haXXX offset by gained claimable wCOLn). The AC auto-compounds back to haXXX when fees are acceptable.
+
+### 6.4 Leveraged SPs standalone
+
+Leveraged SPs rebalance into hsXXX.COLn which is not liquid. Leveraged AC only compounds harvest wCOLn. Not included in HY (different risk profile).
+
+### 6.5 Minting: maxFeeRatio
+
+`mintPeggedToken(wCOLn, receiver, minPeggedOut, maxFeeRatio)` on Minter_v3. Stops when cumulative fee exceeds maxFeeRatio * collateralIn. Returns (0, 0) gracefully if fee too high.
+
+### 6.6 Unified Claim with Fractional Support
+
+`claim(account, receiver, token, maxAmount)` on SP_v3 (via `IMultipleRewardAccumulator_v3`). Claims up to maxAmount from the token, leaves rest as pending. `token == address(0)` claims all active tokens. Array overload `claim(account, receiver, tokens[], maxAmount)` for batch/historical claims.
+
+### 6.7 Oracle Coupling
+
+AC reads price and rate from `IMinter_v3(minter).mintPeggedTokenDryRun()` — always in sync with the Minter, no direct oracle dependency. HY takes no *global* oracle dependency — no single feed prices the whole vault — but it does read a per-vault oracle for each registered equivalent, via `ValuationLib.oracleRatePegUnits`, when computing `totalAssets`. AC-backed vaults need none, since the AC's own Minter price serves (see §5 share accounting and §6.13 peg verification).
+
+### 6.8 Equivalent Token Management
+
+Equivalent yield sources (wXXXn) are held at the HY level only — never inside an AC (see §6.9). Each equivalent is registered as a managed ERC4626 vault; non-ERC4626 tokens are wrapped in a thin 4626 adapter first. There is no preference list and no internal bookkeeping: holdings are whatever `balanceOf(HY)` returns, and weights drive the rebalance target. Value can be routed back into AC positions via `HY.compound(fromVault, toVault, …)` which calls `ISwapper` to cross assets and deposits into the destination ERC4626.
+
+### 6.9 No Equivalents in AC
+
+The AC does NOT hold wXXXn. Unprofitable wCOLn stays as unclaimed rewards in the SP, valued in `totalAssets` via `claimable()`. This avoids the cross-subsidy fairness issue identified in the options analysis.
+
+### 6.10 Compound Trigger
+
+Two distinct "compound" operations live at different layers:
+
+- **AC.compound()** — permissionless. Claims profitable wCOLn, mints haXXX via the Minter, redeposits to the SP. Also triggered by SPM during harvest/rebalance (B.5, pending).
+- **HY.compound(fromVault, toVault, vaultShares, minOut, swapData)** — role-gated (`COMPOUNDER_ROLE | owner`). Redeems from one managed vault, swaps via `ISwapper`, deposits into another managed vault. Used to route equivalent-token yield into the AC layer when profitable.
+
+### 6.11 Withdrawal
+
+**Current (shipped):** AC uses `EXEMPT_WITHDRAWAL_FEE_ROLE` and SP_v3 still has the request/wait withdrawal window. AC withdrawals route through the AC contract and bypass both fee and window.
+
+**Planned (B.6b, NOT YET SHIPPED):** Replace the withdrawal window with a CR-based dynamic fee derived from the Minter's incentive ratios (`fee = mintPeggedRatio - redeemPeggedRatio`, clamped to `[0, MAX_WITHDRAWAL_FEE]`). Naturally zero at healthy CR. Enables atomic ERC4626 `withdraw()`. See [rebalance-fairness.md §5A](ideas/rebalance-fairness.md) for the full design.
+
+### 6.12 HarborYield is not ERC-4626 / ERC-7575
+
+HY's mutation surface is intentionally non-standard:
+
+- **ERC-4626** is single-asset (`asset()` returns one address, `deposit`/`redeem` transact in that asset). HY holds multiple assets by design.
+- **ERC-7575** (multi-asset vaults with one share token) uses per-asset redeem semantics — each asset has its own ERC-4626 entry contract. That directly breaks HY's proportional-redeem fairness invariant: a user could redeem entirely through the highest-yielding component and leave the rest of hyXXX holders with a worse basket.
+
+HY will instead expose ERC-4626-style *views* priced in peg units (`asset()`, `totalAssets`, `convertTo*`, `preview*`) for interop with aggregators, indexers, and price feeds. The mutation API stays HY-specific (`deposit(asset, amount, receiver)`, `redeem(shares, receiver, owner)`, `compound`, `redistribute`). See plan B.4.2 for the exact view surface.
+
+### 6.13 Peg Verification (shipped)
+
+HY assumes every managed vault's asset is pegged to the same RWA. Two failure modes:
+
+1. **Config error** — admin registers a vault whose asset is pegged to the wrong RWA (or not pegged at all). Catastrophic valuation error.
+2. **Market depeg** — a component trades below peg transiently. New depositors are diluted and redeemers get a worse mix than market value would suggest.
+
+The shipped design uses two `addVault` variants and a single `maxPegDriftBps` tunable applied at both registration and runtime swap time:
+
+- **`addAutoCompounderVault(vault, weight)`** — verifies `IAutoCompounder(vault).PEGGED_TOKEN() == _PEG_TOKEN` via direct introspection. No oracle parameter needed; the AC's own immutable proves which peg it serves. Reverts with `WrongPegToken(expected, actual)` on mismatch.
+
+- **`addEquivalentVault(vault, weight, valuationOracle)`** — takes an `IWrappedPriceOracle` address and verifies the oracle's mid-rate is within `maxPegDriftBps` of `1e18` at registration. Stores the oracle in a sparse `vaultValuationOracle` mapping for runtime use. Reverts with `ExcessivePegDrift(expected, actual)` on mismatch.
+
+- **Depeg-aware `totalAssets`** — `_fairRateInPegUnits(vault)` branches on the sparse mapping: AC vaults read `IMinter(AC.MINTER()).peggedTokenPrice()` (fair valuation under haXXX depegs), equivalent vaults read their registered oracle. Each vault's `convertToAssets(balance)` is multiplied by its fair rate before being summed.
+
+- **Oracle-bounded runtime swap floor** — `compound`/`redistribute` compute `_effectiveMinOut(from, to, amountIn, keeperMinOut)` = max of the keeper's `minOut` and `amountIn × fromRate / toRate × (1 - maxPegDriftBps/10_000)`. A compromised keeper passing `minAmountOut = 0` still gets HY's own oracle-derived floor. During a real market depeg, the oracle reflects the depeg and the floor drops with the market — no spurious blocks.
+
+- **Watchtower + deactivateVault** — owner freezes new deposits to a vault during sustained depegs (off-chain governance). Proportional redeems still work.
+
+`maxPegDriftBps` is owner-settable via `setMaxPegDriftBps`. The unified parameter is operational simplicity; it can be split into separate registration and runtime tunables later if needed.
+
+`ISwapper.previewSwap` was deliberately removed from the interface — production swap adapters (1inch, etc.) don't have on-chain quoting, and any consumer that called `previewSwap` for security purposes was a trap. The oracle-bounded floor supersedes it.
+
+### 6.14 ERC-20 Permit (EIP-2612) — shipped
+
+All harbor-side ERC-20 contracts in this work support `permit(owner, spender, value, deadline, v, r, s)` for approve-and-act in a single transaction:
+
+- `HarborYield_v1` — Solady ERC20 with built-in EIP-2612.
+- `AutoCompounder_v1` — Solady ERC4626 (which inherits Solady ERC20) with built-in EIP-2612.
+- `StabilityPool_v3` — Solady ERC20 with built-in EIP-2612. Custom rebasing balance/total-supply accounting overrides Solady's `balanceOf` / `totalSupply`; allowance / nonces / permit / DOMAIN_SEPARATOR are inherited from Solady unchanged. Allowances are nominal (do NOT scale with rebases — same semantic as stETH).
+- `PeggedToken` / `LeveragedToken` — already use `PermittableERC20_v1` / `MintableBurnableERC20_v1` from bao-base; both have permit. Both inherit the new shared `PermitTestBase` test suite.
+
+**Solady was chosen over OZ** after analysis showed Solady's ERC20 / ERC4626 are trivially compatible with UUPS proxies: ERC20 uses hand-picked magic storage slots that cannot collide with ERC7201, ERC4626 has zero storage of its own, and both expose the abstract / virtual hooks needed to wire in upgradeable name/symbol/asset via constructor immutables. The migration freed bytecode on AC (-478 B) while gaining permit on all three contracts. SP_v3 grew ~694 B (accepted, since the bytecode budget still has headroom and the alternative refactors carried more risk than they saved). All five permit-bearing contracts share the `bao-base/test/helpers/PermitTestBase.t.sol` test suite — five canonical permit tests via a single `_permitTarget()` override.
+
+## 7. Access Control
+
+| Role | On Contract | Purpose |
+|------|------------|---------|
+| Owner | HY, AC | Add/weight/deactivate vaults; configure `maxFeeRatio`; UUPS upgrade; sweep |
+| `COMPOUNDER_ROLE` | HY | Call `HY.compound(fromVault, toVault, …)` |
+| `REDISTRIBUTOR_ROLE` | HY | Call `HY.redistribute(…)` |
+| `EXEMPT_WITHDRAWAL_FEE_ROLE` | SP | AC withdraws without fee/delay |
+| Anyone | SP, AC, HY (deposit/redeem), `AC.compound()` | Public entrypoints |
+
+## 8. Contracts
+
+| Contract | Status | Purpose |
+|----------|--------|---------|
+| StabilityPool_v3 | Done | Rebasing ERC20 (Solady + EIP-2612 permit), unified claim, fractional claim, StringPacking_v1 |
+| Minter_v3 | Done | `mintPeggedToken(maxFeeRatio)`, `mintPeggedTokenDryRun`, private→internal |
+| AutoCompounder_v1 | Done | Non-rebasing ERC4626 wrapper per SP (Level 1), Solady ERC4626 + EIP-2612 permit |
+| HarborYield_v1 | Done (core) | Multi-asset ERC-20 basket per peg (Level 2), Solady ERC20 + EIP-2612 permit. Two `addVault` variants (AC introspection vs equivalent + oracle); depeg-aware `totalAssets`; oracle-bounded swap floor; `compound`/`redistribute` role-gated |
+| ISwapper / MockSwapper | Done | Generic swap interface; mock for tests. (`previewSwap` deliberately removed — see §6.13.) |
+| StabilityPoolManager_v2 | Pending (B.5) | SPM triggers `AC.compound()` during harvest/rebalance |
+| SP_v3 — CR-based withdrawal fee | Pending (B.6b) | Replace withdrawal window with `fee = mintPeggedRatio - redeemPeggedRatio` clamped to `[0, MAX_WITHDRAWAL_FEE]`. Enables atomic ERC4626 `withdraw()`. See [rebalance-fairness.md §5A](ideas/rebalance-fairness.md). |
+| SP_v3 — accumulator cleanup | Pending (A2 / H.5) | Drop v1/v2 legacy accumulator storage fallback; one-shot migration via separate `ForceMigrateAccumulator_v1` |
+
+## 9. References
+
+- [Aladdin fxSAVE analysis](aladdin/fxSAVE.md) -- ERC4626 wrapping stability pool, proven pattern
+- [Rebalance fairness](ideas/rebalance-fairness.md) -- worked examples, CR-based withdrawal fee design (B.6b), effective share deferred (B.6c)
+- [Harbor deployment design](harbor-deployment.md) -- pre-flight, seed deposits, deployHY/deployPeg switches
diff --git a/doc/fixes/PR33-coderabbit-audit.md b/doc/fixes/PR33-coderabbit-audit.md
new file mode 100644
index 00000000..fd96334c
--- /dev/null
+++ b/doc/fixes/PR33-coderabbit-audit.md
@@ -0,0 +1,499 @@
+# PR #33 — CodeRabbit audit triage
+
+Source: the CodeRabbit reviews on [PR #33](https://github.com/baofinance/harbor/pull/33), in two
+rounds — six reviews of 2026-07-29 (round 1, below) and two more of 2026-08-08 and 2026-08-09
+(round 2, at the end of this document). Regenerate with:
+
+```bash
+gh api "repos/baofinance/harbor/pulls/33/reviews?per_page=100" --paginate
+gh api "repos/baofinance/harbor/pulls/33/comments?per_page=100" --paginate
+```
+
+Findings are keyed by CodeRabbit's own content hash (`cr-comment:v1:`, truncated to 8
+characters here). That hash is stable across review runs, so it — not file:line — is the
+identity of a finding. Line numbers below are as they stood at review time, against commit
+`4c05265`; the code has moved since (`Minter_v3.sol` by ~125 lines), so treat them as
+historical pointers rather than current locations.
+
+## Where this stands
+
+Each round has its own summary table further down; this is the position across both, and is the
+one to read first.
+
+| | round 1 | round 2 | total |
+|---|---|---|---|
+| Findings, unique by content hash | 70 | 16 | **86** |
+| ✅ need no further work | 69 | 16 | **85** |
+| ❌ outstanding | **1** | – | **1** |
+
+**One finding remains: `56e24c6a`** — deploy-script test coverage, from round 1. Its entry is under
+[config](#config-10), its fix prompt is in the appendix, and what the deploy-stack refactor did and
+did not do for it is measured in the section following that table. Short version: the refactor has
+landed and **closed none of the three files**, though it took the per-contract deploy modules to
+100%; and before anything is written against `HarborDeployer`, its contradictory coverage metrics
+need explaining rather than chasing.
+
+Round 1 raised 107 finding-instances across its six reviews, which deduplicated to 70 by content
+hash; 11 of those were semantic duplicates of another finding, leaving 59 genuinely distinct
+issues. Severity as reported was 2 Critical, 66 Major, 1 Minor, 1 Trivial — CodeRabbit labelled
+almost everything Major, so severity carries no useful signal here and is not used to order this
+document.
+
+## Status vocabulary
+
+A ✅ marks a finding that needs no further work — fixed, refuted, already counted elsewhere, or
+deliberately declined. A ❌ marks one that is still outstanding.
+
+| Status | Meaning |
+|---|---|
+| ✅ **done** | Fixed, verified against current code, commit cited |
+| ❌ **open** | Confirmed still present |
+| ❌ **decide** | Confirmed still present; fixing it is a design decision, not a defect |
+| ✅ **false-positive** | Verified not to be a defect — evidence cited |
+| ✅ **duplicate** | Same issue as another entry |
+| ✅ **won't-fix** | Deliberately not fixed — reason given |
+
+# Round 1 — the six reviews of 2026-07-29
+
+## Summary
+
+| Area | Total | ✅ done | ❌ open | ❌ decide | ✅ false-positive | ✅ duplicate | ✅ won't-fix |
+|---|---|---|---|---|---|---|---|
+| src | 4 | 3 | – | – | – | – | 1 |
+| test | 6 | 3 | – | – | 3 | – | – |
+| config | 10 | 2 | 1 | – | 2 | 1 | 4 |
+| script (live) | 5 | 4 | – | – | – | 1 | – |
+| script (archive) | 20 | 4 | – | – | – | 3 | 13 |
+| doc | 25 | 6 | – | – | – | 5 | 14 |
+| **Total** | **70** | **22** | **1** | **–** | **5** | **10** | **32** |
+
+**1 of round 1's findings is outstanding**: `56e24c6a`, deploy-script test coverage. The other 69
+need no further work. Its fix prompt is reproduced in the appendix.
+
+Five findings turned out to be false positives, and the pattern is worth recording: CodeRabbit
+reads a single file without the surrounding system, so it reported a CI check as deleted when
+it had moved into the composite action being called, a test suite as not covering v3 when its
+mock inherits v3, and a directory as an input when it is generated output. One of the five,
+`077b539f`, would additionally have broken a working script had its fix been applied as written.
+
+---
+
+## src — contract code (4)
+
+| id | location | finding | status |
+|---|---|---|---|
+| `41193653` | `Minter_v3.sol:2038-2061` | Oracle fetch helpers declare `ZeroOraclePrice`/`InvalidOracle` but never perform the check | ✅ **done** — `2e06a58`; `_fetchOracle` now reverts `ZeroOraclePrice`/`ZeroOracleRate`, callers routed through `_fetchMidPrice`/`_fetchMinRate` |
+| `ecc6d23f` | `StabilityPool_v3.sol:616-622` | Unchecked signed↔unsigned casts around `rewardDivisorGap`, three sites | ✅ **done** — `17ec549`; all three now use `SafeCast` |
+| `39f62036` | `StabilityPoolManager_v2.sol:534` | No sum check or migration for legacy ratios before `residualRatio` is computed | ✅ **won't-fix** — `def46d7` fixed the setter half (`updateHarvestRatios` enforces `bounty + cut <= 1e18` atomically, replacing two independently-validated setters that could drift). A runtime guard in `harvest()` is not needed: every existing deployment has been verified compliant with those bounds, so the underflow state is unreachable. The residual risk is that a ratio changes between that verification and the deploy script running — which is a **deployment-time check, not a contract change**. Tracked separately as a deployment-checker requirement, owned by another conversation |
+| `43738187` | `Minter_v3.sol:753-765` | Capped mint returns `(0, 0)` without honouring `minPeggedOut` | ✅ **done** — the capped path now returns `(0, 0)` only when `minPeggedOut` is zero; otherwise it falls through to the existing `MintInsufficientAmount` check, which a zero output can only fail. Reusing that one revert site rather than adding a second kept the cost to **7 bytes** (margin 134 → 127). The contradicting docstring on `IMinter_v3.mintPeggedToken` was corrected, and the implementation's duplicate copy of it replaced with `@inheritdoc`. Covered by `test_mintPegged_feeCappedStillHonoursMinPeggedOut` (confirmed failing before the fix) and `test_mintPegged_feeCappedReturnsZeroWhenNoMinimumDemanded`, which pins the graceful zero for `minPeggedOut == 0` — the capped overload previously had no tests at all |
+
+## test (6)
+
+| id | location | finding | status |
+|---|---|---|---|
+| `9cc381b9` | `RebalanceFairnessScan.t.sol:285-287` | `_gapPct` underflows when `lower > higher` | ✅ **done** — `ad70356`; helper now clamps `lower >= higher` to 0, so every call site is safe |
+| `4d347ef0` | `Genesis.t.sol:234-244` | `test_nullGenesis` calls `endGenesis` without granting `zeroFeeRole`, so it will revert | ✅ **false-positive** — `test_nullGenesis` passes. A null genesis has nothing to mint, so the Minter's zero-fee path is never reached |
+| `cd76dc8c` | `StabilityPoolEnvelope.t.sol:619-630` | `test_widthCorner_depositBeyondUint128Reverts` lacks the supply-cap guard its siblings have; the cap should bind first on `ethScale`/`eurScale` | ✅ **false-positive** — the test passes on all 13 market configurations, including the two named |
+| `2154e67c` | `IMockMultipleRewardCompoundingAccumulator.sol:18-27` | `userRewardSnapshot` widening not propagated to the mock interface | ✅ **done** — `pending`/`claimed_` widened to `uint256` to match the v3 accumulator. Widening made the compiler name the two places the truncation was actually reached, `MultipleRewardCompoundingAccumulator.t.sol:883` and `:927`, both of which declared `uint128` receivers; both now read `uint256`. The reverse direction is lossless (a v2 mock's `uint128` return is ABI-padded to 32 bytes), so the v2 mock needs no change. 31 accumulator tests pass |
+| `4eefe8f5` | `Rebalance.t.sol:10-12` | Duplicate `IStabilityPool` import | ✅ **done** — duplicate removed. CodeRabbit's stated consequence was wrong (Solidity accepts a repeated identical import and the repo compiled), so this was cosmetic, not a build break |
+| `7560604f` | `LinearMultipleRewardDistributor.t.sol:31-40` | Suite only instantiates the v3 mock, so `LinearMultipleRewardDistributor_v2` is unexercised | ✅ **false-positive** — both halves are wrong. The suite does exercise the real v3: `MockLinearMultipleRewardDistributor_v3 is LinearMultipleRewardDistributor_v3` and overrides exactly one member, `_accumulateReward`, replaced with an event so the test can observe it fired — rate, period bounds, queueing and role checks all run production code. And v2 is still exercised, just elsewhere: `LinearMultipleRewardDistributor_v2` has one consumer, `MultipleRewardCompoundingAccumulator_v2.sol:13`, reached in tests via `AccumulatorClaimableEquivalence.t.sol:48` |
+
+## config (10)
+
+| id | location | finding | status |
+|---|---|---|---|
+| `2ec5976e` | `.claude/settings.json:7-9` | Allowlist exposes other local repos and `~/.claude/**` | ✅ **done** — `2b3bdeb`; the rule granting read access to every sibling repository under the developer's home directory was removed, and the `~/.claude/**` rule narrowed to `plans/**` plus this project's own directory |
+| `5b4d32ae` | `.claude/settings.json:7` | CWE-732: constrain permissions to minimum scope | ✅ **duplicate** of `2ec5976e` |
+| `87c9d0d1` | `.claude/settings.json:22` | Remove the `~/.claude/plans` git allow rules | ✅ **won't-fix** — `lib/bao-base/CLAUDE.md` *requires* committing to the plan repo after every plan update ("you own its git, and committing there is required"). Removing the rule would break the documented working mode. The remaining rule is already an exact command with a fixed argument, which is what the sibling finding asked for |
+| `ea436f80` | `.claude/settings.local.json:6` | `Bash(chmod:*)` grants chmod on any path | ✅ **done** — rule removed, so chmod now requires approval. Removing rather than narrowing avoids guessing a scope: a rule can be re-added against a real path if one proves necessary. Whether this machine-local file should be git-tracked at all remains an open question, carried in the plan |
+| `0a5116c7` | `CI-test-foundry-stable.yml:40-41` | Audited-source verification step removed before invoking the submodule action | ✅ **false-positive** — the check was not removed, it moved *into* the action being invoked: [`test-foundry/action.yml:94-101`](../../lib/bao-base/.github/actions/test-foundry/action.yml) runs a "Check audit and deployed code" step calling `yarn verify-audit`. CodeRabbit read only the workflow file and could not see inside the composite action it calls |
+| `077b539f` | `foundry.toml:70` | `read-write` granted on the git-tracked holder-input directory | ✅ **false-positive** — the premise is wrong. That directory holds the *filtered output* of `FilterSpHolders.s.sol`, not its input: inputs are read from the untracked `tmp/sp-holders` ([FilterSpHolders.s.sol:48](../../script/Migrate_StabilityPool_v2_Data_mainnet/FilterSpHolders.s.sol#L48)) and the results written back to the tracked directory, which is git-tracked *deliberately* — "the filtered files are git-tracked: they form the auditable record of which holders were included in the migration batch and which were skipped" (lines 19-21). Write access is required by design; both prescribed remedies would break it — read-only stops `run()` writing at all, and relocating the output to `./tmp` destroys the audit record that tracking exists to preserve |
+| `23d31d75` | `pyproject.toml:7` | Wake used but not declared | ✅ **won't-fix** — Wake is not part of the CI tooling yet; the Python testing of Solidity it supports is work in progress and not on any official path. Declaring it now would pin a dependency the build does not use. Revisit when Wake joins CI, at which point the `requires-python = "==3.10.*"` pin will need resolving against the 3.13 the bao-base tooling expects |
+| `04f01408` | `scripts/deploy.py:3-6` | Literal `ENTER_NODE_URL_HERE` placeholder committed | ✅ **won't-fix** — same reason as `23d31d75`: Python testing of Solidity via Wake is work in progress and not part of any official process. The placeholder marks an entry point that is not yet wired up, not a broken deployment path |
+| `56e24c6a` | `regression/coverage.txt:3` | `ForceMigrateAccumulator_v1.sol` 0%, `HarborDeployStack.sol` 75%, `HarborDeployer.sol` 53% | ❌ **open** — accepted: deployment is part of the production process, so the deploy scripts are tested alongside the contracts. Three files, three different problems, and the deploy-stack refactor has since landed **without closing any of them** — see the measured outcome below. `ForceMigrateAccumulator_v1` is **not** spent: the migration is pending (`StabilityPool_v3` appears 3 times across `deployments/` against `StabilityPool_v2`'s 105) and it *is* tested, by `script/verify/sp-v2-data-prep-for-v3/ForceMigrateAccumulatorTest.t.sol`, which needs a mainnet fork and so cannot join `yarn test`; its assembly slot arithmetic is unit-testable without a fork and is what should be covered |
+| `9b58cc3b` | `regression/sizes.txt:48` | `Minter_v3` near the EIP-170 limit | ✅ **won't-fix** — no action needed: contract size is tracked by CI against `regression/sizes.txt`, so an overrun fails the build rather than surprising a deployment. Recorded for context: the margin was 182 B at review, 143 B at `HEAD~6`, and is **134 B** now — the oracle guards took a third of the remaining headroom, and `StabilityPool_v3` fell 775 B → 646 B. Changes to `Minter_v3` are size-budgeted from here on |
+
+### `56e24c6a` after the deploy-stack refactor — what it actually moved
+
+The refactor was sequenced ahead of this finding on the expectation that running test setups through
+the deploy chain would exercise these files as a side effect. It landed, and the current
+`regression/coverage.txt` says it did not:
+
+| file | at review | now |
+|---|---|---|
+| `ForceMigrateAccumulator_v1.sol` | 0% (0/36) | 0% (0/36) — unchanged |
+| `HarborDeployStack.sol` | 75% (43/57) | **69% (31/45)** — *fell* |
+| `HarborDeployer.sol` | 53% (42/79) | 57% (45/79) |
+
+The expectation was not wrong, only aimed at the wrong files. The gain landed on the per-contract
+deploy modules, which is where the migrated setups actually spend their time:
+`script/src/contracts/` now reads **100%** for `Minter.sol`, `StabilityPool.sol`,
+`StabilityPoolManager.sol`, `Genesis.sol` and `LeveragedToken.sol`, with `PeggedToken.sol` at 85%.
+`HarborDeployStack.sol` *fell* because it shrank — 57 lines to 45 — as R1-R4 folded configuration
+into those modules; what left the file was the well-exercised part.
+
+**Before any test is written against `HarborDeployer`, its numbers need explaining rather than
+chasing.** Its four metrics contradict each other: lines 57% (45/79), statements **95% (70/74)**,
+branches 100% (0/0), functions 14% (5/37). A file cannot be 95% exercised by statement and 14% by
+function, and its 37 functions are almost all two-line `public` key/address resolvers on an
+*abstract* contract reached through inheritance — both overloads of which are in real use across
+`test/` and `script/`. The statement figure is the one consistent with how heavily the deploy path
+is driven. So the line and function figures are suspect as a measurement of this file, and writing
+tests to move them would be precisely the "make line X execute" work `CLAUDE.md` warns against —
+gaming a number that a second number already contradicts.
+
+That makes the first task diagnostic, not remedial: establish what forge is counting here. Only
+then is it worth asking what behaviour of the deploy scripts is genuinely unverified, working from
+what they are meant to do rather than from an uncovered-lines list.
+
+## script — live tooling (5)
+
+| id | location | finding | status |
+|---|---|---|---|
+| `46648263` | `Deploy_StabilityPool_v3_mainnet.s.sol:96-114` | Market configs constructed inside the broadcast scope | ✅ **done** — all six `create*MintersConfig()` calls now sit at lines 94-99, before `vm.startBroadcast()` at 101 |
+| `174b8161` | `FilterSpHolders.s.sol:95-112` | `vm.readLine` returns `""` for both a blank line and EOF, silently truncating the holder list | ✅ **done** — now reads whole-file and splits on `\n` |
+| `c1b76166` | `capture-sp-holders:206` | `mapfile` ignores process-substitution exit status, so a partial holder file can be written | ✅ **done** — `fetch_owners` output is now captured and its status checked (`if ! owners_found=$(...)`) before `mapfile` |
+| `25e7819e` | `capture-sp-holders:206` | Same, phrased as owner discovery | ✅ **duplicate** of `c1b76166` |
+| `bd238ceb` | `capture-sp-holders:206` | Same, and asks for the checked-producer pattern on `mapfile -t SALTS < <(pool_salts)` too | ✅ **done** — the `fetch_owners` half was already fixed; `SALTS` now uses the same capture-check-load pattern, so a failed state-file read aborts instead of reporting "0 stability pools". Doing so exposed a second defect: under `set -euo pipefail` the trailing `grep` in `pool_salts` returns 1 when nothing matches, which the new check could not distinguish from a real failure — the grep now admits no-match as the empty result it is while still propagating grep's error status 2. Verified against four cases: normal, no matching pools, malformed state file, missing state file |
+
+## script — spent one-off migration artefacts (20)
+
+These are runbooks and runners for migrations that have already executed. Default disposition
+is **won't-fix**: correcting a path in a script that will never run again has no value. Several
+were fixed anyway because they set a precedent for how future verification scripts are written.
+
+Both **Critical** findings live here, and both are the same bug — `PROJECT_ROOT` resolving to
+`script/` instead of the repository root — in runners that have already served their purpose.
+
+| id | location | finding | status |
+|---|---|---|---|
+| `788773c5` | `minter-v2-upgrade/test-deploy:4-8` | `TEST_PATH` points at a nonexistent file | ✅ **done** — retargeted to `script/verify/minter-v2-upgrade/DeployMinters.t.sol`, usage comments updated |
+| `b2d7ba9e` | `sp-v2-upgrade/genesis-end.md:21-29` | `ZERO_FEE_ROLE` derived by hashing the signature instead of read from the contract | ✅ **done** — now `cast call $MINTER "ZERO_FEE_ROLE()(uint256)"`, with a comment explaining it is a role bitmask, not a hashed signature |
+| `01b80661` | `sp-v2-upgrade/genesis-end.md:21-30` | Same | ✅ **duplicate** of `b2d7ba9e` |
+| `632f4917` | `spl-remediation/collect-holders:28-40` | End block defaults to latest, so reruns are not reproducible | ✅ **done** — documented and made explicit, citing `RebalanceCheck.t.sol`'s fork block |
+| `c4804fa0` | `sp-v3-reward-divisor-migration/StabilityPoolMigrationPreflight.t.sol:175-184` | Negative gaps misclassified as stale holder lists | ✅ **done** — `seed` is now classified before `recapture` |
+| `97023cbc` | `minter-v2-upgrade/run-upgrade-test-Minter_v2:4-7` | **Critical** — `PROJECT_ROOT` resolves to `script/`, so `./script/run-script` becomes `script/script/run-script` | ✅ won't-fix — spent one-off |
+| `14ded316` | `spl-remediation/run-upgrade-test-remediate-ETH_fxUSD_SPL:4-7` | **Critical** — same bug | ✅ won't-fix — spent one-off |
+| `1b73a1a4` | `spl-remediation/run-upgrade-test-remediate-ETH_fxUSD_SPL:5-7` | Same | ✅ **duplicate** of `14ded316` |
+| `2622590e` | `minter-v2-upgrade/run-upgrade-test-Minter_v2:79-90` | Stale `script/test/...` references after the move to `script/verify/` | ✅ won't-fix — spent one-off |
+| `ca62f98f` | `minter-v2-upgrade/run-upgrade-test-Minter_v2:79-90` | Same | ✅ **duplicate** of `2622590e` |
+| `053b5ec0` | `spl-remediation/collect-holders:34-42` | `tokentx` query not paginated | ✅ won't-fix — spent one-off |
+| `85a40434` | `minter-v2-upgrade/upgrade-Minter_v2.md:18-22` | Documentation paths not normalised | ✅ won't-fix — spent one-off |
+| `5ca283ee` | `sp-v2-data-prep-for-v3/MigrateBalancesTest.t.sol:57-75` | Test passes vacuously when no holder files exist | ✅ won't-fix — spent one-off |
+| `019fdddc` | `sp-v2-data-prep-for-v3/migrate-StabilityPool_v2-data.md:107-123` | Block headers not normalised before diffing | ✅ won't-fix — spent one-off |
+| `833a2eea` | `sp-v2-data-prep-for-v3/migrate-StabilityPool_v2-data.md:75-84` | Completeness claim unsupported | ✅ won't-fix — spent one-off |
+| `a256bd96` | `sp-v2-upgrade/epoch-removal-summary.md:93-109` | Stale v1/uint104 excerpts | ✅ won't-fix — spent one-off |
+| `ad94cb20` | `sp-v3-migration/run-upgrade-test-remediate-accumulators:68-75` | `--match-path` points at a nonexistent test | ✅ won't-fix — spent one-off |
+| `1f52c246` | `spl-remediation/rebalance-bug-remediation.md:121-125` | Compensatory-mint formula unverified | ✅ won't-fix — spent one-off |
+| `d92076aa` | `spl-remediation/rebalance-remediation.md:78-91` | Duplicate bounty-burning step | ✅ won't-fix — spent one-off |
+| `cbbcac3f` | `spl-remediation/rebalance-remediation.md:81-83` | `TODO: confirm ours` left on the bounty receiver address | ✅ won't-fix — spent one-off |
+
+## doc (25)
+
+### doc/frontend (13) — ✅ won't-fix: belongs in another repository
+
+Integration documentation for frontend consumers. It does not belong in this repository, so
+these are not fixed here; they move with the documentation.
+
+| id | location | finding | status |
+|---|---|---|---|
+| `38f49611` | `claim.md:9-26` | Update the claim ABI to the v3 interface | ✅ won't-fix |
+| `df535dfd` | `claim.md:9-26` | Same | ✅ **duplicate** of `38f49611` |
+| `4aa09fe1` | `claim.md:41-46` | Format reward amounts using each token's decimals | ✅ won't-fix |
+| `10ba8d94` | `claim.md:109-117` | Replace the outdated Minter ABI and calls | ✅ won't-fix |
+| `d10d09b8` | `claim.md:111-117` | Add `maxFeeRatio` to the minter examples | ✅ won't-fix |
+| `55883954` | `claim.md:194-199` | Fix the undefined variable in the proportional split example | ✅ won't-fix |
+| `f0c57f82` | `claim.md:194-199` | Same | ✅ **duplicate** of `55883954` |
+| `19f21952` | `display.md:23-46` | Fix the APR example before publishing it | ✅ won't-fix |
+| `f6320b94` | `redeem.md:38-44` | Limit the `1e18` blocked-value rule to supported operations | ✅ won't-fix |
+| `ffbfc808` | `stability-pool.md:24-52` | Include every documented claim overload in the minimal ABI | ✅ won't-fix |
+| `4e2d4df3` | `stability-pool.md:279-297` | Pass `provider` into `getAllClaimableRewards` | ✅ won't-fix |
+| `f3d57c65` | `tokens.md:11-17` | Correct the `rewardData` tuple order and fields | ✅ won't-fix |
+| `ac1a14ff` | `troubleshooting.md:273-286` | Use the configured mock feed addresses | ✅ won't-fix |
+
+### doc/guides (6) — ✅ won't-fix, except one resolved by code
+
+| id | location | finding | status |
+|---|---|---|---|
+| `12c77243` | `fee-structure.md:145-169` | Require bounty plus cut to remain at or below 100% | ✅ **done** — resolved in code, not documentation. This is the same underlying issue as `39f62036`; `def46d7`'s `updateHarvestRatios` now enforces it |
+| `254a085d` | `deployment.md:3-25` | Separate legacy deployment data from the v3/mainnet guide | ✅ won't-fix |
+| `c57cb1a9` | `deployment.md:7-25` | Label these addresses as legacy v1 | ✅ **duplicate** of `254a085d` |
+| `581cf512` | `deployment.md:17` | CWE-16 misconfiguration | ✅ **duplicate** of `254a085d` |
+| `bb6d5dcf` | `oracle-price-feeds.md:41-48` | Document the protocol oracle's actual wstETH source | ✅ won't-fix |
+| `f760e919` | `risk-parameters.md:107-110` | Alert before the configured rebalance threshold | ✅ won't-fix |
+
+### doc — design documents (6) — ✅ resolved by reconciling each document with the code
+
+These are not integration guides; each describes system behaviour. Every one turned out to be
+a *stale document* rather than a system defect: in all four cases where the finding alleged a
+flaw, the code already did the right thing and the prose had not kept up. Two of them
+(`fea3d21a`, `ceeb58b9`) asked for the implementation to change — neither needed to.
+
+| id | location | finding | status |
+|---|---|---|---|
+| `fea3d21a` | `DataEnvelope.md:103-114` | "Do not ship the known harvest-allocation flaw as an accepted behavior" | ✅ **done** — the flaw no longer exists, and the document was describing a version of the harvest that predates the fairness work. `StabilityPoolManager_v2` keeps a per-pool `owed` ledger ([lines 529-559](../../src/minter/StabilityPoolManager_v2.sol#L529)): a share deferred past one period's capacity stays with the pool that earned it and is never re-split; only genuinely *new* yield is allocated by current holdings. Prose corrected to match — no code change needed |
+| `53af2580` | `DataEnvelope.md:103-107` | Reconcile the documented rebalance overflow behaviour | ✅ **done** — the document contradicted *itself*: line 106 said the rebalance clamps and defers, while lines 156 and 191 said it reverts past the integral. The code clamps up front (`_capLiquidation`, [lines 460-481](../../src/minter/StabilityPoolManager_v2.sol#L460)), whose own comment records that the reward path "could revert on overflow" *before* — so 156/191 were the stale side, not 106. Both corrected |
+| `ceeb58b9` | `autocompounding-vault-design.md:223-227` | Reconcile the HarborYield valuation model | ✅ **done** — the "No oracle. All managed assets are assumed 1:1 pegged" invariant was stale in three places (lines 226, 335, 376). `ValuationLib._fairRateInPegUnits` values an AutoCompounder at its Minter's `peggedTokenPrice()` and an equivalent at the `IWrappedPriceOracle` registered by `addEquivalentVault`, drift-checked at registration. The accurate statement is that HarborYield takes no *global* oracle dependency, not that it is oracle-free; all three corrected |
+| `182c6c4d` | `harbor-deployment.md:217-219` | Do not treat `1e12` base units as universally dust | ✅ **done** — the arithmetic in the finding is right (`1e12` base units is 1e-6 of an 18-decimal token but 10,000 whole tokens at 8 decimals), but the implementation never used that constant. `HarborYieldDeployStack` seeds `peg.minDeposit()` in *pegged* tokens and converts via the oracle in `_wrappedCollateralSeedAmount`, so it carries no `decimals()` assumption at all. The document's "`~1e12` wei" was the stale part; corrected to describe the config-driven mechanism |
+| `b4e6f1f1` | `ideas/rebalance-fairness.md:443-459` | Use the combined fee in the attack example | ✅ **done** — internal inconsistency: the document defines `fee = mintPeggedRatio - redeemPeggedRatio` ≈ 2.0% at CR 1.20 and repeats that figure in its open questions, but the Bob walk-through and the comparison table charged only the mint ratio (1.5%). Walk-through and table corrected to 2.0% / 98.0. The combined formula remains a proposal — the pool implements a plain `earlyWithdrawalFee` — so this is consistency within the design, not a description of shipped behaviour |
+| `2e1f09c3` | `ideas/rebalance-fairness.md:443-459` | Use one withdrawal-fee formula consistently | ✅ **duplicate** of `b4e6f1f1` |
+
+---
+
+# Round 2 — reviews of 2026-08-08 and 2026-08-09
+
+Two further reviews, 16 findings across 15 inline comments (one comment stacks two findings).
+Each was checked against `HEAD` on `harbor-yield`. The **status vocabulary is the same as round
+1** — a ✅ marks a finding needing no further work, a ❌ one that is still outstanding — and the
+status cell carries both the verification result and what closing it takes.
+
+Round 2 reviewed a much smaller diff than round 1, and it shows in the composition: no finding
+touches `src/`. Ten are documentation, three are multisig batch scripts, two are tests, one is
+tooling configuration.
+
+## Summary
+
+| Area | Total | ✅ done | ❌ open | ❌ decide | ✅ false-positive | ✅ duplicate | ✅ won't-fix |
+|---|---|---|---|---|---|---|---|
+| test | 2 | 1 | – | – | – | 1 | – |
+| config | 1 | 1 | – | – | – | – | – |
+| script (multisig batches) | 3 | – | – | – | – | – | 3 |
+| doc — this audit file | 3 | 3 | – | – | – | – | – |
+| doc — design documents | 6 | 5 | – | – | – | – | 1 |
+| regression artefact | 1 | 1 | – | – | – | – | – |
+| **Total** | **16** | **11** | **–** | **–** | **–** | **1** | **4** |
+
+**Nothing from round 2 is outstanding.** Round 1's `56e24c6a`, deploy-script coverage, is the
+single remaining finding of the 86 raised.
+
+Every documentation finding in this round is closed. They fell into one pattern worth naming: in
+five of the six, the document **contradicted itself** rather than the code — one section had been
+brought up to date and another left behind, so the correct statement was usually already present
+elsewhere in the same file. `00dc864c` is the clearest case, being round 1's `182c6c4d` returning
+because that fix corrected §7 and left §11 saying the opposite.
+
+## Two things to know before reading the table
+
+**CodeRabbit's own "✅ Addressed" footers are unreliable.** Three findings carry one, and only one
+of the three had actually been addressed. The mark is attributed to commit *ranges* touching the
+same file, not to a re-check of the finding, so any nearby edit satisfies it.
+
+| id | claim | reality |
+|---|---|---|
+| `69c3be35` | Addressed in `9f166a0`..`3b4c8be` | **true** — the row exists at `regression/gas-duration.txt:167` and `MinterV1ToV2Upgrade.t.sol` is gone |
+| `8cb85991` | Addressed in `9f166a0`..`46c12ef` | **false** — the absolute home paths were still in this document when the mark was applied |
+| `00dc864c` | Addressed in `9f166a0`..`46c12ef` | **false** — the universal `1e12` recommendation is still at `doc/harbor-deployment.md:349` |
+
+**For a multisig batch script, whether it has already executed decides the disposition.** These
+scripts queue transactions into a Safe batch; three findings are about the human-readable label
+beside a queued call, which is the only thing a signer reads when approving. Once the batch has
+been signed and executed, the label has done all the harm or good it ever will and correcting it
+edits the record of what was approved rather than anything anyone will see — won't-fix. All three
+turned out to be in that state.
+
+Establishing which is which is where this audit went wrong once, and the mistake is worth keeping.
+The rule is sound: **executed** is provable from a tracked artefact under `deployments//batch/`,
+whereas **pending is not provable from the absence of one** — and nothing under `deployments/local*/`
+is evidence either way, `.gitignore:44` excluding it, so it records one machine's anvil run.
+
+Having said that, `UpdateHarvestCut_MCAP` was then filed as pending on an inference that does not
+hold: that mainnet still runs `StabilityPoolManager_v1` at all 26 sites, and the script's docstring
+gates it to run *before* the v2 upgrade. Both are true and neither says the script has not run. The
+script does not upgrade anything — it calls `updateHarvestCutRatio` on the *existing* v1 managers,
+and running while v1 is live is precisely its purpose. A precondition on when something must happen
+was read as evidence about whether it had. It had:
+`script/verify/spm-v2-upgrade/upgrade-StabilityPoolManager_v2.md` records both MCAP markets repaired
+at mainnet block **25691117**, with all eleven then holding 1e16 / 99e16.
+
+So: look for the record of the action, not for state that would be consistent with it. A runbook
+naming a block beats any inference from surrounding deployment state.
+
+### The gap this exposed, and closing it
+
+`UpdateVolatility_OGPlus` had its batch artefact committed under `deployments/mainnet/batch/`;
+`UpdateHarvestCut_MCAP` had none. Its only copy sat under `deployments/local/mainnet/batch/`,
+which `.gitignore:44` excludes — so a batch that ran on mainnet left no tracked audit record, and
+the one file that did exist was invisible to everyone else and looked like an anvil dry run. That
+is what made the execution hard to see in the first place.
+
+The file has been moved to
+`deployments/mainnet/batch/UpdateHarvestCut_MCAP_2026-08-05T11:42:54Z_harbor_multisig.json`.
+
+It was checked against the record before being moved rather than after, because a **wrong** file in
+the production area is worse than an absent one: it carries the authority of the directory it sits
+in, and the next person to audit these batches would have no reason to doubt it. Four checks, all
+of which had to agree:
+
+| check | result |
+|---|---|
+| `chainId` | `"1"` — mainnet, not a local chain |
+| selector `0x3ab51d60` | `updateHarvestCutRatio(uint256)` |
+| both arguments | **99e16**, exactly the cut the SPM v2 runbook says all eleven markets now hold |
+| the two `to` addresses | resolve in `deployments/mainnet/harbor_v1.state.json` to `MCAP::fxUSD::stabilityPoolManager` and `MCAP::stETH::stabilityPoolManager` |
+
+So it was only ever in `deployments/local/` because of where the generator wrote it, not because of
+what it contains, and its shape is identical to the tracked `UpdateVolatility_OGPlus` artefact.
+
+One thing it is not: proof of execution. Like every artefact in that directory it records what was
+*queued for signing*, and nothing in the file says it was signed. The execution record remains the
+runbook's line naming mainnet block **25691117**. The two are complementary — the artefact says
+what was approved, the runbook says that it landed and when — and neither substitutes for the
+other.
+
+## test (2)
+
+| id | location | finding | status |
+|---|---|---|---|
+| `98c0c63a` | `StabilityPoolManager.t.sol:1345` | `test_harvestAfterRatioPairRepair_` pins `Panic(uint256) 0x11` rather than a named error, and a guard should be added to `harvest()` | ✅ **duplicate** of `39f62036`, closed won't-fix in round 1 — the over-100% pair is unreachable through v2's API (`updateHarvestRatios` validates the sum atomically), reachable only as legacy v1 storage, and every live deployment was verified compliant; `Minter_v3` has 134 B of headroom, so a guard against an unreachable state is not free. The test half is a new point and is answered in the discussion below: the assertion stands |
+| `8cb2fa0e` | `PartialDeploy.t.sol:33-34` | `_ensureBaoFactory()` is called before `forkMainnet()`, and the fork resets the operator registration | ✅ **done** — the ordering was real and the stated consequence did not occur, because the next two lines silently repaired it; that is what made it worth fixing rather than what excused it. `BaoTest.forkMainnetWithBaoFactory()` now pairs the two steps in the only order that works, and all **eleven** sites are converted, so no manual `setOperator` remains in `test/` or `script/`. Proven before it was built: removing only the repair, keeping the wrong order, failed every affected contract with `BaoFactory_v1`'s `Unauthorized()` — so the detector already exists at the point of use, on every path including `script/verify/`, and the repair was suppressing it. Pinned by `lib/bao-base/test/BaoFactoryAcrossForks.t.sol`. Net **−56 lines**, and the full suite plus `sizes`, `gas` and `coverage` regressions are unchanged |
+
+## config (1)
+
+| id | location | finding | status |
+|---|---|---|---|
+| `7fa21b38` | `.claude/settings.json:20-21` | Recursive `Read` access to user-local `.claude` plans and project state | ✅ **done** — half fixed, half declined for a reason round 1 already accepted. The rule granting recursive read access to this project's own `~/.claude/projects/` state directory had nothing requiring it and is **gone**. The plans rule remains, rewritten as `Read(~/.claude/plans/**)`: `lib/bao-base/CLAUDE.md` *requires* committing to the plan repo after every plan update, so removing it would break the documented working mode — which is exactly why the sibling finding `87c9d0d1` was closed won't-fix. Every absolute home path is gone from the file, which also settles the disclosure half |
+
+## script — multisig batch scripts (3)
+
+The queued *call data* is correct in all three; what is wrong is the label beside it. Disposition
+follows execution status, per the rule above — `UpdateVolatility_OGPlus` executed on mainnet on
+2026-01-15, `UpdateHarvestCut_MCAP` is still pending, gated behind the `StabilityPoolManager_v2`
+upgrade it must precede.
+
+| id | location | finding | status |
+|---|---|---|---|
+| `e5c2d779` | `UpdateVolatility_OGPlus.s.sol:60` | GOLD-fxUSD is labelled `updateConfig(105 month1)` but encodes `ConfigPriceVolatility_115` | ✅ **won't-fix** — the finding is correct: the label was copied from the EUR-fxUSD entry above it, and the paired threshold on line 63 is `115e16`, so the label is the only thing saying 105. But the batch has already been signed and executed — `deployments/mainnet/batch/UpdateVolatility_OGPlus_2026-01-15T21:56:04Z.json` is tracked, and is the record of what was approved — so the label has been read for the last time. Correcting it now would edit that record rather than anything a signer will see |
+| `e9e7093e` | `UpdateHarvestCut_MCAP.s.sol:27,35` | Both queue entries carry the identical description `updateHarvestCutRatio(configured)` | ✅ **won't-fix** — the finding is correct: the decoded batch showed two identical labels against two different managers, so a signer could not tell MCAP::fxUSD from MCAP::stETH. But the batch **has** executed — `script/verify/spm-v2-upgrade/upgrade-StabilityPoolManager_v2.md` records both markets repaired by this script at mainnet block **25691117**, all eleven markets now holding 1e16 / 99e16 — so the labels have been read for the last time, and the same reasoning applies as to `e5c2d779` |
+| `389b2d60` | `UpdateHarvestCut_MCAP.s.sol:18` | The run instruction names `./script/safe-batch`, which does not exist | ✅ **won't-fix** — confirmed, and CodeRabbit's replacement is wrong too: it proposes `run-script`, but this contract implements `build()` and queues a batch. Moot either way, the script having executed at block 25691117. Two sibling scripts spell the runner wrongly as well — `UpdateVolatility_OGPlus.s.sol:15` repeats `safe-batch`, `UpdateVolatility_test3_SILVER.s.sol:13` invents `generate-safe-batch` — and all three have run, so the wrong name has misdirected nobody. Worth correcting only if one of them is ever used as a template |
+
+## doc — this audit file (3)
+
+| id | location | finding | status |
+|---|---|---|---|
+| `7cf3b00b` | `:56-60` | "A sixth (`077b539f`)" contradicts the stated five false positives | ✅ **done** — confirmed: `077b539f` is one of the five, so the prose double-counted it. Now reads "One of the five, `077b539f`, would additionally have broken a working script", which keeps the point the sentence was making without inventing a sixth finding |
+| `446b3be0` | `:96` vs `:219-221` | `ForceMigrateAccumulator_v1` is called pending in one place and already-run archive in another | ✅ **done** — confirmed. The status entry cites its evidence (`StabilityPool_v3` appears 3 times across `deployments/` against `StabilityPool_v2`'s 105); the appendix note calling it "migration code that has already run" was an unchecked aside. The appendix now states it is not archive, and says what the missing coverage actually is — unit tests for its assembly slot arithmetic, which need no fork |
+| `8cb85991` | `:88` | Exact local filesystem paths in a public repository | ✅ **done** — the last four occurrences, all in this document, now describe the permission rules rather than quoting them verbatim, which loses nothing since every rule they quoted has since been removed or rewritten. Worth recording *why* it was declined twice before: while `.claude/settings.json` still held twelve of these paths, scrubbing the prose and leaving the configuration would have been theatre, and it would have cost each entry the rule string that was its evidence. `7fa21b38` removed those twelve and the developer cleared `.claude/settings.local.json` as well, at which point this document became the only tracked file still carrying them and the objection expired. **No absolute home path now appears in any tracked file.** |
+
+## doc — design documents (6)
+
+Five of these are `doc/stability-pool-min-total-asset-supply.md`, an analysis document evaluating
+whether `MIN_TOTAL_ASSET_SUPPLY` could become settable. Its purpose is to be *checkable* against
+the code, which raises the cost of a stale reference above the usual for prose.
+
+| id | location | finding | status |
+|---|---|---|---|
+| `ea3f51b0` | `min-total-asset-supply.md:139-143` | The proposed setter guard admits `newMin == supply`, which freezes all outflow | ✅ **done** — the most substantive finding of the round, and correct. The document proposed the band `newMin <= supply <= newMin·FP`, saying the lower bound "keeps outflow headroom above zero"; at equality the headroom `supply − MIN` is exactly zero, so `_capToFloor` caps every withdrawal, sweep and liquidation loss to nothing — the state the document's own §4 calls "a solvency hazard, not a mere inconvenience". The band is now strict (`newMin < supply`), §4 rejects equality explicitly rather than only `newMin > supply`, and the empty pool is routed to the pristine-pool sub-decision in §5 instead of being silently caught by a bound it cannot satisfy |
+| `905c3b88` | `min-total-asset-supply.md`, throughout | Cited source line numbers do not identify the described logic | ✅ **done** — confirmed, and systematic: `:301-303` was the zero-check revert, not the MAX calculation (312); `:442` was `supply.updatedAt`, not the ceiling check (453); `:622` a comment, not `_minTotalShare` (644); `:673` a comment, not the loss-per-unit division (695); `:687` was `lastAssetLossError`, not the product factor (708-710); `:748` a docstring, not `_capToFloor` (776). Only `:300` and `:93` still landed. **All twelve numeric citations are now symbol names** (`_capToFloor`, `maxAssetLoss`, `_minTotalShare`, `DepositAmountExceedsMaximum`, `migrateAndUpgrade`, …) — the file is named once at the top, so nothing re-breaks when the code moves, which is what made them stale in the first place |
+| `2558c8fd` | `min-total-asset-supply.md:41-42` | `supply <= MIN * FACTOR_PRECISION ( = MAX )` ignores `uint128` saturation | ✅ **done** — confirmed, and the document contradicted *itself*: §1 already stated `MAX = min(MIN·FP, uint128.max)`, matching the constructor. The derivation now presents `supply <= MIN·FP` as the *arithmetic requirement* and MAX as that bound saturated at the supply field's width, with the reason the clamp costs nothing: supply is stored in a `uint128`, so a ceiling above `uint128.max` is unreachable, and `supply <= MAX` therefore always implies `supply <= MIN·FP` |
+| `c847c207` | `min-total-asset-supply.md:102` | "six current markets" understates the deployment matrix | ✅ **done** — confirmed: there are **6 pegs and 11 markets** (`ConfigPeg_{BTC,ETH,EUR,GOLD,MCAP,SILVER}`, and eleven `ConfigMarket_*_mainnet`). CodeRabbit offered two remedies and only the first was right: `MIN` is a pegged-token quantity carried on the peg, so it takes six values, not eleven — enumerating the 11 market instances would have been *less* accurate. The text now says so explicitly rather than just swapping the noun, since the one-value-per-peg fact is why six is the right count |
+| `00dc864c` | `harbor-deployment.md:349` | The Open questions section still recommends a universal `1e12` seed | ✅ **done** — confirmed present despite the "addressed" mark, and it was round 1's `182c6c4d` resurfacing: that finding was closed by correcting §7, but §11 was left contradicting it. The open question is now struck through and marked resolved as **neither** of the two options it posed — the seed is denominated in pegged tokens as `peg.minDeposit()` and oracle-converted by `_wrappedCollateralSeedAmount`, so it carries no `decimals()` assumption for either answer to handle |
+| `bc848afa` | `min-total-asset-supply.md`, fenced blocks | `markdownlint` MD040: code fences without a language | ✅ **won't-fix** — the repository has no markdownlint configuration and does not run it in CI, so this is CodeRabbit's own linter reporting against its own defaults, not a standard this codebase holds. Adopting markdownlint would be a deliberate decision of its own, not a review fix |
+
+## regression artefact (1)
+
+| id | location | finding | status |
+|---|---|---|---|
+| `69c3be35` | `regression/gas-duration.txt:167` | No row for `MinterV2ToV3UpgradeTest`, and no exclusion explaining its absence | ✅ **done** — the row is present at line 167, and `MinterV1ToV2Upgrade.t.sol` (whose row the finding saw instead) no longer exists, having been retired when the upgrade tests were retargeted at the pending v2→v3 deploy |
+
+## Discussion — how the three hard ones were settled
+
+All three are closed. Kept because the reasoning is reusable and each cost more than the fix did.
+
+### 1. `7fa21b38` and `8cb85991` — settled, and what settled them
+
+It took three findings to land, and the reason is reusable. The file mixed two things: **project
+policy** (which tools this codebase's work needs) and **one machine's absolute paths**. Every
+finding objected to the second kind, and each was answered by narrowing one more rule, which is
+why a third arrived.
+
+What closed it was removing the category rather than the instances — the project-state read is
+gone entirely, and the plans read is now `Read(~/.claude/plans/**)`, which says the same thing
+without naming a home directory. That rule stays: `lib/bao-base/CLAUDE.md` *requires* committing
+to the plan repo after every plan update, so it implements the documented working mode, exactly
+the grounds round 1 closed `87c9d0d1` on.
+
+That left `8cb85991` as the only place the paths survived, which flipped its disposition: declining
+it had rested on the settings file holding twelve of them, and scrubbing prose while leaving
+configuration would have been theatre. With the configuration clean the objection expired and the
+fix was free, the quoted rules having ceased to exist. One loose end remains:
+`.claude/settings.local.json` is **git-tracked and not in `.gitignore`**, which defeats the point
+of the `.local` name and is what put it in front of a reviewer as `ea436f80`.
+
+### 2. `8cb2fa0e` — a misleading precedent, not an inefficiency
+
+`_ensureBaoFactory()` → `forkMainnet()` → `setOperator()` was what eight suites did, so this was a
+convention rather than a slip in one file.
+
+The wasted work — deploying a BaoFactory into state `createSelectFork` immediately discards — was
+the least of it. What mattered is that **the order was wrong and the next two lines silently
+repaired it**, so the sequence taught a reader something false: read straight it said "ensure the
+factory, then fork", implying the factory survives the fork. It does not, and copying the shape
+without the repair produced a failure nowhere near its cause.
+
+Two mechanisms were considered for catching the mistake and both were rejected, the second by
+measurement rather than argument:
+
+- **A flag set by the ensure and read by the fork** is the transient state `CLAUDE.md` bans, would
+ guard only forks taken through `forkMainnet()` while `script/verify/` calls `vm.createSelectFork`
+ directly, and detects rather than removes.
+- **`vm.makePersistent(factory)`** looked attractive because the registration would then survive.
+ **It does not.** `_isCurrentOperator` is `exists && expiry > block.timestamp`, and
+ `block.timestamp` is **1** in the bare test EVM against **1774019135** on the fork, so an expiry
+ of `now + 365 days` has lapsed by decades whether or not the account carried over. It would also
+ have put a local rebuild in front of the deployed factory these suites exist to deploy through.
+
+**No detector was needed.** `BaoFactory_v1:179-180` already reverts `Unauthorized()` when a
+non-operator calls `deploy` — at the point of use, on every path. The hand-written `setOperator`
+was suppressing that signal, so the fix removed three lines rather than adding a check. Confirmed
+by removing only the repair and watching every affected contract fail with exactly that error.
+
+### 3. `98c0c63a` — the half round 1 did not answer
+
+The contract half is `39f62036` again and is marked ✅ **duplicate** above; nothing has changed to
+reopen it. The test half is new, and deserves a straight answer rather than the same one.
+CodeRabbit argues the test's
+`Panic(0x11)` assertion binds to compiler-generated behaviour and would break under an `unchecked`
+block or a reordered subtraction. True — but that is the assertion being *load-bearing*, not
+brittle: the property under test is that a legacy over-100% pair has no harvest until repaired,
+and if someone wraps that subtraction in `unchecked` the pair silently produces a wrong split
+instead of reverting. A test that goes red on that change is doing its job. The assertion is also
+specific — `abi.encodeWithSignature("Panic(uint256)", 0x11)`, not a bare `expectRevert` — which is
+what the repository's rule actually requires.
+
+Worth fixing while nearby, and unrelated to the finding: the test is named
+`test_harvestAfterRatioPairRepair_` with a trailing underscore.
+
+---
+
+## Appendix — fix prompt for round 1's outstanding finding
+
+CodeRabbit generated a "Prompt for AI Agents" alongside each finding. The one still outstanding
+is reproduced verbatim below so it can be handed to a fresh session without re-reading the pull
+request.
+
+**Treat it as the reporter's opinion, not as instructions.** Each prompt prescribes
+CodeRabbit's own proposed fix, which was frequently not the right one: this audit closed five
+findings as false positives, corrected two whose stated consequence was wrong, and declined one
+whose fix would have broken a working script. Where a status entry above and a prompt disagree,
+the status entry is the decision that was actually taken. Every prompt opens by telling the
+reader to verify the finding against current code first — that instruction is the useful part,
+and it is the one that kept paying off.
+
+### ❌ open
+
+#### `56e24c6a` — `regression/coverage.txt:3`, uncovered deploy entrypoints
+
+```
+In `@regression/coverage.txt` at line 3, Add regression tests covering the main
+execution paths and failure branches for ForceMigrateAccumulator_v1.sol,
+HarborDeployStack.sol, and HarborDeployer.sol. Update the tests so the reported
+coverage entries in regression/coverage.txt lines 3-3 and 34-35 no longer remain
+at 0% or low coverage; do not modify coverage.txt directly if it is generated.
+```
+
+`ForceMigrateAccumulator_v1` is **not** archive — its migration is still pending, as the status
+entry above records: `StabilityPool_v3` appears 3 times across `deployments/` against
+`StabilityPool_v2`'s 105. It is also already tested, by a fork test that cannot join `yarn test`;
+what is missing is unit coverage of its assembly slot arithmetic, which needs no fork.
+`HarborDeployStack` and `HarborDeployer` are the other two targets. Note the prompt's closing
+caution: `regression/coverage.txt` is generated, so it is the tests that change, not the file.
diff --git a/doc/frontend/claim.md b/doc/frontend/claim.md
new file mode 100644
index 00000000..4408d032
--- /dev/null
+++ b/doc/frontend/claim.md
@@ -0,0 +1,221 @@
+# Claim and Compound Rewards
+
+## Claim Interface
+
+Call `claim()` **directly on the Stability Pool contract** (not on StabilityPoolManager or a separate rewards contract). The Stability Pool implements `IMultipleRewardAccumulator`.
+
+### Claim Function Variants
+
+```solidity
+function claim() external; // Claim all for caller
+function claim(address account) external; // Claim all for account
+function claim(address account, address receiver) external; // Claim all, send to receiver
+function claimHistorical(address[] memory tokens) external; // Claim specific historical tokens
+```
+
+### Required ABI
+
+```typescript
+const STABILITY_POOL_REWARDS_ABI = [
+ "function activeRewardTokens() view returns (address[])",
+ "function claimable(address account, address token) view returns (uint256)",
+ "function claim() external",
+ "function claim(address account) external",
+ "function claim(address account, address receiver) external",
+ "event Claim(address indexed account, address indexed token, address indexed receiver, uint256 amount)",
+];
+```
+
+---
+
+## Basic Claim
+
+### Step 1: Check Claimable Rewards
+
+```typescript
+async function checkClaimableRewards(poolAddress: string, userAddress: string, provider: any) {
+ const pool = new Contract(poolAddress, STABILITY_POOL_REWARDS_ABI, provider);
+ const rewardTokens = await pool.activeRewardTokens();
+ const claimableRewards = [];
+
+ for (const tokenAddress of rewardTokens) {
+ const claimable = await pool.claimable(userAddress, tokenAddress);
+ if (claimable > 0n) {
+ const tokenContract = new Contract(tokenAddress, ["function symbol() view returns (string)"], provider);
+ const symbol = await tokenContract.symbol();
+ claimableRewards.push({ token: tokenAddress, symbol, amount: claimable, amountFormatted: formatEther(claimable) });
+ }
+ }
+ return claimableRewards;
+}
+```
+
+### Step 2: Execute Claim
+
+```typescript
+// Simplest form: claims all active reward tokens for the signer
+const pool = new Contract(poolAddress, STABILITY_POOL_REWARDS_ABI, signer);
+const tx = await pool.claim();
+await tx.wait();
+```
+
+### Claim to a Different Receiver
+
+```typescript
+const tx = await pool.claim(accountAddress, receiverAddress);
+```
+
+### Claim from Multiple Pools
+
+```typescript
+async function claimFromMultiplePools(poolAddresses: string[], signer: any) {
+ const txs = await Promise.all(
+ poolAddresses.map(address => new Contract(address, STABILITY_POOL_REWARDS_ABI, signer).claim())
+ );
+ return Promise.all(txs.map(tx => tx.wait()));
+}
+```
+
+### Verify via Claim Events
+
+```typescript
+pool.on("Claim", (account, token, receiver, amount) => {
+ if (account.toLowerCase() === userAddress.toLowerCase()) {
+ console.log(`Claimed ${amount} of token ${token}`);
+ }
+});
+```
+
+### Testing with cast
+
+```bash
+# Check claimable
+cast call "claimable(address,address)(uint256)" --rpc-url http://localhost:8545
+
+# Claim
+cast send "claim()" --private-key --rpc-url http://localhost:8545
+```
+
+---
+
+## Compound
+
+Compound reinvests rewards back into stability pools. The flow depends on the reward token type:
+
+- **Collateral (wstETH)**: Claim -> Mint ha tokens -> Deposit to pool(s)
+- **hs Tokens (Leveraged)**: Claim -> Redeem for collateral -> Mint ha tokens -> Deposit to pool(s)
+- **ha Tokens (Pegged)**: Claim -> Deposit directly to pool(s)
+
+### Required ABIs
+
+```typescript
+const MINTER_ABI = [
+ "function mintPeggedToken(uint256 collateralAmount, address receiver, uint256 minPeggedOut) returns (uint256)",
+ "function mintPeggedTokenDryRun(uint256 collateralAmount) view returns (uint256 peggedOut, uint256 wrappedFee, uint256 fee)",
+ "function redeemLeveragedToken(uint256 leveragedAmount, address receiver, uint256 minCollateralOut) returns (uint256)",
+ "function redeemLeveragedTokenDryRun(uint256 leveragedAmount) view returns (uint256 collateralOut, uint256 wrappedFee, uint256 fee)",
+];
+```
+
+### Step 1: Categorize Reward Tokens
+
+```typescript
+async function categorizeRewards(userAddress: string, pools: any[], wstETH: string, haToken: string, hsToken: string, provider: any) {
+ const rewards = [];
+ for (const pool of pools) {
+ const poolContract = new Contract(pool.address, STABILITY_POOL_ABI, provider);
+ const rewardTokens = await poolContract.activeRewardTokens();
+
+ for (const tokenAddress of rewardTokens) {
+ const claimable = await poolContract.claimable(userAddress, tokenAddress);
+ if (claimable > 0n) {
+ const tokenLower = tokenAddress.toLowerCase();
+ let type: "collateral" | "ha" | "hs";
+ if (tokenLower === wstETH.toLowerCase()) type = "collateral";
+ else if (tokenLower === haToken.toLowerCase()) type = "ha";
+ else if (tokenLower === hsToken.toLowerCase()) type = "hs";
+ else continue;
+
+ rewards.push({ token: tokenAddress, amount: claimable, type, poolAddress: pool.address });
+ }
+ }
+ }
+ return rewards;
+}
+```
+
+### Step 2: Estimate Fees
+
+```typescript
+// For collateral -> ha tokens
+const [peggedOut, , fee] = await minter.mintPeggedTokenDryRun(collateralAmount);
+const minPeggedOut = (peggedOut * 95n) / 100n; // 5% slippage
+
+// For hs tokens -> collateral
+const [collateralOut, , fee] = await minter.redeemLeveragedTokenDryRun(hsTokenAmount);
+const minCollateralOut = (collateralOut * 95n) / 100n;
+```
+
+### Step 3: Execute Compound
+
+Transaction order matters:
+
+1. **Claim** rewards to user's wallet
+2. **Approve** contracts to spend tokens
+3. **Mint** ha tokens (if collateral rewards)
+4. **Deposit** to stability pools
+
+```typescript
+// 1. Claim
+const claimTx = await pool.claim(userAddress, userAddress);
+await claimTx.wait();
+
+// 2. Approve minter (for collateral rewards)
+await wstETH.approve(minterAddress, rewardAmount);
+
+// 3. Mint ha tokens
+const mintTx = await minter.mintPeggedToken(rewardAmount, userAddress, minPeggedOut);
+await mintTx.wait();
+
+// 4. Deposit to pool
+await haToken.approve(targetPoolAddress, depositAmount);
+await targetPool.deposit(depositAmount, userAddress, depositAmount);
+```
+
+### Split Strategies
+
+**Equal split:**
+```typescript
+const amountPerPool = totalAmount / BigInt(targetPools.length);
+const remainder = totalAmount % BigInt(targetPools.length);
+// Add remainder to first pool
+```
+
+**Proportional split (by existing deposit size):**
+```typescript
+const balances = await Promise.all(targetPools.map(p => pool.assetBalanceOf(userAddress)));
+const totalBalance = balances.reduce((sum, b) => sum + b, 0n);
+// proportion = (totalAmount * balance) / totalBalance for each pool
+```
+
+---
+
+## Common Issues
+
+### "No claimable rewards" (`claimable()` returns 0)
+
+- Check user has deposits: `assetBalanceOf(userAddress)`
+- Check if rewards have been deposited to the pool
+- Verify rewards are not still vesting (check `rewardData()`)
+- Verify the correct pool address
+
+### Transaction reverts on `claim()`
+
+- Always check `claimable()` first
+- Verify using stability pool address, not the manager
+- Verify ABI is correct
+- Estimate gas first: `const gas = await pool.claim.estimateGas()`
+
+### Insufficient gas
+
+Add a buffer: `const tx = await pool.claim({ gasLimit: gasEstimate * 120n / 100n })`
diff --git a/doc/frontend/config.md b/doc/frontend/config.md
new file mode 100644
index 00000000..2c16ad75
--- /dev/null
+++ b/doc/frontend/config.md
@@ -0,0 +1,151 @@
+# Frontend Configuration
+
+## Network Configuration
+
+```typescript
+const anvilNetwork = {
+ chainId: 31337,
+ chainName: "Anvil Local",
+ nativeCurrency: {
+ name: "Ether",
+ symbol: "ETH",
+ decimals: 18,
+ },
+ rpcUrls: ["http://localhost:8545"],
+ blockExplorerUrls: [],
+};
+```
+
+## Environment Variables
+
+```bash
+# GraphQL Endpoint
+NEXT_PUBLIC_GRAPH_URL=http://localhost:8000/subgraphs/name/harbor-marks-local
+
+# Network Configuration
+NEXT_PUBLIC_CHAIN_ID=31337
+NEXT_PUBLIC_RPC_URL=http://localhost:8545
+
+# Contract Addresses
+NEXT_PUBLIC_GENESIS_CONTRACT=0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82
+NEXT_PUBLIC_MINTER_CONTRACT=0x8A791620dd6260079BF849Dc5567aDC3F2FdC318
+NEXT_PUBLIC_PEGGED_TOKEN=0x0165878A594ca255338adfa4d48449f69242Eb8F
+NEXT_PUBLIC_LEVERAGED_TOKEN=0xa513E6E4b8f2a923D98304ec87F64353C4D5C853
+NEXT_PUBLIC_WSTETH=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
+NEXT_PUBLIC_STETH=0x5FbDB2315678afecb367f032d93F642f64180aa3
+```
+
+## Contract Addresses
+
+### Core Contracts
+
+```typescript
+export const contracts = {
+ genesis: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
+ minter: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
+ peggedToken: "0x0165878A594ca255338adfa4d48449f69242Eb8F",
+ leveragedToken: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
+ reservePool: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788",
+ stabilityPoolManager: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0",
+ feeReceiver: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
+ collateralToken: "0x5FbDB2315678afecb367f032d93F642f64180aa3", // Mock stETH
+ wrappedCollateralToken: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", // Mock wstETH
+ stabilityPoolCollateral: "0xf5059a5D33d5853360D16C683c16e67980206f36",
+ stabilityPoolSail: "0x99bbA657f2BbC93c02D617f8bA121cB8Fc104Acf",
+} as const;
+```
+
+### Token Information
+
+| Token | Address | Symbol |
+|-------|---------|--------|
+| Pegged Token | `0x0165878A594ca255338adfa4d48449f69242Eb8F` | haPB |
+| Leveraged Token | `0xa513E6E4b8f2a923D98304ec87F64353C4D5C853` | hshsPBxstETH |
+| stETH (Mock) | `0x5FbDB2315678afecb367f032d93F642f64180aa3` | stETH |
+| wstETH (Mock) | `0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512` | wstETH |
+
+### Price Feeds (Mock Chainlink)
+
+| Feed | Address | Value |
+|------|---------|-------|
+| stETH/USD | `0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0` | $2000 (200000000000, 8 decimals) |
+| stETH/ETH | `0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9` | 1.0 (100000000, 8 decimals) |
+| wstETH/USD | `0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9` | $2000 (200000000000, 8 decimals) |
+
+## Subgraph Configuration
+
+```yaml
+network: anvil
+source:
+ address: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82"
+ startBlock: 55
+```
+
+### Deploy Subgraph
+
+```bash
+graph create --node http://localhost:8020/ harbor-marks-local
+graph build
+graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 harbor-marks-local
+```
+
+Check indexing status: http://localhost:8030/graphql
+
+## GraphQL Queries
+
+### Get User Harbor Marks
+
+```graphql
+query GetUserHarborMarks($user: Bytes!) {
+ userHarborMarks(id: $user) {
+ id
+ totalDeposited
+ totalWithdrawn
+ currentBalance
+ }
+}
+```
+
+### Get Deposits
+
+```graphql
+query GetDeposits($user: Bytes!) {
+ deposits(where: { user: $user }, orderBy: timestamp, orderDirection: desc, first: 10) {
+ id
+ user
+ token
+ amount
+ timestamp
+ blockNumber
+ }
+}
+```
+
+### Get Withdrawals
+
+```graphql
+query GetWithdrawals($user: Bytes!) {
+ withdrawals(where: { user: $user }, orderBy: timestamp, orderDirection: desc, first: 10) {
+ id
+ user
+ token
+ amount
+ timestamp
+ blockNumber
+ }
+}
+```
+
+## Developer Account (Testing)
+
+- **Address**: `0xAE7Dbb17bc40D53A6363409c6B1ED88d3cFdc31e`
+- **Balances**: 1000 stETH, 1000 wstETH, 1600 ETH
+- **Permissions**: Owner of Genesis, ZERO_FEE_ROLE on Minter
+- **Default Deployer**: `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266` (Anvil account 0)
+
+## Notes
+
+- This is a clean Anvil chain (no mainnet fork). All contracts are newly deployed mocks.
+- stETH and wstETH are mock contracts implementing standard interfaces but simplified for local testing.
+- Docker Desktop must be running for Graph Node.
+- Graph Node requires starting with `cd graph-node-local && docker compose up -d`.
diff --git a/doc/frontend/display.md b/doc/frontend/display.md
new file mode 100644
index 00000000..9fb140af
--- /dev/null
+++ b/doc/frontend/display.md
@@ -0,0 +1,244 @@
+# Display Calculations
+
+## APR Calculation (Next Period Projection)
+
+This calculates the projected APR for the next reward period, based on the current harvestable amount. Useful at launch before any harvests have occurred.
+
+### Calculation Flow
+
+```
+1. Get harvestable amount from minter
+2. Deduct harvest bounty and cut ratios
+3. Split remaining across pools by their deposit ratio
+4. Add any queued rewards for the target pool
+5. Calculate reward rate: totalRewards / REWARD_PERIOD_LENGTH
+6. Calculate per-token rate: rate / totalPoolSupply
+7. Project user's 7-day rewards: ratePerToken * userBalance * 604800
+8. Annualize: (rewardsValueUSD / depositValueUSD) * (365/7) * 100
+```
+
+### Key Contracts and Values
+
+```typescript
+const harvestableAmount = await minter.harvestable();
+const harvestBountyRatio = await stabilityPoolManager.harvestBountyRatio();
+const harvestCutRatio = await stabilityPoolManager.harvestCutRatio();
+const REWARD_PERIOD_LENGTH = 604800; // 7 days in seconds
+
+// Deductions
+const bounty = (harvestable * bountyRatio) / 1e18;
+const cut = (harvestable * cutRatio) / 1e18;
+const remaining = harvestable - bounty - cut;
+
+// Pool split
+const poolCollateral = await stabilityPoolCollateral.totalAssetSupply();
+const poolLeveraged = await stabilityPoolLeveraged.totalAssetSupply();
+const toThisPool = (remaining * thisPoolSupply) / (poolCollateral + poolLeveraged);
+
+// Rate
+const { queued } = await stabilityPool.rewardData(rewardToken);
+const totalRewards = toThisPool + queued;
+const rate = totalRewards / BigInt(REWARD_PERIOD_LENGTH);
+
+// APR
+const ratePerToken = Number(rate) / Number(totalSupply);
+const rewards7Days = ratePerToken * Number(userBalance) * 604800;
+const apr = (rewardsValueUSD / depositValueUSD) * (365 / 7) * 100;
+```
+
+### Projecting Additional Yield
+
+To account for wstETH rate growth over the remaining period:
+
+```typescript
+const currentRate = await wstETH.stEthPerToken();
+const STAKING_APR = 0.035; // 3.5%
+const remainingDays = Number(remainingSeconds) / 86400;
+const rateGrowthFactor = 1 + (STAKING_APR / 365) * remainingDays;
+const projectedRate = (currentRate * BigInt(Math.floor(rateGrowthFactor * 1e18))) / 1e18;
+```
+
+### Edge Cases
+
+- No harvestable amount: return 0
+- No deposits in pool: return 0
+- Empty pool: return 0
+- Multiple reward tokens: calculate APR for each and sum
+
+---
+
+## Leverage Ratio
+
+The leverage ratio represents the exposure multiplier for leveraged (sail) tokens.
+
+### Formula
+
+```
+leverageRatio = collateralValue / (collateralValue - peggedValue)
+```
+
+### Fetching
+
+```typescript
+const leverageRatioRaw = await minter.leverageRatio(); // uint256, 18 decimals
+const leverageRatio = parseFloat(leverageRatioRaw.toString()) / 1e18;
+```
+
+### Interpretation
+
+| Ratio | Risk Level | Description |
+|-------|-----------|-------------|
+| < 1.5x | Low | Low leverage |
+| 1.5x - 2.0x | Low-Medium | Low leverage |
+| 2.0x - 3.0x | Medium | Moderate leverage |
+| 3.0x - 5.0x | High | High leverage |
+| > 5.0x | Very High | Very high leverage |
+
+### Display Format
+
+Always show as "X.XXx" (e.g., "2.50x").
+
+### Edge Cases
+
+- The contract caps leverage ratio at `_LEVERAGE_RATIO_CAP`
+- Zero pegged tokens with collateral: returns cap or very large number
+- Empty system: returns a default value
+
+### Notes
+
+- Returns 18-decimal value -- always divide by 1e18
+- Depends on the price oracle -- handle stale price errors
+- Refresh every 30 seconds or on new blocks
+
+---
+
+## Pegged Token Value
+
+The pegged token (haPB) targets a $1.00 USD peg. The actual redemption value can vary.
+
+### Fetching Price
+
+```typescript
+const priceRaw = await minter.peggedTokenPrice(); // uint256, 18 decimals
+const priceInStETH = parseFloat(priceRaw.toString()) / 1e18;
+```
+
+The returned value is in **stETH units** (not USD). To get USD:
+
+```
+priceUSD = priceInStETH * stETHPriceUSD
+```
+
+Example: if `peggedTokenPrice()` returns 0.0005 and stETH = $2000, then 1 haPB = 0.0005 * $2000 = $1.00.
+
+### Simplified Approach
+
+For most frontend purposes, assume $1.00 per pegged token:
+
+```typescript
+const PEGGED_TOKEN_PRICE_USD = 1.0;
+```
+
+Use `peggedTokenPrice()` only for:
+- Detecting depeg status
+- Showing actual redemption value
+- Advanced calculations
+
+### Depeg Detection
+
+```typescript
+const priceUSD = priceInStETH * stETHPriceUSD;
+const isPegged = Math.abs(priceUSD - 1.0) < 0.01; // Within 1 cent
+```
+
+When `peggedTokenBalance() == 0`, the function returns 1.0 as default.
+
+---
+
+## Marks Display
+
+### Mark Types and Rates
+
+| Source | Rate | Multiplier |
+|--------|------|-----------|
+| Ha Tokens (wallet holdings) | 1 mark/dollar/day | 1x |
+| Stability Pool Deposits | 1 mark/dollar/day | 1x |
+| Sail Tokens (wallet holdings) | 5 marks/dollar/day | 5x (default) |
+
+**Anchor Ledger Marks** = Ha Token marks + Stability Pool marks (both 1x).
+
+### GraphQL: All Marks Sources
+
+```graphql
+query GetAllUserMarks($userAddress: Bytes!, $genesisId: ID!) {
+ haTokenBalances(where: { user: $userAddress }) {
+ accumulatedMarks
+ marksPerDay
+ balanceUSD
+ lastUpdated
+ }
+ sailTokenBalances(where: { user: $userAddress }) {
+ accumulatedMarks
+ marksPerDay
+ balanceUSD
+ lastUpdated
+ }
+ stabilityPoolDeposits(where: { user: $userAddress }) {
+ accumulatedMarks
+ marksPerDay
+ balanceUSD
+ lastUpdated
+ }
+ userHarborMarks(id: $genesisId) {
+ currentMarks
+ marksPerDay
+ totalMarksEarned
+ }
+}
+```
+
+### Real-Time Estimation (Zero Gas)
+
+The subgraph stores marks at the time of the last on-chain event. Estimate current marks on the frontend:
+
+```typescript
+function calculateEstimatedMarks(balance: { accumulatedMarks: string; marksPerDay: string; lastUpdated: string }): number {
+ const storedMarks = parseFloat(balance.accumulatedMarks || "0");
+ const marksPerDay = parseFloat(balance.marksPerDay || "0");
+ const lastUpdated = parseInt(balance.lastUpdated || "0");
+
+ if (lastUpdated === 0 || marksPerDay === 0) return storedMarks;
+
+ const now = Math.floor(Date.now() / 1000);
+ const daysSinceUpdate = (now - lastUpdated) / 86400;
+ return storedMarks + marksPerDay * daysSinceUpdate;
+}
+```
+
+Update this calculation every 1 second for a smooth live counter. Poll the subgraph every 60 seconds for new on-chain events.
+
+### Combining All Marks
+
+```typescript
+const totalMarks = haMarks + sailMarks + poolMarks + genesisMarks;
+const totalMarksPerDay = haMarksPerDay + sailMarksPerDay + poolMarksPerDay + genesisMarksPerDay;
+```
+
+### Sail Token Marks
+
+- `marksPerDay` from the subgraph **already includes the 5x multiplier** -- do not multiply again
+- Expected: `balanceUSD * 5 = marksPerDay`
+- Same estimation function works for both ha and sail tokens
+
+### Example Values
+
+User holds $100,000 in sail tokens:
+- Marks per day: 500,000 (= $100,000 * 5)
+- After 2 days: 1,000,000 marks
+
+### Important Notes
+
+- Always use **lowercase addresses** in GraphQL queries
+- `balance` is a BigInt string (18 decimals), convert with `formatEther`
+- `balanceUSD` is already human-readable
+- `genesisId` format: `{genesisAddress}-{userAddress}` (both lowercase)
diff --git a/doc/frontend/redeem.md b/doc/frontend/redeem.md
new file mode 100644
index 00000000..27983bf6
--- /dev/null
+++ b/doc/frontend/redeem.md
@@ -0,0 +1,136 @@
+# Redeem Fee Calculation
+
+## Overview
+
+The Minter contract provides `dryRun` functions that simulate redemptions without executing them. Use these to display fees to users before they approve a transaction.
+
+## Dry-Run Functions
+
+### Redeem Pegged Token (haPB)
+
+```solidity
+function redeemPeggedTokenDryRun(uint256 peggedIn)
+ external view returns (
+ int256 incentiveRatio, // Fee (positive) or discount (negative), 1e18 scale
+ uint256 fee, // Fee amount in wrapped collateral
+ uint256 discount, // Discount/bonus amount in wrapped collateral
+ uint256 peggedRedeemed, // Amount of pegged tokens redeemed
+ uint256 wrappedCollateralReturned, // Net collateral returned
+ uint256 price, // Price used
+ uint256 rate // Conversion rate (underlying -> wrapped)
+ );
+```
+
+### Redeem Leveraged Token (hsPB)
+
+```solidity
+function redeemLeveragedTokenDryRun(uint256 leveragedIn)
+ external view returns (
+ int256 incentiveRatio,
+ uint256 fee,
+ uint256 leveragedRedeemed,
+ uint256 collateralReturned,
+ uint256 price,
+ uint256 rate
+ );
+```
+
+## Understanding the Incentive Ratio
+
+- **Positive**: Fee deducted from collateral. Example: `50000000000000000` (0.05e18) = **5% fee**
+- **Negative**: Discount/bonus added. Example: `-100000000000000000` (-0.1e18) = **10% bonus**
+- **1e18**: Transaction is **blocked** (100% fee)
+
+For pegged tokens, discounts are paid from the reserve pool. If the reserve pool is exhausted, the discount may be reduced.
+
+## Implementation
+
+### Minimal ABI
+
+```typescript
+const MINTER_ABI = [
+ "function redeemPeggedTokenDryRun(uint256) view returns (int256, uint256, uint256, uint256, uint256, uint256, uint256)",
+ "function redeemLeveragedTokenDryRun(uint256) view returns (int256, uint256, uint256, uint256, uint256, uint256)",
+];
+```
+
+### Calculate Fee Info
+
+```typescript
+async function calculateRedeemPeggedFee(minterAddress: string, peggedAmount: string, provider: ethers.Provider) {
+ const minter = new Contract(minterAddress, MINTER_ABI, provider);
+ const [incentiveRatio, fee, discount, peggedRedeemed, wrappedCollateralReturned, price, rate] =
+ await minter.redeemPeggedTokenDryRun(peggedAmount);
+
+ const incentiveRatioBN = BigInt(incentiveRatio.toString());
+ const isDisallowed = incentiveRatioBN === BigInt("1000000000000000000");
+
+ let feePercentage = 0;
+ let discountPercentage = 0;
+ if (incentiveRatioBN > 0n) feePercentage = Number(incentiveRatioBN) / 1e16;
+ else if (incentiveRatioBN < 0n) discountPercentage = Number(-incentiveRatioBN) / 1e16;
+
+ return {
+ fee: fee.toString(),
+ discount: discount.toString(),
+ collateralReturned: wrappedCollateralReturned.toString(),
+ feePercentage,
+ discountPercentage,
+ isDisallowed,
+ netCollateralReturned: ethers.formatEther(wrappedCollateralReturned),
+ };
+}
+```
+
+### wagmi/viem Hook
+
+```typescript
+function useRedeemPeggedFee(minterAddress: string, amount: string) {
+ const amountWei = amount ? parseEther(amount).toString() : "0";
+
+ const { data, isLoading, error } = useReadContract({
+ address: minterAddress as `0x${string}`,
+ abi: MINTER_ABI,
+ functionName: "redeemPeggedTokenDryRun",
+ args: [BigInt(amountWei)],
+ query: { enabled: !!amount && amount !== "0" },
+ });
+
+ // Process data same as above
+}
+```
+
+## Fee Structure Reference
+
+### Pegged Token (haPB) Fees
+
+| Collateral Ratio | Fee/Discount |
+|-----------------|--------------|
+| < 1.0x | -10% (Discount) |
+| 1.0x - 1.05x | -5% (Discount) |
+| 1.05x - 1.1x | 0% (Free) |
+| 1.1x - 1.2x | 1% |
+| 1.2x - 1.3x | 2% |
+| 1.3x - 1.5x | 3% |
+| 1.5x - 2.0x | 4% |
+| > 2.0x | 5% |
+
+### Leveraged Token (hsPB) Fees
+
+| Collateral Ratio | Fee |
+|-----------------|-----|
+| < 1.0x | 100% (Blocked) |
+| 1.0x - 1.05x | 30% |
+| 1.05x - 1.1x | 15% |
+| 1.1x - 1.2x | 8% |
+| 1.2x - 1.3x | 5% |
+| 1.3x - 1.5x | 3% |
+| 1.5x - 2.0x | 2% |
+| > 2.0x | 1.5% |
+
+## Notes
+
+- Fees are dynamic -- they change based on the current collateral ratio. Always call the dry-run right before showing transaction details.
+- Even if the dry-run succeeds, the actual transaction may fail if the collateral ratio changes between dry-run and execution.
+- The `price` and `rate` values can be used to display the current exchange rate and price impact.
+- Always call the dry-run again right before submitting the transaction for accuracy.
diff --git a/doc/frontend/stability-pool.md b/doc/frontend/stability-pool.md
new file mode 100644
index 00000000..87f5c581
--- /dev/null
+++ b/doc/frontend/stability-pool.md
@@ -0,0 +1,324 @@
+# Stability Pool Operations
+
+## Contract Functions Reference
+
+### Read Functions
+
+```solidity
+function assetBalanceOf(address account) external view returns (uint256);
+function totalAssetSupply() external view returns (uint256);
+function ASSET_TOKEN() external view returns (address);
+function getWithdrawalRequest(address account) external view returns (uint64 start, uint64 end);
+function getWithdrawalWindow() external view returns (uint64 startDelay, uint64 endWindow);
+function getEarlyWithdrawalFee() external view returns (uint256);
+function getFeeAddress() external view returns (address);
+function MIN_DEPOSIT() external view returns (uint256);
+function activeRewardTokens() external view returns (address[]);
+function claimable(address account, address token) external view returns (uint256);
+function rewardData(address token) external view returns (uint256 lastUpdate, uint256 finishAt, uint256 rate, uint256 queued);
+function REWARD_PERIOD_LENGTH() external view returns (uint40);
+```
+
+### Write Functions
+
+```solidity
+function deposit(uint256 assetAmount, address receiver, uint256 minAmount) external returns (uint256 sharesMinted);
+function withdraw(uint256 assetAmount, address receiver, uint256 minAmount) external returns (uint256);
+function requestWithdrawal() external;
+function claim() external;
+function claim(address account) external;
+function claim(address account, address receiver) external;
+```
+
+### Minimal ABI
+
+```typescript
+const STABILITY_POOL_ABI = [
+ "function assetBalanceOf(address) view returns (uint256)",
+ "function totalAssetSupply() view returns (uint256)",
+ "function ASSET_TOKEN() view returns (address)",
+ "function getWithdrawalRequest(address) view returns (uint64, uint64)",
+ "function getWithdrawalWindow() view returns (uint64, uint64)",
+ "function getEarlyWithdrawalFee() view returns (uint256)",
+ "function MIN_DEPOSIT() view returns (uint256)",
+ "function activeRewardTokens() view returns (address[])",
+ "function claimable(address, address) view returns (uint256)",
+ "function rewardData(address) view returns (uint256, uint256, uint256, uint256)",
+ "function REWARD_PERIOD_LENGTH() view returns (uint40)",
+ "function deposit(uint256, address, uint256) returns (uint256)",
+ "function withdraw(uint256, address, uint256) returns (uint256)",
+ "function requestWithdrawal()",
+ "function claim()",
+];
+```
+
+---
+
+## Deposits
+
+### Prerequisites Check
+
+Before depositing, verify:
+1. User has sufficient token balance
+2. Amount meets `MIN_DEPOSIT()` requirement
+3. Token allowance is sufficient (approve if needed)
+
+```typescript
+async function checkDepositPrerequisites(
+ poolAddress: string,
+ userAddress: string,
+ amount: bigint,
+ provider: any,
+) {
+ const pool = new Contract(poolAddress, STABILITY_POOL_ABI, provider);
+ const assetTokenAddress = await pool.ASSET_TOKEN();
+ const assetToken = new Contract(assetTokenAddress, ERC20_ABI, provider);
+
+ const minDeposit = await pool.MIN_DEPOSIT();
+ const userBalance = await assetToken.balanceOf(userAddress);
+ const allowance = await assetToken.allowance(userAddress, poolAddress);
+
+ const errors: string[] = [];
+ if (amount > userBalance) errors.push("Insufficient balance");
+ if (amount < minDeposit) errors.push(`Amount below minimum deposit: ${minDeposit}`);
+ if (allowance < amount) errors.push("Insufficient allowance. Please approve first.");
+
+ return { canDeposit: errors.length === 0, errors, minDeposit, userBalance, allowance };
+}
+```
+
+### Deposit All Balance
+
+Pass `type(uint256).max` to deposit the full balance:
+
+```typescript
+const maxUint256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
+await pool.deposit(maxUint256, receiver, BigInt(0));
+```
+
+### Important: Depositing cancels any active withdrawal request.
+
+### Error Messages
+
+```typescript
+const ERROR_MESSAGES: Record = {
+ DepositZeroAmount: "Cannot deposit zero amount",
+ DepositAmountLessThanMinimum: "Amount below minimum deposit",
+ InvalidReceiver: "Invalid receiver address",
+ "ERC20: insufficient allowance": "Please approve token first",
+ "ERC20: transfer amount exceeds balance": "Insufficient balance",
+};
+```
+
+---
+
+## Reading Deposits
+
+### Method 1: Contract Query (Real-time, Always Accurate)
+
+```typescript
+async function getStabilityPoolDeposit(poolAddress: string, userAddress: string, provider: any) {
+ const pool = new Contract(poolAddress, STABILITY_POOL_ABI, provider);
+ const balance = await pool.assetBalanceOf(userAddress);
+ const totalSupply = await pool.totalAssetSupply();
+ const [start, end] = await pool.getWithdrawalRequest(userAddress);
+
+ return {
+ balance,
+ balanceUSD: parseFloat(balance.toString()) / 1e18,
+ totalSupply,
+ withdrawalRequest: start > 0 ? { start, end } : null,
+ };
+}
+```
+
+### Method 2: Subgraph Query (Includes Marks and History)
+
+```graphql
+query GetStabilityPoolDeposits($userAddress: Bytes!) {
+ stabilityPoolDeposits(where: { user: $userAddress }) {
+ id
+ poolAddress
+ poolType # "collateral" or "sail"
+ balance # BigInt, 18 decimals
+ balanceUSD # BigDecimal
+ accumulatedMarks
+ marksPerDay
+ totalMarksEarned
+ firstDepositAt
+ lastUpdated
+ }
+}
+```
+
+### Recommended: Use both -- contract for real-time balance, subgraph for marks and historical data.
+
+### Filter by Pool Type
+
+```graphql
+# Collateral pool only
+stabilityPoolDeposits(where: { user: $userAddress, poolType: "collateral" })
+
+# Leveraged pool only
+stabilityPoolDeposits(where: { user: $userAddress, poolType: "sail" })
+```
+
+### Real-Time Marks Estimation (Zero Gas)
+
+```typescript
+function calculateEstimatedStabilityPoolMarks(deposit: StabilityPoolDeposit): number {
+ const storedMarks = parseFloat(deposit.accumulatedMarks || "0");
+ const marksPerDay = parseFloat(deposit.marksPerDay || "0");
+ const lastUpdated = parseInt(deposit.lastUpdated || "0");
+
+ if (lastUpdated === 0 || marksPerDay === 0) return storedMarks;
+
+ const now = Math.floor(Date.now() / 1000);
+ const daysSinceUpdate = (now - lastUpdated) / 86400;
+ return storedMarks + marksPerDay * daysSinceUpdate;
+}
+```
+
+**Always use lowercase addresses in GraphQL queries:** `userAddress.toLowerCase()`
+
+---
+
+## Withdrawal Requests
+
+### How the Withdrawal Window Works
+
+1. User calls `requestWithdrawal()` to create a request
+2. Wait `WITHDRAWAL_START_DELAY` seconds
+3. Fee-free window opens for `WITHDRAWAL_END_WINDOW` seconds
+4. After the window closes, the early withdrawal fee applies again
+
+### Fee Rules
+
+- **Before window starts**: Early withdrawal fee applies
+- **During window [start, end]**: No fee
+- **After window ends**: Early withdrawal fee applies again
+
+### Key Behaviors
+
+- **Depositing cancels the request**: If user deposits during an active window, the request is cancelled
+- **Withdrawal clears the request**: After withdrawing, the request window is cleared
+- **No request needed**: Users can withdraw at any time, but will pay the fee outside the window
+
+### Withdrawal Request Status
+
+```typescript
+async function getWithdrawalRequestStatus(poolAddress: string, userAddress: string, provider: any) {
+ const pool = new Contract(poolAddress, STABILITY_POOL_ABI, provider);
+ const [start, end] = await pool.getWithdrawalRequest(userAddress);
+ const now = BigInt(Math.floor(Date.now() / 1000));
+
+ const hasRequest = start > 0 && end > start;
+ let status: "none" | "waiting" | "active" | "expired" = "none";
+ let canWithdrawFeeFree = false;
+
+ if (hasRequest) {
+ if (now < start) status = "waiting";
+ else if (now >= start && now <= end) { status = "active"; canWithdrawFeeFree = true; }
+ else status = "expired";
+ }
+
+ return {
+ hasRequest, start: hasRequest ? start : null, end: hasRequest ? end : null,
+ status, canWithdrawFeeFree,
+ timeUntilStart: hasRequest && now < start ? Number(start - now) : null,
+ timeUntilEnd: hasRequest && now >= start && now <= end ? Number(end - now) : null,
+ };
+}
+```
+
+### Withdrawal Fee Calculation
+
+```typescript
+function calculateWithdrawalFee(amount: bigint, earlyWithdrawalFee: bigint, canWithdrawFeeFree: boolean) {
+ if (canWithdrawFeeFree) return { feeAmount: 0n, netAmount: amount, feePercentage: 0 };
+
+ const feeAmount = (amount * earlyWithdrawalFee) / BigInt("1000000000000000000");
+ return {
+ feeAmount,
+ netAmount: amount - feeAmount,
+ feePercentage: Number(earlyWithdrawalFee) / 1e18 * 100,
+ };
+}
+```
+
+### Time Formatting Utility
+
+```typescript
+function formatTimeRemaining(seconds: number): string {
+ if (seconds <= 0) return "Now";
+ const days = Math.floor(seconds / 86400);
+ const hours = Math.floor((seconds % 86400) / 3600);
+ const minutes = Math.floor((seconds % 3600) / 60);
+
+ const parts: string[] = [];
+ if (days > 0) parts.push(`${days}d`);
+ if (hours > 0) parts.push(`${hours}h`);
+ if (minutes > 0) parts.push(`${minutes}m`);
+ return parts.join(" ") || "Now";
+}
+```
+
+---
+
+## Rewards Display
+
+### Finding Registered Reward Tokens
+
+```typescript
+const rewardTokens = await stabilityPool.activeRewardTokens();
+```
+
+### Getting Claimable Rewards
+
+```typescript
+async function getAllClaimableRewards(stabilityPool: Contract, userAddress: string, tokenPriceMap: Map) {
+ const rewardTokens = await stabilityPool.activeRewardTokens();
+ const claimableRewards = [];
+
+ for (const token of rewardTokens) {
+ const claimable = await stabilityPool.claimable(userAddress, token);
+ if (claimable > 0n) {
+ const tokenContract = new Contract(token, ERC20_ABI, provider);
+ const symbol = await tokenContract.symbol();
+ const price = tokenPriceMap.get(token.toLowerCase()) || 0;
+ const amountFormatted = formatEther(claimable);
+
+ claimableRewards.push({
+ token, symbol, amount: claimable, amountFormatted,
+ usdValue: parseFloat(amountFormatted) * price,
+ });
+ }
+ }
+ return claimableRewards;
+}
+```
+
+### Reward Data
+
+```typescript
+interface RewardData {
+ lastUpdate: bigint;
+ finishAt: bigint;
+ rate: bigint; // rewards per second
+ queued: bigint; // queued rewards for next period
+}
+
+const [lastUpdate, finishAt, rate, queued] = await stabilityPool.rewardData(rewardTokenAddress);
+```
+
+### Reward Period
+
+Rewards vest over `REWARD_PERIOD_LENGTH` (typically 604800 seconds = 7 days). The `rate` represents rewards per second during the active period.
+
+- **Pending**: Rewards being distributed but not yet fully claimable
+- **Claimable**: Rewards available to claim now (returned by `claimable()`)
+- A pool can have multiple reward tokens simultaneously
+
+### Performance: Batch Queries
+
+Cache reward token list and symbols (change infrequently). Refresh claimable amounts every 30-60 seconds, APR every 5-10 minutes.
diff --git a/doc/frontend/tokens.md b/doc/frontend/tokens.md
new file mode 100644
index 00000000..68a1d745
--- /dev/null
+++ b/doc/frontend/tokens.md
@@ -0,0 +1,180 @@
+# Reward Tokens, Rates, and Sail Token
+
+## Querying Reward Tokens and Rates
+
+### Key Functions
+
+```solidity
+// Get all active reward token addresses
+function activeRewardTokens() external view returns (address[] memory);
+
+// Get reward configuration for a specific token
+function rewardData(address token) external view returns (
+ uint256 rate, // Reward rate in wei per second
+ uint256 period, // Vesting period in seconds
+ uint256 finishTime, // When current period ends
+ uint256 lastUpdateTime // Last update timestamp
+);
+```
+
+### Implementation
+
+```typescript
+const STABILITY_POOL_ABI = [
+ "function activeRewardTokens() view returns (address[])",
+ "function rewardData(address) view returns (uint256, uint256, uint256, uint256)",
+];
+
+async function getAllRewardTokensWithMetadata(poolAddress: string, provider: ethers.Provider) {
+ const pool = new Contract(poolAddress, STABILITY_POOL_ABI, provider);
+ const tokenAddresses = await pool.activeRewardTokens();
+ const currentBlock = await provider.getBlock("latest");
+ const currentTime = currentBlock?.timestamp || Math.floor(Date.now() / 1000);
+
+ return Promise.all(tokenAddresses.map(async (tokenAddress) => {
+ const [rate, period, finishTime, lastUpdateTime] = await pool.rewardData(tokenAddress);
+ const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider);
+ const [symbol, name, decimals] = await Promise.all([
+ tokenContract.symbol(), tokenContract.name(), tokenContract.decimals(),
+ ]);
+
+ const ratePerDay = Number(ethers.formatUnits(rate, decimals)) * 86400;
+ const ratePerYear = Number(ethers.formatUnits(rate, decimals)) * 31536000;
+
+ return {
+ address: tokenAddress, symbol, name, decimals: Number(decimals),
+ rate, ratePerDay, ratePerYear,
+ period: Number(period), periodDays: Number(period) / 86400,
+ finishTime: Number(finishTime), lastUpdateTime: Number(lastUpdateTime),
+ isActive: Number(finishTime) > currentTime,
+ };
+ }));
+}
+```
+
+### Calculating APR per Reward Token
+
+```typescript
+async function calculateAPR(
+ rewardToken: { ratePerYear: number },
+ totalAssetSupply: bigint,
+ rewardTokenPriceUSD: number,
+ assetTokenPriceUSD: number,
+): number {
+ const annualRewardUSD = rewardToken.ratePerYear * rewardTokenPriceUSD;
+ const totalDepositUSD = Number(ethers.formatEther(totalAssetSupply)) * assetTokenPriceUSD;
+ if (totalDepositUSD === 0) return 0;
+ return (annualRewardUSD / totalDepositUSD) * 100;
+}
+```
+
+### Notes
+
+- `rate` is in wei per second. Convert using the token's decimals.
+- Rewards vest linearly over `period` (typically 7 days = 604800 seconds).
+- A token is active if `finishTime > currentTime`. After `finishTime`, rate becomes 0 unless new rewards are deposited.
+- Pools can have multiple reward tokens simultaneously.
+- Rate changes when new rewards are deposited, a vesting period ends, or rewards are fully distributed.
+
+---
+
+## Sail Token (Leveraged Token)
+
+### Marks Earning
+
+Sail tokens (leveraged tokens, `hs` tokens) earn marks at **5x the rate** of ha tokens:
+
+| Token Type | Rate |
+|-----------|------|
+| Ha Tokens | 1 mark/dollar/day (1x) |
+| Sail Tokens | 5 marks/dollar/day (5x) |
+
+The `marksPerDay` field from the subgraph **already includes the 5x multiplier**. Do not multiply again.
+
+### GraphQL Query
+
+```graphql
+query GetSailTokenMarks($userAddress: Bytes!) {
+ sailTokenBalances(where: { user: $userAddress }) {
+ id
+ tokenAddress
+ balance
+ balanceUSD
+ accumulatedMarks
+ marksPerDay # Already includes 5x multiplier
+ lastUpdated
+ firstSeenAt
+ marketId
+ }
+}
+```
+
+### Real-Time Marks Estimation
+
+```typescript
+function calculateEstimatedSailMarks(balance: SailTokenBalance): number {
+ const storedMarks = parseFloat(balance.accumulatedMarks || "0");
+ const marksPerDay = parseFloat(balance.marksPerDay || "0"); // Already 5x
+ const lastUpdated = parseInt(balance.lastUpdated || "0");
+
+ if (lastUpdated === 0 || marksPerDay === 0) return storedMarks;
+
+ const now = Math.floor(Date.now() / 1000);
+ const daysSinceUpdate = (now - lastUpdated) / 86400;
+ return storedMarks + marksPerDay * daysSinceUpdate;
+}
+```
+
+Poll subgraph every 60 seconds for on-chain events. Update estimation every 1 second for smooth display.
+
+### Example
+
+User holds $100,000 in sail tokens:
+- `marksPerDay` = 500,000 (= $100,000 * 5)
+- After 1 day: 500,000 marks
+- After 2 days: 1,000,000 marks
+
+---
+
+## Sail Token TVL
+
+### Approach 1: Contract Query (Recommended for Production)
+
+```typescript
+async function getSailTokenTVL(tokenAddress: string, tokenPriceUSD: number, provider: any): Promise {
+ const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider);
+ const totalSupply = await tokenContract.totalSupply();
+ const totalSupplyTokens = parseFloat(totalSupply.toString()) / 1e18;
+ return totalSupplyTokens * tokenPriceUSD;
+}
+```
+
+### Approach 2: Subgraph Aggregation
+
+```graphql
+query GetSailTokenTVL($tokenAddress: Bytes!) {
+ sailTokenBalances(where: { tokenAddress: $tokenAddress, balance_gt: "0" }, first: 1000) {
+ balanceUSD
+ }
+}
+```
+
+Sum all `balanceUSD` values.
+
+### TVL Formatting
+
+```typescript
+function formatTVL(tvl: number): string {
+ if (tvl >= 1_000_000_000) return `$${(tvl / 1_000_000_000).toFixed(2)}B`;
+ if (tvl >= 1_000_000) return `$${(tvl / 1_000_000).toFixed(2)}M`;
+ if (tvl >= 1_000) return `$${(tvl / 1_000).toFixed(2)}K`;
+ return `$${tvl.toFixed(2)}`;
+}
+```
+
+### Notes
+
+- Sail tokens use 18 decimals (standard ERC20)
+- TVL changes when tokens are minted/burned; refresh every 30 seconds
+- If multiple sail tokens exist (different markets), sum their TVLs
+- Token price must be fetched separately (from price oracle or DEX)
diff --git a/doc/frontend/troubleshooting.md b/doc/frontend/troubleshooting.md
new file mode 100644
index 00000000..976c10ba
--- /dev/null
+++ b/doc/frontend/troubleshooting.md
@@ -0,0 +1,300 @@
+# Frontend Troubleshooting
+
+## Network and Connection Issues
+
+### Wrong RPC URL
+
+Ensure your frontend uses the correct Anvil endpoint:
+
+```typescript
+const RPC_URL = "http://localhost:8545";
+const CHAIN_ID = 31337;
+```
+
+### Wallet Not Connected to Anvil
+
+Add the network to MetaMask:
+
+```typescript
+await window.ethereum.request({
+ method: "wallet_addEthereumChain",
+ params: [{
+ chainId: "0x7A69", // 31337 in hex
+ chainName: "Anvil Local",
+ nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
+ rpcUrls: ["http://localhost:8545"],
+ blockExplorerUrls: [],
+ }],
+});
+```
+
+### Verify Connection
+
+```typescript
+const provider = new ethers.providers.JsonRpcProvider("http://localhost:8545");
+const network = await provider.getNetwork();
+console.log("Chain ID:", network.chainId); // Should be 31337
+```
+
+```bash
+curl http://localhost:8545 -X POST -H "Content-Type: application/json" \
+ -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
+# Should return: {"result":"0x7a69"}
+```
+
+---
+
+## `eth_sendRawTransaction` Does Not Exist
+
+### Cause
+
+The wallet is trying to use `eth_sendRawTransaction`, which Anvil may not support the same way as mainnet, or the frontend is configured incorrectly.
+
+### Fix: Use Wallet Provider, Not Raw Transactions
+
+```typescript
+// Use wallet's signing mechanism
+const provider = new ethers.providers.Web3Provider(window.ethereum);
+const signer = provider.getSigner();
+const contract = new ethers.Contract(address, ABI, signer);
+const tx = await contract.someFunction(); // Uses wallet signing
+```
+
+### Fix: wagmi Configuration
+
+```typescript
+const { chains, publicClient } = configureChains(
+ [{
+ id: 31337,
+ name: "Anvil Local",
+ network: "anvil",
+ nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
+ rpcUrls: { default: { http: ["http://localhost:8545"] } },
+ }],
+ [jsonRpcProvider({ rpc: () => ({ http: "http://localhost:8545" }) })],
+);
+```
+
+---
+
+## Dry-Run Returns Empty Data (`0x`)
+
+### Cause
+
+When `redeemPeggedTokenDryRun()` returns empty data, it means the contract has no code at that address, the function does not exist, or you are on the wrong chain.
+
+### Diagnostic Steps
+
+```typescript
+// 1. Check chain ID
+const chainId = await publicClient.getChainId();
+if (chainId !== 31337) console.error("Wrong chain! Expected 31337, got", chainId);
+
+// 2. Check contract has code
+const bytecode = await publicClient.getBytecode({ address: minterAddress });
+if (!bytecode || bytecode === "0x") console.error("No code at address");
+
+// 3. Test function call
+const result = await publicClient.readContract({
+ address: minterAddress,
+ abi: [{
+ name: "redeemPeggedTokenDryRun",
+ type: "function",
+ stateMutability: "view",
+ inputs: [{ name: "peggedIn", type: "uint256" }],
+ outputs: [
+ { name: "incentiveRatio", type: "int256" },
+ { name: "fee", type: "uint256" },
+ { name: "discount", type: "uint256" },
+ { name: "peggedRedeemed", type: "uint256" },
+ { name: "wrappedCollateralReturned", type: "uint256" },
+ { name: "price", type: "uint256" },
+ { name: "rate", type: "uint256" },
+ ],
+ }],
+ functionName: "redeemPeggedTokenDryRun",
+ args: [1n * 10n ** 18n],
+});
+```
+
+### Common Fixes
+
+1. **Wrong chain ID** (most common): Ensure chain ID is 31337 and RPC is `http://127.0.0.1:8545`
+2. **Missing minter address in market config**: Set to `0x8A791620dd6260079BF849Dc5567aDC3F2FdC318`
+3. **Incomplete ABI**: Must include all output types
+4. **Amount not in wei**: Use `parseEther("1")` not `"1"`
+
+---
+
+## Dry-Run Error: "Fee Unavailable"
+
+### Stale Price Feed (Most Common - 90% of Cases)
+
+Error: `StaleUnderlyingPrice` / `0xd2159c14`
+
+The price oracle checks that price feed data is fresh (`block.timestamp - updatedAt > maxAnswerAge`). Mock price feeds need manual updates.
+
+**Fix -- update price feeds:**
+
+```bash
+# Update a mock Chainlink feed
+cast send "setLatestAnswer(int256)" 200000000000 \
+ --rpc-url http://localhost:8545 \
+ --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
+```
+
+Or run the script: `forge script script/forge/UpdateAllPriceFeeds.s.sol --rpc-url http://127.0.0.1:8545 --broadcast`
+
+### Invalid Price (Zero or Negative)
+
+Error: `InvalidUnderlyingPrice`
+
+Check the price feed value:
+
+```typescript
+const [, answer] = await aggregator.latestRoundData();
+if (answer <= 0) throw new Error("Invalid price");
+```
+
+### Price Deviation Too Large
+
+Error: `UnderlyingPriceDeviation`
+
+Price changed too much between rounds. Update price feeds more gradually.
+
+### Oracle Not Configured
+
+Check: `const oracle = await minter.priceOracle();` -- should not be zero address.
+
+### Frontend Error Handling
+
+```typescript
+const collateralRatio = await publicClient.readContract({
+ address: minterAddress, abi: minterABI, functionName: "collateralRatio",
+}).catch((error) => {
+ if (error.message?.includes("0xd2159c14") || error.message?.includes("StaleUnderlyingPrice")) {
+ console.warn("Price feed is stale");
+ return null;
+ }
+ throw error;
+});
+
+// Display "-" when unavailable
+const displayRatio = collateralRatio ? formatRatio(collateralRatio) : "-";
+```
+
+On mainnet, Chainlink updates feeds automatically. This is only an issue with mock feeds in local development.
+
+---
+
+## Redeem Errors
+
+### Error `0x3dbf8ab9`: Zero Token Balance
+
+User has zero balance of the token being redeemed, or passed `type(uint256).max` with zero balance.
+
+```typescript
+const userBalance = await peggedToken.balanceOf(userAddress);
+if (userBalance === 0n) {
+ // Show: "You have no pegged tokens to redeem"
+ return;
+}
+```
+
+### Insufficient Token Allowance (90% of Redeem Failures)
+
+Always check and request approval before redeeming:
+
+```typescript
+const allowance = await peggedToken.allowance(userAddress, minterAddress);
+if (allowance < redeemAmount) {
+ await peggedToken.approve(minterAddress, redeemAmount);
+}
+```
+
+### Insufficient Redeemable Tokens in Minter
+
+Error: `InsufficientRedeemableTokens`
+
+```typescript
+const minterBalance = await minter.peggedTokenBalance();
+if (redeemAmount > minterBalance) {
+ // Show: "Only X tokens available for redemption"
+}
+```
+
+### Zero Collateral Returned
+
+Error: `ReturnZeroAmount`
+
+Fees exceed the redemption value or price oracle data is invalid. Always run a dry-run first:
+
+```typescript
+const dryRun = await minter.redeemPeggedTokenDryRun(redeemAmount);
+if (dryRun.wrappedCollateralReturned === 0n) {
+ // Show: "Redemption would return zero collateral"
+}
+```
+
+### Complete Pre-Redemption Check
+
+Before allowing a redeem:
+
+- User has pegged token balance > 0
+- User has approved Minter to spend pegged tokens
+- Minter has sufficient pegged token balance
+- Dry-run returns non-zero collateral
+- Amount is in wei (not human-readable)
+- User is on the correct chain (31337)
+
+### Error Decoding
+
+```typescript
+import { decodeErrorResult } from "viem";
+
+try {
+ await writeContract({...});
+} catch (error: any) {
+ if (error.data) {
+ const decoded = decodeErrorResult({ abi: minterAbi, data: error.data });
+ console.log("Decoded error:", decoded.errorName, decoded.args);
+ }
+}
+```
+
+---
+
+## Collateral Ratio Unavailable
+
+### Cause
+
+`collateralRatio()` reverts with `StaleUnderlyingPrice` when mock price feeds have stale timestamps.
+
+### Fix: Update All Price Feeds
+
+```bash
+# wstETH/USD
+cast send 0xeC827421505972a2AE9C320302d3573B42363C26 "setLatestAnswer(int256)" 200000000000 \
+ --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
+
+# stETH/USD
+cast send 0xb007167714e2940013ec3bb551584130b7497e22 "setLatestAnswer(int256)" 200000000000 \
+ --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
+
+# stETH/ETH
+cast send 0x6b39b761b1b64c8c095bf0e3bb0c6a74705b4788 "setLatestAnswer(int256)" 100000000 \
+ --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
+```
+
+### Verify
+
+```bash
+cast call "collateralRatio()(uint256)" --rpc-url http://localhost:8545
+# Expected: uint256 value (e.g., 2000000000000000000 for 2.0x)
+```
+
+### Prevention
+
+- Create a script that updates price feeds every few minutes
+- Show "-" or "N/A" when collateral ratio is unavailable
+- Log the error but do not break the UI
diff --git a/doc/guides/ANCHOR-LEDGER-MARKS-EXPLANATION.md b/doc/guides/ANCHOR-LEDGER-MARKS-EXPLANATION.md
deleted file mode 100644
index 250aa874..00000000
--- a/doc/guides/ANCHOR-LEDGER-MARKS-EXPLANATION.md
+++ /dev/null
@@ -1,132 +0,0 @@
-# Anchor Ledger Marks Explanation
-
-## What Are Anchor Ledger Marks?
-
-**Anchor Ledger Marks** represent marks earned from holding or depositing **ha tokens** (anchor tokens). They include:
-
-1. **Ha Token Holdings** (wallet balances)
- - Holding ha tokens in your wallet
- - Earns: 1 mark per dollar per day
- - Tracked via: `haTokenBalances` entity
-
-2. **Stability Pool Deposits** (pool deposits)
- - Depositing ha tokens in stability pools (collateral or sail pools)
- - Earns: 1 mark per dollar per day
- - Tracked via: `stabilityPoolDeposits` entity
-
-## Key Points
-
-- **Same Rate**: Both sources earn marks at the **same rate** (1 mark/dollar/day)
-- **Combined Total**: "Anchor Ledger Marks" = Ha Token Marks + Stability Pool Marks
-- **Separate Tracking**: Each source is tracked separately in the subgraph
-- **Configurable Multipliers**: Each stability pool can have its own multiplier (currently all set to 1.0x)
-
-## Current Multipliers
-
-All sources use the same multiplier (1.0x):
-- Ha tokens: 1.0x
-- Stability Pool Collateral: 1.0x
-- Stability Pool Sail: 1.0x
-
-## How to Query
-
-```graphql
-query GetAnchorLedgerMarks($userAddress: Bytes!) {
- haTokenBalances(where: { user: $userAddress }) {
- accumulatedMarks
- marksPerDay
- }
- stabilityPoolDeposits(where: { user: $userAddress }) {
- accumulatedMarks
- marksPerDay
- poolType
- }
-}
-```
-
-Then sum: `totalAnchorLedgerMarks = haTokenMarks + stabilityPoolMarks`
-
-## Example
-
-User has:
-- 200,000 ha tokens in wallet ($200,000 value) = 200,000 marks/day
-- 100,000 ha tokens in stability pool ($100,000 value) = 100,000 marks/day
-
-**Total Anchor Ledger Marks/Day**: 300,000 marks/day
-**Total Anchor Ledger Marks** (after 2 days): 600,000 marks
-
-## Notes
-
-- Stability pool deposits are tracked separately from ha token holdings
-- Both earn at the same rate (1 mark/dollar/day) by default
-- Multipliers can be configured per pool type in the future
-- The subgraph tracks both sources independently for flexibility
-
-
-
-## What Are Anchor Ledger Marks?
-
-**Anchor Ledger Marks** represent marks earned from holding or depositing **ha tokens** (anchor tokens). They include:
-
-1. **Ha Token Holdings** (wallet balances)
- - Holding ha tokens in your wallet
- - Earns: 1 mark per dollar per day
- - Tracked via: `haTokenBalances` entity
-
-2. **Stability Pool Deposits** (pool deposits)
- - Depositing ha tokens in stability pools (collateral or sail pools)
- - Earns: 1 mark per dollar per day
- - Tracked via: `stabilityPoolDeposits` entity
-
-## Key Points
-
-- **Same Rate**: Both sources earn marks at the **same rate** (1 mark/dollar/day)
-- **Combined Total**: "Anchor Ledger Marks" = Ha Token Marks + Stability Pool Marks
-- **Separate Tracking**: Each source is tracked separately in the subgraph
-- **Configurable Multipliers**: Each stability pool can have its own multiplier (currently all set to 1.0x)
-
-## Current Multipliers
-
-All sources use the same multiplier (1.0x):
-- Ha tokens: 1.0x
-- Stability Pool Collateral: 1.0x
-- Stability Pool Sail: 1.0x
-
-## How to Query
-
-```graphql
-query GetAnchorLedgerMarks($userAddress: Bytes!) {
- haTokenBalances(where: { user: $userAddress }) {
- accumulatedMarks
- marksPerDay
- }
- stabilityPoolDeposits(where: { user: $userAddress }) {
- accumulatedMarks
- marksPerDay
- poolType
- }
-}
-```
-
-Then sum: `totalAnchorLedgerMarks = haTokenMarks + stabilityPoolMarks`
-
-## Example
-
-User has:
-- 200,000 ha tokens in wallet ($200,000 value) = 200,000 marks/day
-- 100,000 ha tokens in stability pool ($100,000 value) = 100,000 marks/day
-
-**Total Anchor Ledger Marks/Day**: 300,000 marks/day
-**Total Anchor Ledger Marks** (after 2 days): 600,000 marks
-
-## Notes
-
-- Stability pool deposits are tracked separately from ha token holdings
-- Both earn at the same rate (1 mark/dollar/day) by default
-- Multipliers can be configured per pool type in the future
-- The subgraph tracks both sources independently for flexibility
-
-
-
-
-
diff --git a/doc/guides/CHAINLINK-MIN-MAX-REALITY.md b/doc/guides/CHAINLINK-MIN-MAX-REALITY.md
deleted file mode 100644
index d6c369bb..00000000
--- a/doc/guides/CHAINLINK-MIN-MAX-REALITY.md
+++ /dev/null
@@ -1,208 +0,0 @@
-# Chainlink Min/Max Prices in Production - The Reality
-
-## You're Absolutely Right! ✅
-
-In production with Chainlink price feeds, **min and max will be exactly the same** (or so close they're effectively identical).
-
-## Why?
-
-### 1. **Chainlink Provides a Single Price**
-Chainlink's `latestRoundData()` returns:
-- **One price** (`answer`)
-- **One timestamp** (`updatedAt`)
-- **One round ID**
-
-There's no built-in min/max spread from Chainlink itself.
-
-### 2. **Current Implementation**
-Looking at `StakedETHWrappedPriceOracle_v1.sol` line 73:
-```solidity
-minUnderlyingPrice = maxUnderlyingPrice = PriceOracle_v1.latestAnswer(feed, constraints);
-```
-
-Both are set to the **exact same Chainlink price**. There's no spread logic.
-
-### 3. **The Math**
-- **Min Price** = Chainlink price
-- **Max Price** = Chainlink price
-- **Mid Price** = (Min + Max) / 2 = **Same Chainlink price**
-
-So in practice:
-- `_fetchMin()` = Chainlink price
-- `_fetchMid()` = Chainlink price
-- `_fetchMax()` = Chainlink price
-
-**All three return the same value!**
-
-## Why Does the Design Support Min/Max?
-
-The min/max design is there for **future flexibility**, not current functionality:
-
-### Potential Future Uses:
-
-1. **Multiple Price Feeds**
- - Could query multiple Chainlink feeds (e.g., stETH/USD from different sources)
- - Take min across all feeds (most conservative)
- - Take max across all feeds (most optimistic)
-
-2. **Price Spreads/Buffers**
- - Could apply a small spread (e.g., ±0.1%) to account for:
- - Slippage
- - Market volatility
- - Safety margins
-
-3. **Bid/Ask Prices**
- - Could integrate with a DEX aggregator to get bid/ask spreads
- - Min = bid price (what you can sell for)
- - Max = ask price (what you can buy for)
-
-4. **Price Uncertainty**
- - Could use historical volatility to create a confidence interval
- - Min = price - uncertainty
- - Max = price + uncertainty
-
-## Current Reality
-
-**Right now:**
-- ✅ Single Chainlink feed
-- ✅ No spread logic
-- ✅ Min = Max = Chainlink price
-- ✅ All three fetch functions return the same value
-
-**So why use different functions?**
-- **Code clarity**: Makes intent clear (conservative vs generous)
-- **Future-proofing**: Easy to add spread logic later
-- **Consistent API**: Same interface whether min/max differ or not
-
-## Impact on Liquidation Rewards
-
-Since min = max in production:
-- **Liquidation using `_fetchMax()`** = Same price as normal operations
-- **The "favorable rate" benefit is minimal** (just the no-fees benefit remains)
-
-The real benefits of liquidation rewards come from:
-1. ✅ **No fees** (vs normal redemption which has fees)
-2. ✅ **System health improvement** (remaining deposit becomes more valuable)
-3. ⚠️ **Max price** (currently same as mid, but could be different in future)
-
-## Summary
-
-| Question | Answer |
-|----------|--------|
-| **Are min and max the same in production?** | ✅ Yes, exactly the same |
-| **Why does the code support min/max?** | Future flexibility |
-| **Does liquidation get a better price?** | Currently no (same price), but no fees |
-| **Could min/max differ in the future?** | Yes, if spread logic is added |
-
-## Bottom Line
-
-You're correct - with Chainlink feeds, min and max are **identical in practice**. The design supports min/max for future enhancements, but currently all three price types (`_fetchMin`, `_fetchMid`, `_fetchMax`) return the same Chainlink price.
-
-The liquidation reward advantage comes from **no fees** and **system health improvement**, not from a price difference (since there isn't one currently).
-
-
-
-## You're Absolutely Right! ✅
-
-In production with Chainlink price feeds, **min and max will be exactly the same** (or so close they're effectively identical).
-
-## Why?
-
-### 1. **Chainlink Provides a Single Price**
-Chainlink's `latestRoundData()` returns:
-- **One price** (`answer`)
-- **One timestamp** (`updatedAt`)
-- **One round ID**
-
-There's no built-in min/max spread from Chainlink itself.
-
-### 2. **Current Implementation**
-Looking at `StakedETHWrappedPriceOracle_v1.sol` line 73:
-```solidity
-minUnderlyingPrice = maxUnderlyingPrice = PriceOracle_v1.latestAnswer(feed, constraints);
-```
-
-Both are set to the **exact same Chainlink price**. There's no spread logic.
-
-### 3. **The Math**
-- **Min Price** = Chainlink price
-- **Max Price** = Chainlink price
-- **Mid Price** = (Min + Max) / 2 = **Same Chainlink price**
-
-So in practice:
-- `_fetchMin()` = Chainlink price
-- `_fetchMid()` = Chainlink price
-- `_fetchMax()` = Chainlink price
-
-**All three return the same value!**
-
-## Why Does the Design Support Min/Max?
-
-The min/max design is there for **future flexibility**, not current functionality:
-
-### Potential Future Uses:
-
-1. **Multiple Price Feeds**
- - Could query multiple Chainlink feeds (e.g., stETH/USD from different sources)
- - Take min across all feeds (most conservative)
- - Take max across all feeds (most optimistic)
-
-2. **Price Spreads/Buffers**
- - Could apply a small spread (e.g., ±0.1%) to account for:
- - Slippage
- - Market volatility
- - Safety margins
-
-3. **Bid/Ask Prices**
- - Could integrate with a DEX aggregator to get bid/ask spreads
- - Min = bid price (what you can sell for)
- - Max = ask price (what you can buy for)
-
-4. **Price Uncertainty**
- - Could use historical volatility to create a confidence interval
- - Min = price - uncertainty
- - Max = price + uncertainty
-
-## Current Reality
-
-**Right now:**
-- ✅ Single Chainlink feed
-- ✅ No spread logic
-- ✅ Min = Max = Chainlink price
-- ✅ All three fetch functions return the same value
-
-**So why use different functions?**
-- **Code clarity**: Makes intent clear (conservative vs generous)
-- **Future-proofing**: Easy to add spread logic later
-- **Consistent API**: Same interface whether min/max differ or not
-
-## Impact on Liquidation Rewards
-
-Since min = max in production:
-- **Liquidation using `_fetchMax()`** = Same price as normal operations
-- **The "favorable rate" benefit is minimal** (just the no-fees benefit remains)
-
-The real benefits of liquidation rewards come from:
-1. ✅ **No fees** (vs normal redemption which has fees)
-2. ✅ **System health improvement** (remaining deposit becomes more valuable)
-3. ⚠️ **Max price** (currently same as mid, but could be different in future)
-
-## Summary
-
-| Question | Answer |
-|----------|--------|
-| **Are min and max the same in production?** | ✅ Yes, exactly the same |
-| **Why does the code support min/max?** | Future flexibility |
-| **Does liquidation get a better price?** | Currently no (same price), but no fees |
-| **Could min/max differ in the future?** | Yes, if spread logic is added |
-
-## Bottom Line
-
-You're correct - with Chainlink feeds, min and max are **identical in practice**. The design supports min/max for future enhancements, but currently all three price types (`_fetchMin`, `_fetchMid`, `_fetchMax`) return the same Chainlink price.
-
-The liquidation reward advantage comes from **no fees** and **system health improvement**, not from a price difference (since there isn't one currently).
-
-
-
-
-
diff --git a/doc/guides/CHECK-HARVESTABLE.md b/doc/guides/CHECK-HARVESTABLE.md
deleted file mode 100644
index 0193b11d..00000000
--- a/doc/guides/CHECK-HARVESTABLE.md
+++ /dev/null
@@ -1,189 +0,0 @@
-# How to Check Harvestable Amount
-
-## Quick Answer
-
-To check how much would be harvested, call `harvestable()` on either:
-1. **Minter contract** - Returns the raw harvestable amount
-2. **StabilityPoolManager contract** - Also returns harvestable (calls Minter internally)
-
-## Method 1: Using cast (Command Line)
-
-```bash
-# If you have the Minter address
-cast call "harvestable()(uint256)" --rpc-url http://localhost:8545
-
-# Or using StabilityPoolManager
-cast call "harvestable()(uint256)" --rpc-url http://localhost:8545
-```
-
-**Example:**
-```bash
-# Get the amount in wei (18 decimals)
-cast call 0x8A791620dd6260079BF849Dc5567aDC3F2FdC318 "harvestable()(uint256)" --rpc-url http://localhost:8545
-
-# Convert to human-readable (divide by 1e18)
-cast call 0x8A791620dd6260079BF849Dc5567aDC3F2FdC318 "harvestable()(uint256)" --rpc-url http://localhost:8545 | cast --to-unit eth
-```
-
-## Method 2: Using TypeScript/JavaScript
-
-```typescript
-import { Contract } from "ethers";
-
-const MINTER_ABI = [
- "function harvestable() external view returns (uint256 wrappedAmount)",
-] as const;
-
-async function getHarvestableAmount(
- minterAddress: string,
- provider: any
-): Promise<{
- amount: bigint;
- amountFormatted: string;
-}> {
- const minter = new Contract(minterAddress, MINTER_ABI, provider);
- const harvestable = await minter.harvestable();
-
- return {
- amount: harvestable,
- amountFormatted: formatEther(harvestable), // Converts from wei to ether
- };
-}
-```
-
-## Method 3: Using React Hook (wagmi)
-
-```typescript
-import { useContractRead } from "wagmi";
-
-function useHarvestable(minterAddress: string) {
- const { data: harvestable, isLoading, error } = useContractRead({
- address: minterAddress as `0x${string}`,
- abi: [
- {
- name: "harvestable",
- type: "function",
- stateMutability: "view",
- inputs: [],
- outputs: [{ name: "wrappedAmount", type: "uint256" }],
- },
- ],
- functionName: "harvestable",
- });
-
- return {
- harvestable: harvestable || 0n,
- harvestableFormatted: harvestable ? formatEther(harvestable) : "0",
- isLoading,
- error,
- };
-}
-```
-
-## What Does `harvestable()` Return?
-
-The function returns the **amount of wrapped collateral tokens** (wstETH) that have accumulated as yield and can be harvested.
-
-### How It's Calculated
-
-```solidity
-function harvestable() external view returns (uint256 wrappedAmount) {
- // Gets current wstETH balance of Minter
- uint256 balance = IERC20(WRAPPED_COLLATERAL_TOKEN).balanceOf(address(this));
-
- // Gets the current rate (stETH per wstETH)
- uint256 rate = _fetchMid($.priceOracle).rate;
-
- // Calculates underlying collateral
- uint256 underlyingCollateral = (balance * 1e18) / rate;
-
- // Harvestable = current balance - (underlying collateral / rate)
- // This represents the yield that has accumulated
- wrappedAmount = balance - (underlyingCollateral / rate);
-}
-```
-
-**In simple terms:**
-- The Minter holds wstETH
-- Over time, the wstETH rate increases (staking rewards)
-- The "harvestable" amount is the difference between:
- - Current wstETH balance
- - The original underlying collateral converted back to wstETH at current rate
-
-## Example Output
-
-If you call `harvestable()` and get:
-```
-1000000000000000000000 // 1000 * 10^18 (1000 wstETH in wei)
-```
-
-This means **1000 wstETH** is currently harvestable.
-
-## What Happens When You Harvest?
-
-When `harvest()` is called on StabilityPoolManager:
-
-1. **Total Harvestable**: 1000 wstETH (example)
-2. **Bounty** (e.g., 5%): 50 wstETH → goes to harvester
-3. **Cut** (e.g., 10%): 100 wstETH → goes to fee receiver
-4. **Remainder** (85%): 850 wstETH → **automatically deposited to stability pools**
-
-The remainder is split between:
-- **Collateral Pool**: Based on proportion of total deposits
-- **Leveraged Pool**: Remaining amount
-
-## Check Current State
-
-To see the full breakdown of what would be harvested:
-
-```typescript
-async function getHarvestBreakdown(
- minterAddress: string,
- stabilityPoolManagerAddress: string,
- provider: any
-): Promise<{
- totalHarvestable: bigint;
- bountyRatio: bigint;
- cutRatio: bigint;
- bountyAmount: bigint;
- cutAmount: bigint;
- remainderForPools: bigint;
-}> {
- const minter = new Contract(minterAddress, MINTER_ABI, provider);
- const manager = new Contract(
- stabilityPoolManagerAddress,
- [
- "function harvestBountyRatio() view returns (uint256)",
- "function harvestCutRatio() view returns (uint256)",
- ],
- provider
- );
-
- const totalHarvestable = await minter.harvestable();
- const bountyRatio = await manager.harvestBountyRatio();
- const cutRatio = await manager.harvestCutRatio();
-
- const bountyAmount = (totalHarvestable * bountyRatio) / ethers.parseEther("1");
- const cutAmount = (totalHarvestable * cutRatio) / ethers.parseEther("1");
- const remainderForPools = totalHarvestable - bountyAmount - cutAmount;
-
- return {
- totalHarvestable,
- bountyRatio,
- cutRatio,
- bountyAmount,
- cutAmount,
- remainderForPools,
- };
-}
-```
-
-## Notes
-
-- **Harvestable grows over time** as staking rewards accumulate
-- The amount is in **wrapped collateral tokens** (wstETH), not underlying (stETH)
-- Returns **0** if no yield has accumulated yet
-- The amount represents **accrued yield**, not the total collateral held
-
-
-
diff --git a/doc/guides/CURRENT-STATUS.md b/doc/guides/CURRENT-STATUS.md
deleted file mode 100644
index 0ec02beb..00000000
--- a/doc/guides/CURRENT-STATUS.md
+++ /dev/null
@@ -1,142 +0,0 @@
-# Current System Status - After Cursor Restart
-
-## ✅ Services Running
-
-- **Anvil**: http://localhost:8545 (Current Block: 84)
-- **Graph Node**: http://localhost:8000
-- **Docker Services**: Running (PostgreSQL, IPFS, Graph Node)
-
-## 📋 Contract Addresses (Clean Chain Deployment)
-
-### Main Contracts
-- **Genesis**: `0x67d269191c92Caf3cD7723F116c85e6E9bf55933`
- - Owner: `0xAE7Dbb17bc40D53A6363409c6B1ED88d3cFdc31e` ✅
- - Status: Deployed and verified
-
-- **Minter**: `0x4A679253410272dd5232B3Ff7cF5dbB88f295319`
- - Status: Deployed and verified
-
-### Token Contracts
-- **Mock stETH**: `0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9`
-- **Mock wstETH**: `0x5FC8d32690cc91D4c39d9d3abcBD16989F875707`
-
-### Developer Account
-- **Address**: `0xAE7Dbb17bc40D53A6363409c6B1ED88d3cFdc31e`
-- **Token Balances**:
- - stETH: 1000 tokens ✅
- - wstETH: 1000 tokens ✅
-
-## 📊 Subgraph Status
-
-**3 subgraphs currently deployed:**
-1. Subgraph 1: Block 23829228 | Health: healthy
-2. Subgraph 2: Block 23829249 | Health: healthy
-3. Subgraph 3: Block 29 | Health: healthy
-
-**Note**: The subgraph at block 29 is likely the one for the clean chain, but it may need to be updated with the correct Genesis address (`0x67d269191c92Caf3cD7723F116c85e6E9bf55933`).
-
-## 🔧 Next Steps
-
-1. **Verify Subgraph Configuration**
- - Check if subgraph is pointing to the correct Genesis address
- - Update `startBlock` if needed (Genesis was deployed early in the chain)
-
-2. **Test Contract Functionality**
- - Make a test deposit to Genesis using mock wstETH
- - Verify events are being indexed
-
-3. **Monitor Indexing**
- - Check if new events are being processed
- - Verify GraphQL queries return expected data
-
-## 📝 Configuration Files
-
-- **Frontend Config**: `FRONTEND-CONFIG-CLEAN-CHAIN.txt`
-- **Token Config**: `lib/bao-base/script/bcinfo.local.json`
-- **Network**: anvil (Chain ID: 31337)
-- **RPC URL**: http://localhost:8545
-
-## 🔗 GraphQL Endpoint
-
-**GraphQL**: `http://localhost:8000/subgraphs/name/harbor-marks-local/graphql`
-
----
-
-**Last Updated**: After Cursor restart
-**Chain**: Clean Anvil (no fork)
-**Current Block**: 84
-
-
-
-
-## ✅ Services Running
-
-- **Anvil**: http://localhost:8545 (Current Block: 84)
-- **Graph Node**: http://localhost:8000
-- **Docker Services**: Running (PostgreSQL, IPFS, Graph Node)
-
-## 📋 Contract Addresses (Clean Chain Deployment)
-
-### Main Contracts
-- **Genesis**: `0x67d269191c92Caf3cD7723F116c85e6E9bf55933`
- - Owner: `0xAE7Dbb17bc40D53A6363409c6B1ED88d3cFdc31e` ✅
- - Status: Deployed and verified
-
-- **Minter**: `0x4A679253410272dd5232B3Ff7cF5dbB88f295319`
- - Status: Deployed and verified
-
-### Token Contracts
-- **Mock stETH**: `0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9`
-- **Mock wstETH**: `0x5FC8d32690cc91D4c39d9d3abcBD16989F875707`
-
-### Developer Account
-- **Address**: `0xAE7Dbb17bc40D53A6363409c6B1ED88d3cFdc31e`
-- **Token Balances**:
- - stETH: 1000 tokens ✅
- - wstETH: 1000 tokens ✅
-
-## 📊 Subgraph Status
-
-**3 subgraphs currently deployed:**
-1. Subgraph 1: Block 23829228 | Health: healthy
-2. Subgraph 2: Block 23829249 | Health: healthy
-3. Subgraph 3: Block 29 | Health: healthy
-
-**Note**: The subgraph at block 29 is likely the one for the clean chain, but it may need to be updated with the correct Genesis address (`0x67d269191c92Caf3cD7723F116c85e6E9bf55933`).
-
-## 🔧 Next Steps
-
-1. **Verify Subgraph Configuration**
- - Check if subgraph is pointing to the correct Genesis address
- - Update `startBlock` if needed (Genesis was deployed early in the chain)
-
-2. **Test Contract Functionality**
- - Make a test deposit to Genesis using mock wstETH
- - Verify events are being indexed
-
-3. **Monitor Indexing**
- - Check if new events are being processed
- - Verify GraphQL queries return expected data
-
-## 📝 Configuration Files
-
-- **Frontend Config**: `FRONTEND-CONFIG-CLEAN-CHAIN.txt`
-- **Token Config**: `lib/bao-base/script/bcinfo.local.json`
-- **Network**: anvil (Chain ID: 31337)
-- **RPC URL**: http://localhost:8545
-
-## 🔗 GraphQL Endpoint
-
-**GraphQL**: `http://localhost:8000/subgraphs/name/harbor-marks-local/graphql`
-
----
-
-**Last Updated**: After Cursor restart
-**Chain**: Clean Anvil (no fork)
-**Current Block**: 84
-
-
-
-
-
-
diff --git a/doc/guides/DAILY-POLL-SIMULATION.md b/doc/guides/DAILY-POLL-SIMULATION.md
deleted file mode 100644
index f3f66578..00000000
--- a/doc/guides/DAILY-POLL-SIMULATION.md
+++ /dev/null
@@ -1,132 +0,0 @@
-# Daily Poll Simulation - Summary
-
-## What We Did
-
-1. **Advanced Time**: Used `anvil_increaseTime 86400` to advance time by 1 day (86400 seconds)
-2. **Triggered Transfer**: Sent a 1 wei transfer from the user account to trigger the handler
-3. **Transfer Success**: Transfer was successful at block 161
-
-## Current Status
-
-- **Transfer Event**: ✅ Successfully created at block 161
- - From: `0xae7dbb17bc40d53a6363409c6b1ed88d3cfdc31e`
- - To: `0x1111111111111111111111111111111111111111`
- - Value: 1 wei
- - Block: 161
-
-- **Subgraph Indexing**: ⏳ Stuck at block 158
- - Needs to catch up to block 161 to process the transfer
- - Once indexed, marks should be calculated for 1 full day
-
-## Expected Result
-
-Once the subgraph indexes block 161, the handler should:
-
-1. **Process Transfer Event**: Detect the transfer from the user
-2. **Calculate Marks**:
- - Time since last update: ~1 day (86400+ seconds)
- - Full days: 1 day
- - Balance: 200,000 haPB tokens
- - Balance USD: $200,000 (assuming $1 per token)
- - Marks per day: 200,000 marks/day (1 mark per dollar per day)
- - **Accumulated marks: 200,000 marks** (for 1 full day)
-
-3. **Update Snapshot**:
- - `lastUpdated`: Updated to start of current day
- - `balance`: 200,000 tokens (minus 1 wei)
- - `balanceUSD`: ~$200,000
-
-## Next Steps
-
-1. Wait for subgraph to catch up to block 161
-2. Query ha token balances to verify marks accumulation
-3. If subgraph is stuck, may need to restart Graph Node or redeploy subgraph
-
-## Query to Check Results
-
-```graphql
-{
- haTokenBalances(where: {user: "0xae7dbb17bc40d53a6363409c6b1ed88d3cfdc31e"}) {
- id
- balance
- balanceUSD
- accumulatedMarks
- marksPerDay
- lastUpdated
- }
-}
-```
-
-Expected after indexing:
-- `accumulatedMarks`: Should be ~200,000 (for 1 full day)
-- `marksPerDay`: Should be ~200,000 (current rate)
-- `lastUpdated`: Should be updated to block 161 timestamp
-
-
-
-## What We Did
-
-1. **Advanced Time**: Used `anvil_increaseTime 86400` to advance time by 1 day (86400 seconds)
-2. **Triggered Transfer**: Sent a 1 wei transfer from the user account to trigger the handler
-3. **Transfer Success**: Transfer was successful at block 161
-
-## Current Status
-
-- **Transfer Event**: ✅ Successfully created at block 161
- - From: `0xae7dbb17bc40d53a6363409c6b1ed88d3cfdc31e`
- - To: `0x1111111111111111111111111111111111111111`
- - Value: 1 wei
- - Block: 161
-
-- **Subgraph Indexing**: ⏳ Stuck at block 158
- - Needs to catch up to block 161 to process the transfer
- - Once indexed, marks should be calculated for 1 full day
-
-## Expected Result
-
-Once the subgraph indexes block 161, the handler should:
-
-1. **Process Transfer Event**: Detect the transfer from the user
-2. **Calculate Marks**:
- - Time since last update: ~1 day (86400+ seconds)
- - Full days: 1 day
- - Balance: 200,000 haPB tokens
- - Balance USD: $200,000 (assuming $1 per token)
- - Marks per day: 200,000 marks/day (1 mark per dollar per day)
- - **Accumulated marks: 200,000 marks** (for 1 full day)
-
-3. **Update Snapshot**:
- - `lastUpdated`: Updated to start of current day
- - `balance`: 200,000 tokens (minus 1 wei)
- - `balanceUSD`: ~$200,000
-
-## Next Steps
-
-1. Wait for subgraph to catch up to block 161
-2. Query ha token balances to verify marks accumulation
-3. If subgraph is stuck, may need to restart Graph Node or redeploy subgraph
-
-## Query to Check Results
-
-```graphql
-{
- haTokenBalances(where: {user: "0xae7dbb17bc40d53a6363409c6b1ed88d3cfdc31e"}) {
- id
- balance
- balanceUSD
- accumulatedMarks
- marksPerDay
- lastUpdated
- }
-}
-```
-
-Expected after indexing:
-- `accumulatedMarks`: Should be ~200,000 (for 1 full day)
-- `marksPerDay`: Should be ~200,000 (current rate)
-- `lastUpdated`: Should be updated to block 161 timestamp
-
-
-
-
-
diff --git a/doc/guides/DAILY-SNAPSHOT-APPROACH.md b/doc/guides/DAILY-SNAPSHOT-APPROACH.md
deleted file mode 100644
index b19d0f6d..00000000
--- a/doc/guides/DAILY-SNAPSHOT-APPROACH.md
+++ /dev/null
@@ -1,196 +0,0 @@
-# Daily Snapshot Approach for Ha Token Marks
-
-## Overview
-
-We've simplified the ha token marks calculation to use a **daily snapshot approach**, which approximates polling balances once per day and awarding marks accordingly.
-
-## How It Works
-
-### 1. Event-Driven Balance Updates
-- The subgraph tracks `Transfer` events for ha tokens
-- On each transfer, we query the current balance from the contract
-- This gives us a "snapshot" of the balance at that moment
-
-### 2. Daily Marks Accumulation
-- Marks are calculated based on **full days** since the last snapshot
-- If someone holds tokens for 1.5 days, they get marks for 1 full day
-- The balance used for calculation is the balance from the last snapshot (the balance held for those days)
-
-### 3. Snapshot Timing
-- `lastUpdated` tracks when the last snapshot was taken
-- When marks are accumulated, `lastUpdated` is updated to the start of the current day
-- This ensures we only count full days going forward
-
-## Example
-
-**Day 1 (Block 100, 10:00 AM):**
-- User receives 200,000 haPB tokens
-- Balance snapshot: 200,000 tokens
-- `lastUpdated`: Block 100 timestamp
-- Marks accumulated: 0
-
-**Day 2 (Block 200, 2:00 PM - 1.2 days later):**
-- User still holds 200,000 haPB tokens
-- Transfer event occurs
-- Calculate: 1.2 days since last update → 1 full day
-- Marks for 1 day: 200,000 tokens × $1 × 1 mark/dollar/day × 1 day = 200,000 marks
-- `lastUpdated`: Updated to start of Day 2
-- Balance snapshot: 200,000 tokens (unchanged)
-
-**Day 3 (Block 300, 11:00 AM - 0.9 days later):**
-- User still holds 200,000 haPB tokens
-- Transfer event occurs
-- Calculate: 0.9 days since last update → 0 full days
-- Marks accumulated: 0 (less than 1 full day)
-- `lastUpdated`: Remains at start of Day 2
-- Balance snapshot: 200,000 tokens
-
-**Day 4 (Block 400, 3:00 PM - 1.1 days later):**
-- User still holds 200,000 haPB tokens
-- Transfer event occurs
-- Calculate: 1.1 days since last update → 1 full day
-- Marks for 1 day: 200,000 marks
-- Total accumulated: 400,000 marks
-- `lastUpdated`: Updated to start of Day 4
-
-## Benefits
-
-1. **Simpler Logic**: No complex time calculations, just count full days
-2. **Event-Driven**: Works with subgraph's event-driven architecture
-3. **Fair**: Users get marks for full days they held tokens
-4. **Efficient**: Only calculates when transfers occur (balance changes)
-
-## Implementation Details
-
-### `accumulateMarks()` Function
-- Calculates full days since `lastUpdated`
-- Awards marks based on `balanceUSD` from last snapshot
-- Updates `lastUpdated` to start of current day
-
-### `handleHaTokenTransfer()` Handler
-- Called on every Transfer event
-- Accumulates marks for full days
-- Updates balance snapshot from contract
-- Resets snapshot time if balance goes to zero
-
-## Querying Marks
-
-```graphql
-{
- haTokenBalances(where: {user: "0x..."}) {
- balance
- balanceUSD
- accumulatedMarks
- marksPerDay
- lastUpdated
- }
-}
-```
-
-## Notes
-
-- Marks accumulate in **full day increments only**
-- If no transfers occur for a long time, marks won't accumulate until the next transfer
-- This is intentional - it approximates "polling once per day"
-- For more frequent updates, users would need to trigger transfers (or we could add a periodic update mechanism)
-
-
-
-## Overview
-
-We've simplified the ha token marks calculation to use a **daily snapshot approach**, which approximates polling balances once per day and awarding marks accordingly.
-
-## How It Works
-
-### 1. Event-Driven Balance Updates
-- The subgraph tracks `Transfer` events for ha tokens
-- On each transfer, we query the current balance from the contract
-- This gives us a "snapshot" of the balance at that moment
-
-### 2. Daily Marks Accumulation
-- Marks are calculated based on **full days** since the last snapshot
-- If someone holds tokens for 1.5 days, they get marks for 1 full day
-- The balance used for calculation is the balance from the last snapshot (the balance held for those days)
-
-### 3. Snapshot Timing
-- `lastUpdated` tracks when the last snapshot was taken
-- When marks are accumulated, `lastUpdated` is updated to the start of the current day
-- This ensures we only count full days going forward
-
-## Example
-
-**Day 1 (Block 100, 10:00 AM):**
-- User receives 200,000 haPB tokens
-- Balance snapshot: 200,000 tokens
-- `lastUpdated`: Block 100 timestamp
-- Marks accumulated: 0
-
-**Day 2 (Block 200, 2:00 PM - 1.2 days later):**
-- User still holds 200,000 haPB tokens
-- Transfer event occurs
-- Calculate: 1.2 days since last update → 1 full day
-- Marks for 1 day: 200,000 tokens × $1 × 1 mark/dollar/day × 1 day = 200,000 marks
-- `lastUpdated`: Updated to start of Day 2
-- Balance snapshot: 200,000 tokens (unchanged)
-
-**Day 3 (Block 300, 11:00 AM - 0.9 days later):**
-- User still holds 200,000 haPB tokens
-- Transfer event occurs
-- Calculate: 0.9 days since last update → 0 full days
-- Marks accumulated: 0 (less than 1 full day)
-- `lastUpdated`: Remains at start of Day 2
-- Balance snapshot: 200,000 tokens
-
-**Day 4 (Block 400, 3:00 PM - 1.1 days later):**
-- User still holds 200,000 haPB tokens
-- Transfer event occurs
-- Calculate: 1.1 days since last update → 1 full day
-- Marks for 1 day: 200,000 marks
-- Total accumulated: 400,000 marks
-- `lastUpdated`: Updated to start of Day 4
-
-## Benefits
-
-1. **Simpler Logic**: No complex time calculations, just count full days
-2. **Event-Driven**: Works with subgraph's event-driven architecture
-3. **Fair**: Users get marks for full days they held tokens
-4. **Efficient**: Only calculates when transfers occur (balance changes)
-
-## Implementation Details
-
-### `accumulateMarks()` Function
-- Calculates full days since `lastUpdated`
-- Awards marks based on `balanceUSD` from last snapshot
-- Updates `lastUpdated` to start of current day
-
-### `handleHaTokenTransfer()` Handler
-- Called on every Transfer event
-- Accumulates marks for full days
-- Updates balance snapshot from contract
-- Resets snapshot time if balance goes to zero
-
-## Querying Marks
-
-```graphql
-{
- haTokenBalances(where: {user: "0x..."}) {
- balance
- balanceUSD
- accumulatedMarks
- marksPerDay
- lastUpdated
- }
-}
-```
-
-## Notes
-
-- Marks accumulate in **full day increments only**
-- If no transfers occur for a long time, marks won't accumulate until the next transfer
-- This is intentional - it approximates "polling once per day"
-- For more frequent updates, users would need to trigger transfers (or we could add a periodic update mechanism)
-
-
-
-
-
diff --git a/doc/guides/DEBUG-END-GENESIS.md b/doc/guides/DEBUG-END-GENESIS.md
deleted file mode 100644
index bb2ff016..00000000
--- a/doc/guides/DEBUG-END-GENESIS.md
+++ /dev/null
@@ -1,128 +0,0 @@
-# Debug: endGenesis() Unauthorized Error
-
-## Error
-`execution reverted: custom error 0x82b42900` = `Unauthorized()` from BaoOwnable
-
-## Current Status
-- ✅ Genesis owner: `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`
-- ✅ Genesis address: `0xAD523115cd35a8d4E60B3C0953E0E0ac10418309`
-- ✅ ZERO_FEE_ROLE granted to Genesis on Minter
-- ❌ `endGenesis()` fails with Unauthorized even from owner account
-
-## Possible Causes
-
-### 1. Wallet Account Mismatch
-**Most Likely**: Your wallet is connected with a different account than the owner.
-
-**Check**:
-- Wallet should show: `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`
-- If it shows a different address, that's the problem
-
-**Fix**: Import the owner account into your wallet:
-- Address: `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`
-- Private Key: `0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80`
-
-### 2. Frontend Calling Wrong Function
-The frontend might be calling a different function or with wrong parameters.
-
-**Verify**: Frontend should call `endGenesis()` with no parameters.
-
-### 3. Proxy Storage Issue
-Genesis is a UUPS proxy. There might be a storage layout issue.
-
-**Unlikely** since `owner()` returns the correct value and deposits work.
-
-## Verification Steps
-
-1. **Check wallet address in browser console:**
- ```javascript
- // In browser console
- (await window.ethereum.request({method: 'eth_accounts'}))[0]
- ```
- Should return: `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`
-
-2. **Check network:**
- - Chain ID: 31337
- - RPC: http://localhost:8545
-
-3. **Check Genesis contract:**
- - Address: `0xAD523115cd35a8d4E60B3C0953E0E0ac10418309`
- - Owner: `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`
-
-## Quick Test
-
-Try calling `endGenesis()` directly with cast:
-```bash
-cast send 0xAD523115cd35a8d4E60B3C0953E0E0ac10418309 "endGenesis()" \
- --rpc-url http://localhost:8545 \
- --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
-```
-
-If this also fails, there's a contract issue. If it succeeds, the problem is the wallet account.
-
-
-
-## Error
-`execution reverted: custom error 0x82b42900` = `Unauthorized()` from BaoOwnable
-
-## Current Status
-- ✅ Genesis owner: `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`
-- ✅ Genesis address: `0xAD523115cd35a8d4E60B3C0953E0E0ac10418309`
-- ✅ ZERO_FEE_ROLE granted to Genesis on Minter
-- ❌ `endGenesis()` fails with Unauthorized even from owner account
-
-## Possible Causes
-
-### 1. Wallet Account Mismatch
-**Most Likely**: Your wallet is connected with a different account than the owner.
-
-**Check**:
-- Wallet should show: `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`
-- If it shows a different address, that's the problem
-
-**Fix**: Import the owner account into your wallet:
-- Address: `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`
-- Private Key: `0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80`
-
-### 2. Frontend Calling Wrong Function
-The frontend might be calling a different function or with wrong parameters.
-
-**Verify**: Frontend should call `endGenesis()` with no parameters.
-
-### 3. Proxy Storage Issue
-Genesis is a UUPS proxy. There might be a storage layout issue.
-
-**Unlikely** since `owner()` returns the correct value and deposits work.
-
-## Verification Steps
-
-1. **Check wallet address in browser console:**
- ```javascript
- // In browser console
- (await window.ethereum.request({method: 'eth_accounts'}))[0]
- ```
- Should return: `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`
-
-2. **Check network:**
- - Chain ID: 31337
- - RPC: http://localhost:8545
-
-3. **Check Genesis contract:**
- - Address: `0xAD523115cd35a8d4E60B3C0953E0E0ac10418309`
- - Owner: `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`
-
-## Quick Test
-
-Try calling `endGenesis()` directly with cast:
-```bash
-cast send 0xAD523115cd35a8d4E60B3C0953E0E0ac10418309 "endGenesis()" \
- --rpc-url http://localhost:8545 \
- --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
-```
-
-If this also fails, there's a contract issue. If it succeeds, the problem is the wallet account.
-
-
-
-
-
diff --git a/doc/guides/DEPLOYMENT-SUMMARY-CLEAN-CHAIN.txt b/doc/guides/DEPLOYMENT-SUMMARY-CLEAN-CHAIN.txt
deleted file mode 100644
index 995b309a..00000000
--- a/doc/guides/DEPLOYMENT-SUMMARY-CLEAN-CHAIN.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-=== Harbor Clean Chain Deployment Summary ===
-
-Date: $(date)
-Chain: Anvil (clean, no fork)
-Chain ID: 31337
-RPC URL: http://localhost:8545
-
-=== Mock Contracts Deployed ===
-- stETH: 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
-- wstETH: 0x0165878A594ca255338adfa4d48449f69242Eb8F
-- stETH/USD Feed: 0xa513E6E4b8f2a923D98304ec87F64353C4D5C853
-- stETH/ETH Feed: 0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6
-- wstETH/USD Feed: 0x8A791620dd6260079BF849Dc5567aDC3F2FdC318
-
-=== Harbor Contracts Deployed ===
-- Genesis: 0x99dBE4AEa58E518C50a1c04aE9b48C9F6354612f
-- Minter: 0x34B40BA116d5Dec75548a9e9A8f15411461E8c70
-- StabilityPoolManager: 0xb9bEECD1A582768711dE1EE7B0A1d582D9d72a6C
-- StabilityPoolCollateral: 0x3aAde2dCD2Df6a8cAc689EE797591b2913658659
-- StabilityPoolLeveraged: 0x525C7063E7C20997BaaE9bDa922159152D0e8417
-
-=== Status ===
-✅ Anvil running (clean chain)
-✅ Mock contracts deployed
-✅ Harbor contracts deployed
-✅ Price feeds updated with fresh timestamps
-⏳ Genesis ownership needs to be set
-⏳ Tokens need to be minted to developer
-⏳ Verify ZERO_FEE_ROLE granted to Genesis
-
-=== Next Steps ===
-1. Set Genesis owner to developer:
- cast send 0x99dBE4AEa58E518C50a1c04aE9b48C9F6354612f "transferOwnership(address)" 0xAE7Dbb17bc40D53A6363409c6B1ED88d3cFdc31e --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
-
-2. Mint tokens to developer:
- cast send 0x0165878A594ca255338adfa4d48449f69242Eb8F "mint(address,uint256)" 0xAE7Dbb17bc40D53A6363409c6B1ED88d3cFdc31e 1000000000000000000000 --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
-
-3. Verify permissions and roles
-
diff --git a/doc/guides/DEPLOYMENT-SUMMARY.txt b/doc/guides/DEPLOYMENT-SUMMARY.txt
deleted file mode 100644
index 1a7b7865..00000000
--- a/doc/guides/DEPLOYMENT-SUMMARY.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-================================================================================
-DEPLOYMENT SUMMARY - Fresh Start After Problematic Block
-================================================================================
-
-✅ COMPLETED:
-------------
-1. Anvil restarted with fork from block 23829220 (after problematic block)
-2. All contracts redeployed
-3. Developer address verified as owner of Genesis contract
-4. Developer has ZERO_FEE_ROLE on Minter
-5. Subgraph deployed with correct start block
-6. Frontend configuration created
-
-⚠️ CURRENT STATUS:
-------------------
-- Graph Node block ingestor may still hit problematic blocks from mainnet history
-- Subgraph is healthy and waiting to index
-- Start block is set correctly (23829229)
-- Once Graph Node progresses, indexing will begin
-
-📋 FRONTEND CONFIGURATION:
---------------------------
-See: FRONTEND-CONFIG-NEW.txt
-
-All contract addresses are ready for frontend integration.
-
-🔍 MONITORING:
--------------
-Run: cd graph-node-local && ./check-subgraph-status.sh
-Or: cd graph-node-local && ./monitor-subgraph.sh
-
-📝 NEXT STEPS:
--------------
-1. Make test deposits/withdrawals to generate events
-2. Monitor subgraph to see when events are indexed
-3. Update frontend with addresses from FRONTEND-CONFIG-NEW.txt
-
-================================================================================
diff --git a/doc/guides/DEPOSIT-FEES-TO-POOLS-GUIDE.md b/doc/guides/DEPOSIT-FEES-TO-POOLS-GUIDE.md
deleted file mode 100644
index 50fa1f08..00000000
--- a/doc/guides/DEPOSIT-FEES-TO-POOLS-GUIDE.md
+++ /dev/null
@@ -1,206 +0,0 @@
-# Guide: Depositing Fees to Stability Pools
-
-## Current State
-
-### Fee Receiver Balance
-- **Address**: `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266` (Owner/Fee Receiver)
-- **ha Token Balance**: 1,250 tokens (1,250,000,000,000,000,000,000 wei)
-- **wstETH Balance**: 0 tokens
-- **Source**: Early withdrawal fees from stability pool withdrawals (paid in ha tokens, the pool's asset token)
-
-### Important Note
-**Fees are in ha tokens (pegged tokens), NOT wstETH**. To deposit them to pools as rewards, you have two options:
-1. **Redeem ha tokens for wstETH** (collateral), then deposit wstETH as rewards
-2. **Register ha tokens as a reward token** and deposit directly (if pools accept ha tokens as rewards)
-
-## Can You Deposit Fees to Pools?
-
-### ✅ Yes, It's Possible
-
-Fees can be deposited to stability pools using the `depositReward()` function. The fees are already in wstETH, so no conversion is needed.
-
-### Who Can Deposit?
-
-1. **Pool Owner** - Can deposit directly
-2. **Addresses with `REWARD_DEPOSITOR_ROLE`** - Can deposit rewards
-
-### Current Setup
-
-- **StabilityPoolManager** has `REWARD_DEPOSITOR_ROLE` on both pools
-- **Pool Owner** (`0xf39...`) can deposit directly
-- **Fee Receiver** (`0xf39...`) is the same as pool owner, so can deposit
-
-## How to Deposit Fees
-
-Since fees are in **ha tokens** (not wstETH), you have two options:
-
-### Option 1: Redeem ha Tokens for wstETH, Then Deposit
-
-#### Step 1: Redeem ha Tokens
-
-```solidity
-// Redeem ha tokens for wstETH
-IMinter(minter).redeemPeggedToken(
- haTokenAmount, // amount of ha tokens to redeem
- receiver, // address to receive wstETH
- minWrappedOut // minimum wstETH expected (slippage protection)
-);
-```
-
-**Note**: This will:
-- Take ha tokens from the caller
-- Return wstETH to the receiver
-- May incur a fee (depending on collateral ratio)
-
-#### Step 2: Deposit wstETH to Pools
-
-After redeeming, deposit the wstETH as rewards:
-
-```solidity
-// First, register wstETH as a reward token (if not already registered)
-IMultipleRewardDistributor(pool).registerRewardToken(wstETH);
-
-// Then deposit wstETH as rewards
-IMultipleRewardDistributor(collateralPool).depositReward(
- wstETH, // reward token
- amount // amount to deposit
-);
-
-IMultipleRewardDistributor(leveragedPool).depositReward(
- wstETH, // reward token
- amount // amount to deposit
-);
-```
-
-### Option 2: Register ha Tokens as Reward Token and Deposit Directly
-
-#### Step 1: Register ha Tokens as Reward Token
-
-```solidity
-// Register ha tokens as a reward token for each pool
-IMultipleRewardDistributor(collateralPool).registerRewardToken(haToken);
-IMultipleRewardDistributor(leveragedPool).registerRewardToken(haToken);
-```
-
-#### Step 2: Approve and Deposit
-
-```solidity
-// Approve pools to spend ha tokens
-IERC20(haToken).approve(collateralPool, amount);
-IERC20(haToken).approve(leveragedPool, amount);
-
-// Deposit ha tokens directly as rewards
-IMultipleRewardDistributor(collateralPool).depositReward(
- haToken, // reward token
- amount // amount to deposit
-);
-
-IMultipleRewardDistributor(leveragedPool).depositReward(
- haToken, // reward token
- amount // amount to deposit
-);
-```
-
-**Note**: Users would receive ha tokens as rewards, which they can then redeem or use as they wish.
-
-### Distribution Options (After Converting to wstETH or Registering ha Tokens)
-
-**Option 1: Split Equally**
-```solidity
-uint256 half = totalFees / 2;
-depositReward(collateralPool, rewardToken, half);
-depositReward(leveragedPool, rewardToken, half);
-```
-
-**Option 2: Proportional to Pool Sizes**
-```solidity
-uint256 totalPoolSupply = collateralPoolSupply + leveragedPoolSupply;
-uint256 toCollateral = (fees * collateralPoolSupply) / totalPoolSupply;
-uint256 toLeveraged = fees - toCollateral;
-depositReward(collateralPool, rewardToken, toCollateral);
-depositReward(leveragedPool, rewardToken, toLeveraged);
-```
-
-**Option 3: All to One Pool**
-```solidity
-depositReward(collateralPool, rewardToken, totalFees);
-// or
-depositReward(leveragedPool, rewardToken, totalFees);
-```
-
-**Note**: `rewardToken` is either `wstETH` (if you redeemed) or `haToken` (if you registered ha tokens as rewards).
-
-## What Happens After Deposit?
-
-1. **Tokens Transferred**: wstETH is transferred to the pool contract
-2. **Linear Vesting**: Rewards vest over 7 days (configurable `REWARD_PERIOD_LENGTH`)
-3. **Proportional Distribution**: Users earn rewards based on their deposit share
-4. **Claimable Over Time**: Rewards become claimable gradually
-
-## Harvest Status
-
-### Current Harvestable Amount: **0 wstETH**
-
-**Why?**
-- `harvestable()` returns the excess wstETH in the Minter beyond what's needed for collateral backing
-- Currently: Minter has 0 wstETH balance
-- Therefore: Nothing to harvest
-
-### What Would Be Returned If Harvest Was Possible?
-
-If there was a harvestable amount, here's what would happen:
-
-#### Example: 100 wstETH Harvestable
-
-**Split:**
-1. **Bounty** (~1-5%): Goes to harvester (whoever calls `harvest()`)
- - Example: 2 wstETH
-2. **Cut** (~1-5%): Goes to fee receiver
- - Example: 3 wstETH
-3. **Remainder** (~90-98%): **Automatically deposited to stability pools**
- - Example: 95 wstETH
- - Split proportionally between pools based on their sizes
-
-**Distribution:**
-- If Collateral Pool has 60% of total deposits → Gets 60% of remainder (57 wstETH)
-- If Leveraged Pool has 40% of total deposits → Gets 40% of remainder (38 wstETH)
-
-**Vesting:**
-- All rewards vest over 7 days (linear)
-- Users can claim gradually over time
-
-## Current Pool Sizes
-
-- **Collateral Pool**: 150,000 tokens
-- **Leveraged Pool**: 100,000 tokens
-- **Total**: 250,000 tokens
-
-**Distribution Ratio**: 60% collateral, 40% leveraged
-
-## Summary
-
-### Depositing Fees to Pools
-
-✅ **Possible**: Yes, but requires either redeeming ha tokens for wstETH OR registering ha tokens as reward tokens
-✅ **Who Can Do It**: Pool owner or addresses with `REWARD_DEPOSITOR_ROLE`
-✅ **Current Fees**: 1,250 ha tokens at owner address
-⚠️ **Redemption/Registration Needed**: Fees are in ha tokens, not wstETH
-
-### Harvest Status
-
-❌ **Harvestable**: 0 wstETH (nothing to harvest)
-✅ **If Harvestable**: Would automatically deposit ~90-98% to pools
-✅ **Vesting**: All rewards vest over 7 days
-
-### Next Steps (If You Want to Deposit Fees)
-
-1. **Choose approach**: Redeem for wstETH OR register ha tokens as rewards
-2. **If redeeming**: Call `redeemPeggedToken()` to convert ha → wstETH
-3. **Register reward token**: Register wstETH (or ha tokens) as reward token if not already
-4. **Approve**: Approve pools to spend tokens
-5. **Decide distribution** (equal, proportional, or all to one pool)
-6. **Call `depositReward()`** on the pool(s)
-7. **Rewards will vest** over 7 days for users
-
-**Note**: I haven't executed this yet, as you requested. Let me know if you want me to proceed!
-
diff --git a/doc/guides/DEV-ACCOUNT-INFO.txt b/doc/guides/DEV-ACCOUNT-INFO.txt
deleted file mode 100644
index bdc8ccdf..00000000
--- a/doc/guides/DEV-ACCOUNT-INFO.txt
+++ /dev/null
@@ -1,76 +0,0 @@
-===============================================================================
-DEV ACCOUNT INFORMATION
-===============================================================================
-
-Address: 0xAE7Dbb17bc40D53A6363409c6B1ED88d3cFdc31e
-
-BALANCES:
----------
-ETH: 100 ETH
-wstETH: 1000 tokens
-stETH: 1000 tokens
-
-PERMISSIONS:
-------------
-✅ Owner of Genesis contract
-✅ Has ZERO_FEE_ROLE on Minter contract
-
-READY FOR:
-----------
-✅ Gas payments (100 ETH)
-✅ Token transactions (wstETH, stETH)
-✅ Contract interactions (Genesis, Minter)
-
-===============================================================================
-QUICK REFERENCE:
-===============================================================================
-
-# Check balance
-cast balance 0xAE7Dbb17bc40D53A6363409c6B1ED88d3cFdc31e --rpc-url local
-
-# Add more ETH if needed
-cast rpc anvil_setBalance 0xAE7Dbb17bc40D53A6363409c6B1ED88d3cFdc31e \
- $(cast to-hex $(cast to-wei 100)) --rpc-url local
-
-===============================================================================
-
-
-DEV ACCOUNT INFORMATION
-===============================================================================
-
-Address: 0xAE7Dbb17bc40D53A6363409c6B1ED88d3cFdc31e
-
-BALANCES:
----------
-ETH: 100 ETH
-wstETH: 1000 tokens
-stETH: 1000 tokens
-
-PERMISSIONS:
-------------
-✅ Owner of Genesis contract
-✅ Has ZERO_FEE_ROLE on Minter contract
-
-READY FOR:
-----------
-✅ Gas payments (100 ETH)
-✅ Token transactions (wstETH, stETH)
-✅ Contract interactions (Genesis, Minter)
-
-===============================================================================
-QUICK REFERENCE:
-===============================================================================
-
-# Check balance
-cast balance 0xAE7Dbb17bc40D53A6363409c6B1ED88d3cFdc31e --rpc-url local
-
-# Add more ETH if needed
-cast rpc anvil_setBalance 0xAE7Dbb17bc40D53A6363409c6B1ED88d3cFdc31e \
- $(cast to-hex $(cast to-wei 100)) --rpc-url local
-
-===============================================================================
-
-
-
-
-
diff --git a/doc/guides/DEV-ADDRESS-ANCHOR-MARKS-REPORT.md b/doc/guides/DEV-ADDRESS-ANCHOR-MARKS-REPORT.md
deleted file mode 100644
index f1aea1dd..00000000
--- a/doc/guides/DEV-ADDRESS-ANCHOR-MARKS-REPORT.md
+++ /dev/null
@@ -1,100 +0,0 @@
-# Dev Address Anchor Token Marks Report
-
-## Summary
-
-**Address**: `0xae7dbb17bc40d53a6363409c6b1ed88d3cfdc31e`
-**Token**: haPB (Anchor Token)
-**Total Accumulated Marks**: **1,200,000 marks**
-
----
-
-## Current Status
-
-- **Current Balance**: 593,257.73 ha tokens
-- **Current Balance USD**: $593,257.73
-- **Current Marks Per Day**: 200,000 marks/day
-- **Accumulated Marks**: 1,200,000 marks
-- **Total Marks Earned**: 1,200,000 marks
-
----
-
-## How Marks Were Earned
-
-### Daily Snapshot Approach
-
-The subgraph uses a **daily snapshot approach** to calculate marks:
-
-1. **Event-Driven Updates**: Marks accumulate when `Transfer` events occur
-2. **Full Days Only**: Marks are awarded for **full days** since the last update
-3. **Balance Snapshot**: The balance held during those days is used for calculation
-
-### Calculation Formula
-
-```
-Marks = Balance USD × Multiplier × Full Days Held
-```
-
-Where:
-- **Multiplier**: 1.0x (1 mark per dollar per day)
-- **Full Days**: Only complete 24-hour periods count
-
-### Timeline
-
-- **First Seen**: November 27, 2025 18:34:34
-- **Last Updated**: December 3, 2025 18:34:34
-- **Time Held**: 6.00 days (518,400 seconds)
-
-### Marks Breakdown
-
-The **1,200,000 marks** were earned as follows:
-
-1. **Initial Balance**: The dev address received ha tokens (likely ~200,000 tokens based on marksPerDay)
-2. **Daily Accumulation**: Marks accumulated at **200,000 marks/day** for **6 full days**
-3. **Calculation**: 200,000 marks/day × 6 days = **1,200,000 marks**
-
-### Why Current Balance is Different
-
-The current balance (593,257.73 tokens) is **higher** than what was used for the marks calculation (which was based on ~200,000 tokens). This means:
-
-- The dev address earned marks while holding ~200,000 tokens
-- After earning those marks, the balance increased to 593,257.73 tokens
-- The new balance will earn marks going forward at the new rate (593,257 marks/day)
-
----
-
-## Key Points
-
-1. **Marks Only Accumulate on Transfers**: Marks are calculated when Transfer events occur, not continuously
-2. **Full Days Only**: Partial days don't count - you need to hold for a full 24 hours
-3. **Balance Changes**: If balance changes, marks are calculated based on the balance held during each period
-4. **Current Rate**: The current marksPerDay (200,000) suggests the balance was around 200k when last updated
-
----
-
-## Query to Get This Data
-
-```graphql
-{
- haTokenBalances(where: {user: "0xae7dbb17bc40d53a6363409c6b1ed88d3cfdc31e"}) {
- balance
- balanceUSD
- accumulatedMarks
- totalMarksEarned
- marksPerDay
- firstSeenAt
- lastUpdated
- }
-}
-```
-
----
-
-## Next Steps
-
-To see updated marks:
-1. Wait for the next Transfer event (any ha token transfer)
-2. The subgraph will recalculate marks for full days since `lastUpdated`
-3. The new balance (593,257.73 tokens) will be used for future calculations
-
-
-
diff --git a/doc/guides/DEV-WALLET-MARKS-REPORT.md b/doc/guides/DEV-WALLET-MARKS-REPORT.md
deleted file mode 100644
index e3cc8f36..00000000
--- a/doc/guides/DEV-WALLET-MARKS-REPORT.md
+++ /dev/null
@@ -1,119 +0,0 @@
-# Dev Wallet Marks Report
-
-**Wallet Address**: `0xAE7Dbb17bc40D53A6363409c6B1ED88d3cFdc31e`
-**Report Time**: Block 272, Timestamp 1764895365 (Fri, Dec 5, 2025 00:42:45 GMT)
-
-## Summary by Position
-
-### 1. Genesis Marks
-- **Source**: Genesis Deposit
-- **Current Marks**: 40,110,277.78 marks
-- **Total Marks Earned**: 40,110,277.78 marks
-- **Bonus Marks**: 40,000,000 marks
-- **Marks Per Day**: 0 (Genesis ended)
-- **Status**: ✅ Genesis ended, marks finalized
-
-### 2. Ha Token (Anchor Token) Marks
-- **Token Address**: `0x1c85638e118b37167e9298c2268758e058DdfDA0`
-- **Balance**: 442,007.73 tokens
-- **Balance USD**: $442,007.73
-- **Accumulated Marks**: 1,597,283.96 marks
-- **Marks Per Day**: 393,257.73 marks/day
-- **Total Marks Earned**: 1,597,283.96 marks
-- **Last Updated**: 1764895365
-
-### 3. Stability Pool Deposit Marks
-- **Pool Address**: `0x3aAde2dCD2Df6a8cAc689EE797591b2913658659` (Collateral Pool)
-- **Pool Type**: Collateral
-- **Balance**: 150,000 tokens
-- **Balance USD**: $150,000
-- **Accumulated Marks**: 21,587.96 marks
-- **Marks Per Day**: 150,000 marks/day
-- **Total Marks Earned**: 21,587.96 marks
-- **Last Updated**: 1764895365
-
-### 4. Sail Token (Leveraged Token) Marks
-- **Token Address**: `0x367761085BF3C12e5DA2Df99AC6E1a824612b8fb`
-- **Balance**: 401,651.48 tokens
-- **Balance USD**: $401,651.48
-- **Accumulated Marks**: 7,105,061.00 marks
-- **Marks Per Day**: 2,008,257.39 marks/day (5x multiplier)
-- **Total Marks Earned**: 7,105,061.00 marks
-- **Last Updated**: 1764882233
-
-## Total Marks Breakdown
-
-| Source | Accumulated Marks | Marks Per Day |
-|--------|-----------------|---------------|
-| **Genesis** | 40,110,277.78 | 0 (ended) |
-| **Ha Tokens** | 1,597,283.96 | 393,257.73 |
-| **Stability Pool** | 21,587.96 | 150,000.00 |
-| **Sail Tokens** | 7,105,061.00 | 2,008,257.39 |
-| **TOTAL** | **48,834,210.70** | **2,551,515.12** |
-
-## Detailed Breakdown
-
-### Genesis Marks
-- **Base Marks**: 1,102,277.78 marks (from $400,000 deposit)
-- **Bonus Marks**: 40,000,000 marks (100x bonus at Genesis end)
-- **Total**: 40,110,277.78 marks
-- **Status**: Finalized (Genesis ended)
-
-### Ha Token Position
-- **Holding**: 442,007.73 ha tokens
-- **Value**: $442,007.73 USD
-- **Marks Rate**: 1 mark per dollar per day
-- **Current Rate**: 393,257.73 marks/day
-- **Time Held**: ~4.06 days (based on accumulated marks)
-
-### Stability Pool Position
-- **Deposit**: 150,000 tokens in Collateral Pool
-- **Value**: $150,000 USD
-- **Marks Rate**: 1 mark per dollar per day
-- **Current Rate**: 150,000 marks/day
-- **Time Deposited**: ~0.14 days (based on accumulated marks)
-
-### Sail Token Position
-- **Holding**: 401,651.48 hs tokens
-- **Value**: $401,651.48 USD
-- **Marks Rate**: 5 marks per dollar per day (5x multiplier)
-- **Current Rate**: 2,008,257.39 marks/day
-- **Time Held**: ~3.54 days (based on accumulated marks)
-
-## Current Marks Per Day Rate
-
-**Total Active Marks Per Day**: 2,551,515.12 marks/day
-
-Breakdown:
-- Ha Tokens: 393,257.73 marks/day (15.4%)
-- Stability Pool: 150,000.00 marks/day (5.9%)
-- Sail Tokens: 2,008,257.39 marks/day (78.7%)
-
-## Notes
-
-1. **Genesis marks are finalized** - No longer earning marks from Genesis
-2. **Ha tokens** - Largest active position by value, earning at 1x rate
-3. **Stability pool** - Recently deposited (150k tokens), earning at 1x rate
-4. **Sail tokens** - Highest marks per day due to 5x multiplier, despite lower USD value
-
-## Estimated Marks Growth
-
-At current rates, the dev wallet is earning approximately:
-- **Per Hour**: ~106,313 marks/hour
-- **Per Day**: 2,551,515 marks/day
-- **Per Week**: 17,860,606 marks/week
-
-## Real-Time Estimation
-
-Since last update, additional marks may have accumulated:
-- **Ha Tokens**: Last updated at 1764895365 (current time)
-- **Stability Pool**: Last updated at 1764895365 (current time)
-- **Sail Tokens**: Last updated at 1764882233 (~13,132 seconds ago = ~0.15 days)
-
-**Estimated additional marks since last update**:
-- Sail Tokens: ~301,239 marks (0.15 days × 2,008,257.39 marks/day)
-
-**Total Estimated Marks (with real-time calculation)**: ~49,135,450 marks
-
-
-
diff --git a/doc/guides/DIAGNOSE-DEPLOYMENT.md b/doc/guides/DIAGNOSE-DEPLOYMENT.md
deleted file mode 100644
index b3a4f5e4..00000000
--- a/doc/guides/DIAGNOSE-DEPLOYMENT.md
+++ /dev/null
@@ -1,232 +0,0 @@
-# Diagnosing Stuck Subgraph Deployment
-
-## Quick Checks
-
-### 1. Check Docker Services Status
-```bash
-cd graph-node-local
-docker compose ps
-```
-
-**What to look for:**
-- All services should be "Up" and "healthy"
-- Graph Node should be running
-- PostgreSQL should be healthy
-- IPFS should be running
-
-### 2. Check Graph Node Logs
-```bash
-cd graph-node-local
-docker compose logs graph-node --tail 50
-```
-
-**What to look for:**
-- Any ERROR messages
-- "Block data unavailable" or "uncled" errors
-- "Downloading latest blocks" messages
-- Any deployment-related messages
-
-### 3. Check if Services are Responding
-```bash
-# Graph Node JSON-RPC
-curl http://localhost:8020
-
-# IPFS
-curl http://localhost:5001
-
-# GraphQL
-curl http://localhost:8000
-```
-
-### 4. Check Anvil Connection
-```bash
-curl -X POST http://localhost:8545 \
- -H "Content-Type: application/json" \
- -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
-```
-
-## Common Issues
-
-### Issue 1: Block Ingestor Stuck
-**Symptom**: Logs show repeated "Block data unavailable" errors
-
-**Solution**: The block ingestor is trying to fetch a block that doesn't exist. This happens when Graph Node has cached block data from a previous deployment.
-
-**Fix**:
-```bash
-cd graph-node-local
-docker compose down -v # Remove volumes
-docker compose up -d # Start fresh
-```
-
-### Issue 2: IPFS Not Responding
-**Symptom**: Deployment hangs, IPFS connection errors
-
-**Solution**: Restart IPFS
-```bash
-cd graph-node-local
-docker compose restart ipfs
-```
-
-### Issue 3: Graph Node Not Ready
-**Symptom**: Graph Node is starting but not ready
-
-**Solution**: Wait a bit longer (30-60 seconds) for Graph Node to fully initialize
-
-### Issue 4: Deployment Hangs on "Uploading to IPFS"
-**Symptom**: Deployment stops at IPFS upload step
-
-**Solution**:
-- Check IPFS logs: `docker compose logs ipfs`
-- Try deploying with verbose output: `graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 harbor-marks-local -v`
-
-## Alternative: Manual Deployment Steps
-
-If automatic deployment is stuck, try these steps manually:
-
-1. **Build the subgraph**:
- ```bash
- cd /Users/andrewyoung/Harbor-App/harbor-app/subgraph
- graph build
- ```
-
-2. **Create subgraph** (if needed):
- ```bash
- graph create --node http://localhost:8020/ harbor-marks-local
- ```
-
-3. **Deploy with verbose output**:
- ```bash
- graph deploy --node http://localhost:8020/ \
- --ipfs http://localhost:5001 \
- harbor-marks-local \
- -v
- ```
-
-## What to Share
-
-If deployment is still stuck, please share:
-
-1. **Docker Compose Status**: `docker compose ps` output
-2. **Graph Node Logs**: Last 50 lines from `docker compose logs graph-node --tail 50`
-3. **IPFS Logs**: `docker compose logs ipfs --tail 20`
-4. **Where it's stuck**: What's the last message you see in the deployment output?
-
-
-
-## Quick Checks
-
-### 1. Check Docker Services Status
-```bash
-cd graph-node-local
-docker compose ps
-```
-
-**What to look for:**
-- All services should be "Up" and "healthy"
-- Graph Node should be running
-- PostgreSQL should be healthy
-- IPFS should be running
-
-### 2. Check Graph Node Logs
-```bash
-cd graph-node-local
-docker compose logs graph-node --tail 50
-```
-
-**What to look for:**
-- Any ERROR messages
-- "Block data unavailable" or "uncled" errors
-- "Downloading latest blocks" messages
-- Any deployment-related messages
-
-### 3. Check if Services are Responding
-```bash
-# Graph Node JSON-RPC
-curl http://localhost:8020
-
-# IPFS
-curl http://localhost:5001
-
-# GraphQL
-curl http://localhost:8000
-```
-
-### 4. Check Anvil Connection
-```bash
-curl -X POST http://localhost:8545 \
- -H "Content-Type: application/json" \
- -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
-```
-
-## Common Issues
-
-### Issue 1: Block Ingestor Stuck
-**Symptom**: Logs show repeated "Block data unavailable" errors
-
-**Solution**: The block ingestor is trying to fetch a block that doesn't exist. This happens when Graph Node has cached block data from a previous deployment.
-
-**Fix**:
-```bash
-cd graph-node-local
-docker compose down -v # Remove volumes
-docker compose up -d # Start fresh
-```
-
-### Issue 2: IPFS Not Responding
-**Symptom**: Deployment hangs, IPFS connection errors
-
-**Solution**: Restart IPFS
-```bash
-cd graph-node-local
-docker compose restart ipfs
-```
-
-### Issue 3: Graph Node Not Ready
-**Symptom**: Graph Node is starting but not ready
-
-**Solution**: Wait a bit longer (30-60 seconds) for Graph Node to fully initialize
-
-### Issue 4: Deployment Hangs on "Uploading to IPFS"
-**Symptom**: Deployment stops at IPFS upload step
-
-**Solution**:
-- Check IPFS logs: `docker compose logs ipfs`
-- Try deploying with verbose output: `graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 harbor-marks-local -v`
-
-## Alternative: Manual Deployment Steps
-
-If automatic deployment is stuck, try these steps manually:
-
-1. **Build the subgraph**:
- ```bash
- cd /Users/andrewyoung/Harbor-App/harbor-app/subgraph
- graph build
- ```
-
-2. **Create subgraph** (if needed):
- ```bash
- graph create --node http://localhost:8020/ harbor-marks-local
- ```
-
-3. **Deploy with verbose output**:
- ```bash
- graph deploy --node http://localhost:8020/ \
- --ipfs http://localhost:5001 \
- harbor-marks-local \
- -v
- ```
-
-## What to Share
-
-If deployment is still stuck, please share:
-
-1. **Docker Compose Status**: `docker compose ps` output
-2. **Graph Node Logs**: Last 50 lines from `docker compose logs graph-node --tail 50`
-3. **IPFS Logs**: `docker compose logs ipfs --tail 20`
-4. **Where it's stuck**: What's the last message you see in the deployment output?
-
-
-
-
-
diff --git a/doc/guides/END-GENESIS-FIX.txt b/doc/guides/END-GENESIS-FIX.txt
deleted file mode 100644
index 7a10472d..00000000
--- a/doc/guides/END-GENESIS-FIX.txt
+++ /dev/null
@@ -1,84 +0,0 @@
-=== End Genesis Fix Applied ===
-
-ISSUE
------
-endGenesis() was failing with Unauthorized() error (0x82b42900)
-
-ROOT CAUSE
-----------
-1. Genesis contract did NOT have ZERO_FEE_ROLE on Minter
- - endGenesis() calls freeMintPeggedToken/freeMintLeveragedToken
- - These functions require ZERO_FEE_ROLE
-
-2. The deploy script grants this role, but it may have failed silently
-
-FIX APPLIED
------------
-✅ Granted ZERO_FEE_ROLE to Genesis on Minter using:
- - Function: grantRoles(address,uint256)
- - Role value: 1 (ZERO_FEE_ROLE)
- - Genesis: 0x572316aC11CB4bc5daf6BDae68f43EA3CCE3aE0e
- - Minter: 0xe38b6847E611e942E6c80eD89aE867F522402e80
-
-VERIFICATION
-------------
-✅ Genesis has ZERO_FEE_ROLE (verified with hasAnyRole)
-✅ Owner account: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
-
-FRONTEND REQUIREMENTS
-----------------------
-For endGenesis() to work from frontend:
-1. Wallet must be connected with owner account: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
-2. Use correct Genesis address: 0x572316aC11CB4bc5daf6BDae68f43EA3CCE3aE0e
-3. Ensure price feeds are up to date (may need to update mock price feeds)
-
-NOTE
-----
-If you want the developer account to call endGenesis(), you would need to:
-- Transfer ownership from deployer to developer account
-- Or grant a custom role that allows endGenesis()
-
-
-
-ISSUE
------
-endGenesis() was failing with Unauthorized() error (0x82b42900)
-
-ROOT CAUSE
-----------
-1. Genesis contract did NOT have ZERO_FEE_ROLE on Minter
- - endGenesis() calls freeMintPeggedToken/freeMintLeveragedToken
- - These functions require ZERO_FEE_ROLE
-
-2. The deploy script grants this role, but it may have failed silently
-
-FIX APPLIED
------------
-✅ Granted ZERO_FEE_ROLE to Genesis on Minter using:
- - Function: grantRoles(address,uint256)
- - Role value: 1 (ZERO_FEE_ROLE)
- - Genesis: 0x572316aC11CB4bc5daf6BDae68f43EA3CCE3aE0e
- - Minter: 0xe38b6847E611e942E6c80eD89aE867F522402e80
-
-VERIFICATION
-------------
-✅ Genesis has ZERO_FEE_ROLE (verified with hasAnyRole)
-✅ Owner account: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
-
-FRONTEND REQUIREMENTS
-----------------------
-For endGenesis() to work from frontend:
-1. Wallet must be connected with owner account: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
-2. Use correct Genesis address: 0x572316aC11CB4bc5daf6BDae68f43EA3CCE3aE0e
-3. Ensure price feeds are up to date (may need to update mock price feeds)
-
-NOTE
-----
-If you want the developer account to call endGenesis(), you would need to:
-- Transfer ownership from deployer to developer account
-- Or grant a custom role that allows endGenesis()
-
-
-
-
-
diff --git a/doc/guides/EVENT-STATUS.md b/doc/guides/EVENT-STATUS.md
deleted file mode 100644
index a8a4a46d..00000000
--- a/doc/guides/EVENT-STATUS.md
+++ /dev/null
@@ -1,138 +0,0 @@
-# Event Status Check
-
-## ✅ On-Chain Events
-
-**Deposit Event Found:**
-- **Block**: 71
-- **Amount**: 100 wstETH (100000000000000000000 wei)
-- **Status**: ✅ Confirmed on-chain
-
-## ❌ Subgraph Indexing
-
-**Current Status:**
-- **Subgraph Block**: 29
-- **Required Block**: 71+
-- **Gap**: 42 blocks behind
-- **Deposits Indexed**: 0
-- **Status**: Not synced
-
-## 🔍 Problem
-
-The subgraph is **stuck at block 29** and hasn't indexed the deposit event that occurred at block 71.
-
-**Root Cause**: The subgraph is likely configured with the **wrong Genesis contract address**. It needs to be updated to:
-- **Genesis Address**: `0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82`
-- **Start Block**: 55
-
-## 🔧 Solution
-
-The subgraph needs to be redeployed with the correct configuration:
-
-1. Update `subgraph.yaml`:
- ```yaml
- network: anvil
- source:
- address: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82"
- startBlock: 55
- ```
-
-2. Redeploy:
- ```bash
- graph build
- graph deploy --node http://localhost:8020/ \
- --ipfs http://localhost:5001 \
- harbor-marks-local
- ```
-
-3. Wait for sync (should catch up quickly on clean chain)
-
-## ✅ Verification
-
-After redeployment, check:
-```bash
-# Check sync status
-curl -X POST http://localhost:8030/graphql \
- -H "Content-Type: application/json" \
- -d '{"query":"{ indexingStatuses { subgraph chains { latestBlock { number } } synced } }"}'
-
-# Query deposits
-curl -X POST http://localhost:8000/subgraphs/name/harbor-marks-local \
- -H "Content-Type: application/json" \
- -d '{"query":"{ deposits { id user amount blockNumber } }"}'
-```
-
----
-
-**Summary**: Events exist on-chain but are NOT indexed by the subgraph because it's configured incorrectly.
-
-
-
-## ✅ On-Chain Events
-
-**Deposit Event Found:**
-- **Block**: 71
-- **Amount**: 100 wstETH (100000000000000000000 wei)
-- **Status**: ✅ Confirmed on-chain
-
-## ❌ Subgraph Indexing
-
-**Current Status:**
-- **Subgraph Block**: 29
-- **Required Block**: 71+
-- **Gap**: 42 blocks behind
-- **Deposits Indexed**: 0
-- **Status**: Not synced
-
-## 🔍 Problem
-
-The subgraph is **stuck at block 29** and hasn't indexed the deposit event that occurred at block 71.
-
-**Root Cause**: The subgraph is likely configured with the **wrong Genesis contract address**. It needs to be updated to:
-- **Genesis Address**: `0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82`
-- **Start Block**: 55
-
-## 🔧 Solution
-
-The subgraph needs to be redeployed with the correct configuration:
-
-1. Update `subgraph.yaml`:
- ```yaml
- network: anvil
- source:
- address: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82"
- startBlock: 55
- ```
-
-2. Redeploy:
- ```bash
- graph build
- graph deploy --node http://localhost:8020/ \
- --ipfs http://localhost:5001 \
- harbor-marks-local
- ```
-
-3. Wait for sync (should catch up quickly on clean chain)
-
-## ✅ Verification
-
-After redeployment, check:
-```bash
-# Check sync status
-curl -X POST http://localhost:8030/graphql \
- -H "Content-Type: application/json" \
- -d '{"query":"{ indexingStatuses { subgraph chains { latestBlock { number } } synced } }"}'
-
-# Query deposits
-curl -X POST http://localhost:8000/subgraphs/name/harbor-marks-local \
- -H "Content-Type: application/json" \
- -d '{"query":"{ deposits { id user amount blockNumber } }"}'
-```
-
----
-
-**Summary**: Events exist on-chain but are NOT indexed by the subgraph because it's configured incorrectly.
-
-
-
-
-
diff --git a/doc/guides/FEE-EXPLANATION.md b/doc/guides/FEE-EXPLANATION.md
deleted file mode 100644
index e4045cc5..00000000
--- a/doc/guides/FEE-EXPLANATION.md
+++ /dev/null
@@ -1,146 +0,0 @@
-# Fee Structure Explanation
-
-## Current Situation
-
-You're seeing **0% fees** when minting anchor tokens, which seems unexpected given we configured a health-based fee structure.
-
-## Why This Is Happening
-
-### System State
-- **Pegged Token Balance**: 0 (no tokens minted yet)
-- **Collateral**: Likely 0 or very small
-- **Collateral Ratio**: Infinity (1e36) when pegged tokens = 0
-
-### Fee Band Logic
-
-When the system is **empty** (no pegged tokens):
-1. Collateral ratio = `infinity` (encoded as `1e36`)
-2. The `_findBand()` function iterates through bands checking `collateralRatio <= bandUpperBound`
-3. Since `1e36` is way higher than any bound (highest is `2.0e18`), it ends up in the **last band**
-4. The last band for mint pegged is: **> 2.0x with 0.5% fee**
-
-### Why You See 0%
-
-There are a few possibilities:
-
-1. **Rounding**: 0.5% fee on 1 wstETH = 0.005 wstETH, which might round to 0 in the UI display
-2. **Edge Case**: The fee calculation might have special handling for empty systems
-3. **First Deposit**: The first deposit might be treated differently (no fees to bootstrap the system)
-
-## Expected Behavior After First Deposit
-
-Once you make the first deposit:
-- Pegged tokens will be minted
-- Collateral ratio will be calculable (not infinity)
-- The ratio will likely be very high initially (since you're adding collateral but the system is new)
-- Fees should appear based on the actual collateral ratio
-
-## Fee Structure Reminder
-
-**Mint Anchor (Pegged) Fees:**
-- < 1.0x: **BLOCKED** (100% fee)
-- 1.0x - 1.05x: **50%** fee
-- 1.05x - 1.1x: **20%** fee
-- 1.1x - 1.2x: **10%** fee
-- 1.2x - 1.3x: **5%** fee
-- 1.3x - 1.5x: **2%** fee
-- 1.5x - 2.0x: **1%** fee
-- **> 2.0x: 0.5% fee** ← You're likely here (empty system = infinite ratio)
-
-## Testing the Fees
-
-To see fees in action:
-
-1. **Make a small first deposit** - This will create pegged tokens and establish a real collateral ratio
-2. **Check the fee again** - It should show 0.5% (or higher if the ratio drops)
-3. **As the system grows** - Fees will adjust based on the actual collateral ratio
-
-## Verification
-
-You can verify the fee config was applied by checking:
-- The transaction hash from `updateConfig()`: `0x636bdc29b546f69b11288547deb39ad557f14c3c79faf0409c50008f7fb156c9`
-- The fee structure is active, but you're in the highest band (lowest fee) due to the empty system state
-
-## Next Steps
-
-1. Make a test deposit to bootstrap the system
-2. Check the fee again - it should show 0.5% (or be calculated properly)
-3. As the collateral ratio changes, fees will adjust accordingly
-
-The fee structure **is working correctly** - you're just in the edge case of an empty system where the ratio is infinite, placing you in the highest (lowest fee) band.
-
-
-
-## Current Situation
-
-You're seeing **0% fees** when minting anchor tokens, which seems unexpected given we configured a health-based fee structure.
-
-## Why This Is Happening
-
-### System State
-- **Pegged Token Balance**: 0 (no tokens minted yet)
-- **Collateral**: Likely 0 or very small
-- **Collateral Ratio**: Infinity (1e36) when pegged tokens = 0
-
-### Fee Band Logic
-
-When the system is **empty** (no pegged tokens):
-1. Collateral ratio = `infinity` (encoded as `1e36`)
-2. The `_findBand()` function iterates through bands checking `collateralRatio <= bandUpperBound`
-3. Since `1e36` is way higher than any bound (highest is `2.0e18`), it ends up in the **last band**
-4. The last band for mint pegged is: **> 2.0x with 0.5% fee**
-
-### Why You See 0%
-
-There are a few possibilities:
-
-1. **Rounding**: 0.5% fee on 1 wstETH = 0.005 wstETH, which might round to 0 in the UI display
-2. **Edge Case**: The fee calculation might have special handling for empty systems
-3. **First Deposit**: The first deposit might be treated differently (no fees to bootstrap the system)
-
-## Expected Behavior After First Deposit
-
-Once you make the first deposit:
-- Pegged tokens will be minted
-- Collateral ratio will be calculable (not infinity)
-- The ratio will likely be very high initially (since you're adding collateral but the system is new)
-- Fees should appear based on the actual collateral ratio
-
-## Fee Structure Reminder
-
-**Mint Anchor (Pegged) Fees:**
-- < 1.0x: **BLOCKED** (100% fee)
-- 1.0x - 1.05x: **50%** fee
-- 1.05x - 1.1x: **20%** fee
-- 1.1x - 1.2x: **10%** fee
-- 1.2x - 1.3x: **5%** fee
-- 1.3x - 1.5x: **2%** fee
-- 1.5x - 2.0x: **1%** fee
-- **> 2.0x: 0.5% fee** ← You're likely here (empty system = infinite ratio)
-
-## Testing the Fees
-
-To see fees in action:
-
-1. **Make a small first deposit** - This will create pegged tokens and establish a real collateral ratio
-2. **Check the fee again** - It should show 0.5% (or higher if the ratio drops)
-3. **As the system grows** - Fees will adjust based on the actual collateral ratio
-
-## Verification
-
-You can verify the fee config was applied by checking:
-- The transaction hash from `updateConfig()`: `0x636bdc29b546f69b11288547deb39ad557f14c3c79faf0409c50008f7fb156c9`
-- The fee structure is active, but you're in the highest band (lowest fee) due to the empty system state
-
-## Next Steps
-
-1. Make a test deposit to bootstrap the system
-2. Check the fee again - it should show 0.5% (or be calculated properly)
-3. As the collateral ratio changes, fees will adjust accordingly
-
-The fee structure **is working correctly** - you're just in the edge case of an empty system where the ratio is infinite, placing you in the highest (lowest fee) band.
-
-
-
-
-
diff --git a/doc/guides/FEE-STRUCTURE-DESIGN.md b/doc/guides/FEE-STRUCTURE-DESIGN.md
deleted file mode 100644
index 97863532..00000000
--- a/doc/guides/FEE-STRUCTURE-DESIGN.md
+++ /dev/null
@@ -1,274 +0,0 @@
-# Fee Structure Design - Health-Based Incentives
-
-## Overview
-
-This fee structure is designed to incentivize actions that improve system health and discourage actions that worsen it, based on the current collateral ratio.
-
-## Key Principles
-
-1. **Minting Anchor (Pegged) Tokens**: Discouraged when system is unhealthy
-2. **Redeeming Anchor (Pegged) Tokens**: Encouraged when system is unhealthy
-3. **Minting Leveraged Tokens**: Encouraged when system is unhealthy (improves health)
-4. **Redeeming Leveraged Tokens**: Discouraged when system is unhealthy
-
-## Fee Structure Details
-
-### 1. Mint Anchor (Pegged) Tokens
-
-**Goal**: Discourage minting when system is unhealthy, allow normal minting when healthy.
-
-| Collateral Ratio | Fee | Behavior |
-|-----------------|-----|----------|
-| < 1.0x | **100% (Disallow)** | Completely blocked - system is undercollateralized |
-| 1.0x - 1.05x | **50%** | Very expensive - system is at risk |
-| 1.05x - 1.1x | **20%** | High fee - system is stressed |
-| 1.1x - 1.2x | **10%** | Medium fee - system is recovering |
-| 1.2x - 1.3x | **5%** | Low fee - system is healthy |
-| 1.3x - 1.5x | **2%** | Very low fee - system is very healthy |
-| 1.5x - 2.0x | **1%** | Minimal fee - system is extremely healthy |
-| > 2.0x | **0.5%** | Minimal fee - system is overcollateralized |
-
-**Rationale**: As collateral ratio approaches minimum (1.0x), minting becomes prohibitively expensive. This prevents further stress on the system.
-
-### 2. Redeem Anchor (Pegged) Tokens
-
-**Goal**: Encourage redemption when system is unhealthy, normal fees when healthy.
-
-| Collateral Ratio | Fee/Discount | Behavior |
-|-----------------|--------------|----------|
-| < 1.0x | **-10% (Discount)** | Strong incentive to redeem - improves system health |
-| 1.0x - 1.05x | **-5% (Discount)** | Incentive to redeem - helps stabilize system |
-| 1.05x - 1.1x | **0%** | Free redemption - system needs help |
-| 1.1x - 1.2x | **1%** | Low fee - system is recovering |
-| 1.2x - 1.3x | **2%** | Small fee - system is healthy |
-| 1.3x - 1.5x | **3%** | Moderate fee - system is very healthy |
-| 1.5x - 2.0x | **4%** | Higher fee - system is extremely healthy |
-| > 2.0x | **5%** | Standard fee - system is overcollateralized |
-
-**Rationale**: When system is unhealthy, redemptions improve the collateral ratio. Discounts/free redemptions incentivize this behavior.
-
-### 3. Mint Leveraged Tokens
-
-**Goal**: Encourage minting when system is unhealthy (improves health), normal fees when healthy.
-
-| Collateral Ratio | Fee/Discount | Behavior |
-|-----------------|--------------|----------|
-| < 1.0x | **-15% (Discount)** | Strong incentive - minting leveraged improves CR |
-| 1.0x - 1.05x | **-10% (Discount)** | Good incentive - helps stabilize system |
-| 1.05x - 1.1x | **-5% (Discount)** | Small incentive - system needs help |
-| 1.1x - 1.2x | **-2% (Discount)** | Minimal incentive - system is recovering |
-| 1.2x - 1.3x | **0%** | Free - system is healthy |
-| 1.3x - 1.5x | **1%** | Small fee - system is very healthy |
-| 1.5x - 2.0x | **2%** | Moderate fee - system is extremely healthy |
-| > 2.0x | **3%** | Standard fee - system is overcollateralized |
-
-**Rationale**: Minting leveraged tokens increases leverage, which improves the collateral ratio when it's low. This is beneficial for system health.
-
-### 4. Redeem Leveraged Tokens
-
-**Goal**: Discourage redemption when system is unhealthy, allow normal redemption when healthy.
-
-| Collateral Ratio | Fee | Behavior |
-|-----------------|-----|----------|
-| < 1.0x | **100% (Disallow)** | Completely blocked - would worsen system health |
-| 1.0x - 1.05x | **30%** | Very expensive - system is at risk |
-| 1.05x - 1.1x | **15%** | High fee - system is stressed |
-| 1.1x - 1.2x | **8%** | Medium-high fee - system is recovering |
-| 1.2x - 1.3x | **5%** | Medium fee - system is healthy |
-| 1.3x - 1.5x | **3%** | Low fee - system is very healthy |
-| 1.5x - 2.0x | **2%** | Very low fee - system is extremely healthy |
-| > 2.0x | **1.5%** | Minimal fee - system is overcollateralized |
-
-**Rationale**: Redeeming leveraged tokens reduces leverage, which worsens the collateral ratio when it's already low. This should be discouraged.
-
-## Implementation Notes
-
-### Incentive Ratio Format
-
-- **Positive values**: Fees (0 to 1.0 ether = 0% to 100%)
-- **Negative values**: Discounts (-1.0 to 0 ether = -100% to 0%)
-- **1.0 ether**: Disallow (100% fee = blocked)
-- **0 ether**: No fee, no discount
-
-### Collateral Ratio Bands
-
-- Bands are defined by `collateralRatioBandUpperBounds`
-- Each band has one `incentiveRatio`
-- First band must start at 1.0x (minimum collateral ratio)
-- Bands must be strictly increasing
-
-### Validation Rules
-
-1. **Mint Pegged / Redeem Leveraged**: Values in [0, 1 ether]
- - Can have disallow (1.0 ether) at index 0
- - Cannot have discounts (negative values)
-
-2. **Redeem Pegged / Mint Leveraged**: Values in (-1 ether, 1 ether)
- - Can have discounts (negative values)
- - Cannot have disallow (1.0 ether)
-
-## Example Scenarios
-
-### Scenario 1: System at 1.05x (Stressed)
-- **Mint Anchor**: 20% fee (expensive)
-- **Redeem Anchor**: -5% discount (encouraged)
-- **Mint Leveraged**: -10% discount (encouraged)
-- **Redeem Leveraged**: 30% fee (discouraged)
-
-### Scenario 2: System at 1.25x (Healthy)
-- **Mint Anchor**: 5% fee (reasonable)
-- **Redeem Anchor**: 2% fee (normal)
-- **Mint Leveraged**: -2% discount (small incentive)
-- **Redeem Leveraged**: 5% fee (normal)
-
-### Scenario 3: System at 0.98x (Undercollateralized)
-- **Mint Anchor**: 100% fee (BLOCKED)
-- **Redeem Anchor**: -10% discount (strongly encouraged)
-- **Mint Leveraged**: -15% discount (strongly encouraged)
-- **Redeem Leveraged**: 100% fee (BLOCKED)
-
-## File Location
-
-Configuration file: `script/minter-fee-config-health-based.json`
-
-This can be used to update the Minter contract configuration via `updateConfig()`.
-
-
-
-## Overview
-
-This fee structure is designed to incentivize actions that improve system health and discourage actions that worsen it, based on the current collateral ratio.
-
-## Key Principles
-
-1. **Minting Anchor (Pegged) Tokens**: Discouraged when system is unhealthy
-2. **Redeeming Anchor (Pegged) Tokens**: Encouraged when system is unhealthy
-3. **Minting Leveraged Tokens**: Encouraged when system is unhealthy (improves health)
-4. **Redeeming Leveraged Tokens**: Discouraged when system is unhealthy
-
-## Fee Structure Details
-
-### 1. Mint Anchor (Pegged) Tokens
-
-**Goal**: Discourage minting when system is unhealthy, allow normal minting when healthy.
-
-| Collateral Ratio | Fee | Behavior |
-|-----------------|-----|----------|
-| < 1.0x | **100% (Disallow)** | Completely blocked - system is undercollateralized |
-| 1.0x - 1.05x | **50%** | Very expensive - system is at risk |
-| 1.05x - 1.1x | **20%** | High fee - system is stressed |
-| 1.1x - 1.2x | **10%** | Medium fee - system is recovering |
-| 1.2x - 1.3x | **5%** | Low fee - system is healthy |
-| 1.3x - 1.5x | **2%** | Very low fee - system is very healthy |
-| 1.5x - 2.0x | **1%** | Minimal fee - system is extremely healthy |
-| > 2.0x | **0.5%** | Minimal fee - system is overcollateralized |
-
-**Rationale**: As collateral ratio approaches minimum (1.0x), minting becomes prohibitively expensive. This prevents further stress on the system.
-
-### 2. Redeem Anchor (Pegged) Tokens
-
-**Goal**: Encourage redemption when system is unhealthy, normal fees when healthy.
-
-| Collateral Ratio | Fee/Discount | Behavior |
-|-----------------|--------------|----------|
-| < 1.0x | **-10% (Discount)** | Strong incentive to redeem - improves system health |
-| 1.0x - 1.05x | **-5% (Discount)** | Incentive to redeem - helps stabilize system |
-| 1.05x - 1.1x | **0%** | Free redemption - system needs help |
-| 1.1x - 1.2x | **1%** | Low fee - system is recovering |
-| 1.2x - 1.3x | **2%** | Small fee - system is healthy |
-| 1.3x - 1.5x | **3%** | Moderate fee - system is very healthy |
-| 1.5x - 2.0x | **4%** | Higher fee - system is extremely healthy |
-| > 2.0x | **5%** | Standard fee - system is overcollateralized |
-
-**Rationale**: When system is unhealthy, redemptions improve the collateral ratio. Discounts/free redemptions incentivize this behavior.
-
-### 3. Mint Leveraged Tokens
-
-**Goal**: Encourage minting when system is unhealthy (improves health), normal fees when healthy.
-
-| Collateral Ratio | Fee/Discount | Behavior |
-|-----------------|--------------|----------|
-| < 1.0x | **-15% (Discount)** | Strong incentive - minting leveraged improves CR |
-| 1.0x - 1.05x | **-10% (Discount)** | Good incentive - helps stabilize system |
-| 1.05x - 1.1x | **-5% (Discount)** | Small incentive - system needs help |
-| 1.1x - 1.2x | **-2% (Discount)** | Minimal incentive - system is recovering |
-| 1.2x - 1.3x | **0%** | Free - system is healthy |
-| 1.3x - 1.5x | **1%** | Small fee - system is very healthy |
-| 1.5x - 2.0x | **2%** | Moderate fee - system is extremely healthy |
-| > 2.0x | **3%** | Standard fee - system is overcollateralized |
-
-**Rationale**: Minting leveraged tokens increases leverage, which improves the collateral ratio when it's low. This is beneficial for system health.
-
-### 4. Redeem Leveraged Tokens
-
-**Goal**: Discourage redemption when system is unhealthy, allow normal redemption when healthy.
-
-| Collateral Ratio | Fee | Behavior |
-|-----------------|-----|----------|
-| < 1.0x | **100% (Disallow)** | Completely blocked - would worsen system health |
-| 1.0x - 1.05x | **30%** | Very expensive - system is at risk |
-| 1.05x - 1.1x | **15%** | High fee - system is stressed |
-| 1.1x - 1.2x | **8%** | Medium-high fee - system is recovering |
-| 1.2x - 1.3x | **5%** | Medium fee - system is healthy |
-| 1.3x - 1.5x | **3%** | Low fee - system is very healthy |
-| 1.5x - 2.0x | **2%** | Very low fee - system is extremely healthy |
-| > 2.0x | **1.5%** | Minimal fee - system is overcollateralized |
-
-**Rationale**: Redeeming leveraged tokens reduces leverage, which worsens the collateral ratio when it's already low. This should be discouraged.
-
-## Implementation Notes
-
-### Incentive Ratio Format
-
-- **Positive values**: Fees (0 to 1.0 ether = 0% to 100%)
-- **Negative values**: Discounts (-1.0 to 0 ether = -100% to 0%)
-- **1.0 ether**: Disallow (100% fee = blocked)
-- **0 ether**: No fee, no discount
-
-### Collateral Ratio Bands
-
-- Bands are defined by `collateralRatioBandUpperBounds`
-- Each band has one `incentiveRatio`
-- First band must start at 1.0x (minimum collateral ratio)
-- Bands must be strictly increasing
-
-### Validation Rules
-
-1. **Mint Pegged / Redeem Leveraged**: Values in [0, 1 ether]
- - Can have disallow (1.0 ether) at index 0
- - Cannot have discounts (negative values)
-
-2. **Redeem Pegged / Mint Leveraged**: Values in (-1 ether, 1 ether)
- - Can have discounts (negative values)
- - Cannot have disallow (1.0 ether)
-
-## Example Scenarios
-
-### Scenario 1: System at 1.05x (Stressed)
-- **Mint Anchor**: 20% fee (expensive)
-- **Redeem Anchor**: -5% discount (encouraged)
-- **Mint Leveraged**: -10% discount (encouraged)
-- **Redeem Leveraged**: 30% fee (discouraged)
-
-### Scenario 2: System at 1.25x (Healthy)
-- **Mint Anchor**: 5% fee (reasonable)
-- **Redeem Anchor**: 2% fee (normal)
-- **Mint Leveraged**: -2% discount (small incentive)
-- **Redeem Leveraged**: 5% fee (normal)
-
-### Scenario 3: System at 0.98x (Undercollateralized)
-- **Mint Anchor**: 100% fee (BLOCKED)
-- **Redeem Anchor**: -10% discount (strongly encouraged)
-- **Mint Leveraged**: -15% discount (strongly encouraged)
-- **Redeem Leveraged**: 100% fee (BLOCKED)
-
-## File Location
-
-Configuration file: `script/minter-fee-config-health-based.json`
-
-This can be used to update the Minter contract configuration via `updateConfig()`.
-
-
-
-
-
diff --git a/doc/guides/FEE-STRUCTURE-QUICK-REFERENCE.md b/doc/guides/FEE-STRUCTURE-QUICK-REFERENCE.md
deleted file mode 100644
index 82243304..00000000
--- a/doc/guides/FEE-STRUCTURE-QUICK-REFERENCE.md
+++ /dev/null
@@ -1,244 +0,0 @@
-# Fee Structure Quick Reference
-
-## How to Apply
-
-### Option 1: Using the Helper Script
-```bash
-# Set Minter address (or it will be read from bcinfo.local.json)
-export MINTER_ADDRESS=0x...
-export RPC_URL=http://localhost:8545 # Optional, defaults to localhost:8545
-export PRIVATE_KEY=0x... # Optional for local, uses Anvil default
-
-# Run the script
-./script/apply-fee-config.sh
-```
-
-### Option 2: Using Forge Script Directly
-```bash
-export MINTER_ADDRESS=0x...
-forge script script/UpdateMinterFees.s.sol:UpdateMinterFees \
- --rpc-url http://localhost:8545 \
- --broadcast \
- --private-key 0x...
-```
-
-### Option 3: Using Cast (Manual)
-```bash
-# Read the config from minter-fee-config-health-based.json
-# Then construct the updateConfig call with the proper tuple format
-cast send $MINTER_ADDRESS \
- "updateConfig(((uint256[],int256[]),(uint256[],int256[]),(uint256[],int256[]),(uint256[],int256[])))" \
- "(($MINT_PEGGED_BOUNDS,$MINT_PEGGED_RATIOS),($REDEEM_PEGGED_BOUNDS,$REDEEM_PEGGED_RATIOS),($MINT_LEVERAGED_BOUNDS,$MINT_LEVERAGED_RATIOS),($REDEEM_LEVERAGED_BOUNDS,$REDEEM_LEVERAGED_RATIOS))" \
- --rpc-url http://localhost:8545 \
- --private-key 0x...
-```
-
-## Fee Structure at a Glance
-
-### Mint Anchor (Pegged) Tokens
-- **< 1.0x**: ❌ BLOCKED (100% fee)
-- **1.0x - 1.05x**: 50% fee
-- **1.05x - 1.1x**: 20% fee
-- **1.1x - 1.2x**: 10% fee
-- **1.2x - 1.3x**: 5% fee
-- **1.3x - 1.5x**: 2% fee
-- **1.5x - 2.0x**: 1% fee
-- **> 2.0x**: 0.5% fee
-
-**Rationale**: Discourage minting when system is unhealthy.
-
-### Redeem Anchor (Pegged) Tokens
-- **< 1.0x**: -10% discount (you get 10% bonus)
-- **1.0x - 1.05x**: -5% discount
-- **1.05x - 1.1x**: FREE (0% fee)
-- **1.1x - 1.2x**: 1% fee
-- **1.2x - 1.3x**: 2% fee
-- **1.3x - 1.5x**: 3% fee
-- **1.5x - 2.0x**: 4% fee
-- **> 2.0x**: 5% fee
-
-**Rationale**: Encourage redemption when system is unhealthy (improves CR).
-
-### Mint Leveraged Tokens
-- **< 1.0x**: -15% discount (you get 15% bonus)
-- **1.0x - 1.05x**: -10% discount
-- **1.05x - 1.1x**: -5% discount
-- **1.1x - 1.2x**: -2% discount
-- **1.2x - 1.3x**: FREE (0% fee)
-- **1.3x - 1.5x**: 1% fee
-- **1.5x - 2.0x**: 2% fee
-- **> 2.0x**: 3% fee
-
-**Rationale**: Encourage minting when system is unhealthy (increases leverage, improves CR).
-
-### Redeem Leveraged Tokens
-- **< 1.0x**: ❌ BLOCKED (100% fee)
-- **1.0x - 1.05x**: 30% fee
-- **1.05x - 1.1x**: 15% fee
-- **1.1x - 1.2x**: 8% fee
-- **1.2x - 1.3x**: 5% fee
-- **1.3x - 1.5x**: 3% fee
-- **1.5x - 2.0x**: 2% fee
-- **> 2.0x**: 1.5% fee
-
-**Rationale**: Discourage redemption when system is unhealthy (reduces leverage, worsens CR).
-
-## Example Scenarios
-
-### System at 1.05x (Stressed)
-- Mint Anchor: **20% fee** (expensive)
-- Redeem Anchor: **-5% discount** (encouraged)
-- Mint Leveraged: **-10% discount** (encouraged)
-- Redeem Leveraged: **30% fee** (discouraged)
-
-### System at 1.25x (Healthy)
-- Mint Anchor: **5% fee** (reasonable)
-- Redeem Anchor: **2% fee** (normal)
-- Mint Leveraged: **-2% discount** (small incentive)
-- Redeem Leveraged: **5% fee** (normal)
-
-### System at 0.98x (Undercollateralized)
-- Mint Anchor: **BLOCKED** ❌
-- Redeem Anchor: **-10% discount** (strongly encouraged)
-- Mint Leveraged: **-15% discount** (strongly encouraged)
-- Redeem Leveraged: **BLOCKED** ❌
-
-## Files
-
-- **Config JSON**: `script/minter-fee-config-health-based.json`
-- **Forge Script**: `script/UpdateMinterFees.s.sol`
-- **Helper Script**: `script/apply-fee-config.sh`
-- **Full Documentation**: `FEE-STRUCTURE-DESIGN.md`
-
-## Notes
-
-- Fees are calculated dynamically based on the current collateral ratio
-- The system uses bands to determine which fee applies
-- Positive values = fees, negative values = discounts
-- `1.0 ether` = 100% fee = disallow (blocked)
-- Only the contract owner can update the config
-
-
-
-## How to Apply
-
-### Option 1: Using the Helper Script
-```bash
-# Set Minter address (or it will be read from bcinfo.local.json)
-export MINTER_ADDRESS=0x...
-export RPC_URL=http://localhost:8545 # Optional, defaults to localhost:8545
-export PRIVATE_KEY=0x... # Optional for local, uses Anvil default
-
-# Run the script
-./script/apply-fee-config.sh
-```
-
-### Option 2: Using Forge Script Directly
-```bash
-export MINTER_ADDRESS=0x...
-forge script script/UpdateMinterFees.s.sol:UpdateMinterFees \
- --rpc-url http://localhost:8545 \
- --broadcast \
- --private-key 0x...
-```
-
-### Option 3: Using Cast (Manual)
-```bash
-# Read the config from minter-fee-config-health-based.json
-# Then construct the updateConfig call with the proper tuple format
-cast send $MINTER_ADDRESS \
- "updateConfig(((uint256[],int256[]),(uint256[],int256[]),(uint256[],int256[]),(uint256[],int256[])))" \
- "(($MINT_PEGGED_BOUNDS,$MINT_PEGGED_RATIOS),($REDEEM_PEGGED_BOUNDS,$REDEEM_PEGGED_RATIOS),($MINT_LEVERAGED_BOUNDS,$MINT_LEVERAGED_RATIOS),($REDEEM_LEVERAGED_BOUNDS,$REDEEM_LEVERAGED_RATIOS))" \
- --rpc-url http://localhost:8545 \
- --private-key 0x...
-```
-
-## Fee Structure at a Glance
-
-### Mint Anchor (Pegged) Tokens
-- **< 1.0x**: ❌ BLOCKED (100% fee)
-- **1.0x - 1.05x**: 50% fee
-- **1.05x - 1.1x**: 20% fee
-- **1.1x - 1.2x**: 10% fee
-- **1.2x - 1.3x**: 5% fee
-- **1.3x - 1.5x**: 2% fee
-- **1.5x - 2.0x**: 1% fee
-- **> 2.0x**: 0.5% fee
-
-**Rationale**: Discourage minting when system is unhealthy.
-
-### Redeem Anchor (Pegged) Tokens
-- **< 1.0x**: -10% discount (you get 10% bonus)
-- **1.0x - 1.05x**: -5% discount
-- **1.05x - 1.1x**: FREE (0% fee)
-- **1.1x - 1.2x**: 1% fee
-- **1.2x - 1.3x**: 2% fee
-- **1.3x - 1.5x**: 3% fee
-- **1.5x - 2.0x**: 4% fee
-- **> 2.0x**: 5% fee
-
-**Rationale**: Encourage redemption when system is unhealthy (improves CR).
-
-### Mint Leveraged Tokens
-- **< 1.0x**: -15% discount (you get 15% bonus)
-- **1.0x - 1.05x**: -10% discount
-- **1.05x - 1.1x**: -5% discount
-- **1.1x - 1.2x**: -2% discount
-- **1.2x - 1.3x**: FREE (0% fee)
-- **1.3x - 1.5x**: 1% fee
-- **1.5x - 2.0x**: 2% fee
-- **> 2.0x**: 3% fee
-
-**Rationale**: Encourage minting when system is unhealthy (increases leverage, improves CR).
-
-### Redeem Leveraged Tokens
-- **< 1.0x**: ❌ BLOCKED (100% fee)
-- **1.0x - 1.05x**: 30% fee
-- **1.05x - 1.1x**: 15% fee
-- **1.1x - 1.2x**: 8% fee
-- **1.2x - 1.3x**: 5% fee
-- **1.3x - 1.5x**: 3% fee
-- **1.5x - 2.0x**: 2% fee
-- **> 2.0x**: 1.5% fee
-
-**Rationale**: Discourage redemption when system is unhealthy (reduces leverage, worsens CR).
-
-## Example Scenarios
-
-### System at 1.05x (Stressed)
-- Mint Anchor: **20% fee** (expensive)
-- Redeem Anchor: **-5% discount** (encouraged)
-- Mint Leveraged: **-10% discount** (encouraged)
-- Redeem Leveraged: **30% fee** (discouraged)
-
-### System at 1.25x (Healthy)
-- Mint Anchor: **5% fee** (reasonable)
-- Redeem Anchor: **2% fee** (normal)
-- Mint Leveraged: **-2% discount** (small incentive)
-- Redeem Leveraged: **5% fee** (normal)
-
-### System at 0.98x (Undercollateralized)
-- Mint Anchor: **BLOCKED** ❌
-- Redeem Anchor: **-10% discount** (strongly encouraged)
-- Mint Leveraged: **-15% discount** (strongly encouraged)
-- Redeem Leveraged: **BLOCKED** ❌
-
-## Files
-
-- **Config JSON**: `script/minter-fee-config-health-based.json`
-- **Forge Script**: `script/UpdateMinterFees.s.sol`
-- **Helper Script**: `script/apply-fee-config.sh`
-- **Full Documentation**: `FEE-STRUCTURE-DESIGN.md`
-
-## Notes
-
-- Fees are calculated dynamically based on the current collateral ratio
-- The system uses bands to determine which fee applies
-- Positive values = fees, negative values = discounts
-- `1.0 ether` = 100% fee = disallow (blocked)
-- Only the contract owner can update the config
-
-
-
-
-
diff --git a/doc/guides/FEE-TO-STABILITY-POOL-REWARDS.md b/doc/guides/FEE-TO-STABILITY-POOL-REWARDS.md
deleted file mode 100644
index ba414c45..00000000
--- a/doc/guides/FEE-TO-STABILITY-POOL-REWARDS.md
+++ /dev/null
@@ -1,354 +0,0 @@
-# Depositing Fees to Stability Pools - Is It Possible?
-
-## Quick Answer
-
-**Yes, it's possible, but it's NOT automatic.** Fees from mint/redeem operations go to the `feeReceiver` address, but there's no built-in mechanism to automatically deposit them to stability pools. However, anyone with the right permissions can manually deposit fees (or any tokens) as rewards.
-
-## How Fees Currently Work
-
-### Where Fees Go
-
-All mint/redeem fees are sent directly to the `feeReceiver` address:
-
-```solidity
-// From Minter_v1.sol
-if (wrappedFee > 0) {
- IERC20(WRAPPED_COLLATERAL_TOKEN).safeTransfer($.feeReceiver, wrappedFee);
-}
-```
-
-**Fee sources:**
-- `mintPeggedToken()` → fees to `feeReceiver`
-- `redeemPeggedToken()` → fees to `feeReceiver`
-- `mintLeveragedToken()` → fees to `feeReceiver`
-- `redeemLeveragedToken()` → fees to `feeReceiver`
-
-**Current behavior:**
-- Fees accumulate at the `feeReceiver` address
-- No automatic distribution to stability pools
-- Fee receiver can do whatever it wants with the fees
-
-## How to Deposit Rewards to Stability Pools
-
-### The `depositReward()` Function
-
-Stability pools have a `depositReward()` function that can deposit any tokens as rewards:
-
-```solidity
-IMultipleRewardDistributor(pool).depositReward(token, amount)
-```
-
-**Who can call it:**
-1. **Owner** of the stability pool
-2. **Anyone with `REWARD_DEPOSITOR_ROLE`** on the pool
-
-### Current Setup
-
-From the test code, `StabilityPoolManager` is granted `REWARD_DEPOSITOR_ROLE` on both pools:
-
-```solidity
-// From test setup
-IBaoRoles(stabilityPoolCollateral).grantRoles(stabilityPoolManager, rewardDepositorRole);
-IBaoRoles(stabilityPoolLeveraged).grantRoles(stabilityPoolManager, rewardDepositorRole);
-```
-
-This is why `StabilityPoolManager` can deposit rewards during harvest.
-
-## Ways to Deposit Fees as Rewards
-
-### Option 1: Manual Deposit by Fee Receiver
-
-If the `feeReceiver` is granted `REWARD_DEPOSITOR_ROLE`:
-
-```solidity
-// Fee receiver calls this after collecting fees
-IMultipleRewardDistributor(stabilityPool).depositReward(wstETH, feeAmount);
-```
-
-**Pros:**
-- Simple
-- Full control over timing and amounts
-
-**Cons:**
-- Requires manual action
-- Fee receiver must have the role
-
-### Option 2: Owner Deposits Fees
-
-The stability pool owner can deposit rewards directly:
-
-```solidity
-// Owner calls this
-IMultipleRewardDistributor(stabilityPool).depositReward(wstETH, feeAmount);
-```
-
-**Pros:**
-- No role needed (owner has permission)
-- Can be done by governance
-
-**Cons:**
-- Requires owner to collect fees from feeReceiver first
-- Manual process
-
-### Option 3: Automated Contract
-
-Create a contract that:
-1. Collects fees from `feeReceiver`
-2. Automatically deposits them to stability pools
-3. Runs periodically (via keeper)
-
-**Example flow:**
-```solidity
-contract FeeDistributor {
- function distributeFees() external {
- uint256 fees = IERC20(wstETH).balanceOf(feeReceiver);
- // Transfer from feeReceiver
- IERC20(wstETH).transferFrom(feeReceiver, address(this), fees);
- // Deposit to pools
- IMultipleRewardDistributor(pool1).depositReward(wstETH, fees / 2);
- IMultipleRewardDistributor(pool2).depositReward(wstETH, fees / 2);
- }
-}
-```
-
-**Pros:**
-- Fully automated
-- Can run on schedule
-- Customizable distribution logic
-
-**Cons:**
-- Requires deploying new contract
-- Needs keeper to trigger
-- Gas costs
-
-### Option 4: Set Fee Receiver to StabilityPoolManager
-
-If `feeReceiver` is set to `StabilityPoolManager`, fees would go there, but they still wouldn't be automatically deposited. You'd need to add logic to `StabilityPoolManager` to automatically deposit fees.
-
-**Note:** This would require modifying `StabilityPoolManager` contract.
-
-## Comparison: Harvest vs Fee Deposits
-
-| Aspect | Harvest | Fee Deposits |
-|--------|---------|--------------|
-| **Source** | Minter's harvestable amount | Mint/redeem fees |
-| **Automatic?** | No (requires `harvest()` call) | No (requires manual/automated deposit) |
-| **Who can do it?** | Anyone (public function) | Owner or `REWARD_DEPOSITOR_ROLE` |
-| **Vesting?** | Yes (7 days linear) | Yes (7 days linear) |
-| **Distribution** | Proportional to pool sizes | Manual (can choose pools) |
-
-## Current State
-
-**What exists:**
-- ✅ `depositReward()` function on stability pools
-- ✅ `REWARD_DEPOSITOR_ROLE` system
-- ✅ `StabilityPoolManager` has the role (for harvest)
-
-**What doesn't exist:**
-- ❌ Automatic fee distribution to pools
-- ❌ Built-in mechanism to route fees to pools
-- ❌ Fee receiver automatically depositing rewards
-
-## Summary
-
-**Can fees be deposited to stability pools?**
-- ✅ **Yes** - via `depositReward()` function
-
-**Is it automatic?**
-- ❌ **No** - requires manual action or automated contract
-
-**How to enable it:**
-1. Grant `REWARD_DEPOSITOR_ROLE` to fee receiver (or another address)
-2. Manually call `depositReward()` with collected fees
-3. Or deploy an automated contract to do it
-
-**Benefits:**
-- Fees become rewards for stability pool depositors
-- Encourages more deposits
-- Better alignment of incentives
-
-**Considerations:**
-- Fees vest over 7 days (same as harvest rewards)
-- Can choose which pools to deposit to
-- Can deposit any amount at any time
-- Requires active management or automation
-
-
-
-## Quick Answer
-
-**Yes, it's possible, but it's NOT automatic.** Fees from mint/redeem operations go to the `feeReceiver` address, but there's no built-in mechanism to automatically deposit them to stability pools. However, anyone with the right permissions can manually deposit fees (or any tokens) as rewards.
-
-## How Fees Currently Work
-
-### Where Fees Go
-
-All mint/redeem fees are sent directly to the `feeReceiver` address:
-
-```solidity
-// From Minter_v1.sol
-if (wrappedFee > 0) {
- IERC20(WRAPPED_COLLATERAL_TOKEN).safeTransfer($.feeReceiver, wrappedFee);
-}
-```
-
-**Fee sources:**
-- `mintPeggedToken()` → fees to `feeReceiver`
-- `redeemPeggedToken()` → fees to `feeReceiver`
-- `mintLeveragedToken()` → fees to `feeReceiver`
-- `redeemLeveragedToken()` → fees to `feeReceiver`
-
-**Current behavior:**
-- Fees accumulate at the `feeReceiver` address
-- No automatic distribution to stability pools
-- Fee receiver can do whatever it wants with the fees
-
-## How to Deposit Rewards to Stability Pools
-
-### The `depositReward()` Function
-
-Stability pools have a `depositReward()` function that can deposit any tokens as rewards:
-
-```solidity
-IMultipleRewardDistributor(pool).depositReward(token, amount)
-```
-
-**Who can call it:**
-1. **Owner** of the stability pool
-2. **Anyone with `REWARD_DEPOSITOR_ROLE`** on the pool
-
-### Current Setup
-
-From the test code, `StabilityPoolManager` is granted `REWARD_DEPOSITOR_ROLE` on both pools:
-
-```solidity
-// From test setup
-IBaoRoles(stabilityPoolCollateral).grantRoles(stabilityPoolManager, rewardDepositorRole);
-IBaoRoles(stabilityPoolLeveraged).grantRoles(stabilityPoolManager, rewardDepositorRole);
-```
-
-This is why `StabilityPoolManager` can deposit rewards during harvest.
-
-## Ways to Deposit Fees as Rewards
-
-### Option 1: Manual Deposit by Fee Receiver
-
-If the `feeReceiver` is granted `REWARD_DEPOSITOR_ROLE`:
-
-```solidity
-// Fee receiver calls this after collecting fees
-IMultipleRewardDistributor(stabilityPool).depositReward(wstETH, feeAmount);
-```
-
-**Pros:**
-- Simple
-- Full control over timing and amounts
-
-**Cons:**
-- Requires manual action
-- Fee receiver must have the role
-
-### Option 2: Owner Deposits Fees
-
-The stability pool owner can deposit rewards directly:
-
-```solidity
-// Owner calls this
-IMultipleRewardDistributor(stabilityPool).depositReward(wstETH, feeAmount);
-```
-
-**Pros:**
-- No role needed (owner has permission)
-- Can be done by governance
-
-**Cons:**
-- Requires owner to collect fees from feeReceiver first
-- Manual process
-
-### Option 3: Automated Contract
-
-Create a contract that:
-1. Collects fees from `feeReceiver`
-2. Automatically deposits them to stability pools
-3. Runs periodically (via keeper)
-
-**Example flow:**
-```solidity
-contract FeeDistributor {
- function distributeFees() external {
- uint256 fees = IERC20(wstETH).balanceOf(feeReceiver);
- // Transfer from feeReceiver
- IERC20(wstETH).transferFrom(feeReceiver, address(this), fees);
- // Deposit to pools
- IMultipleRewardDistributor(pool1).depositReward(wstETH, fees / 2);
- IMultipleRewardDistributor(pool2).depositReward(wstETH, fees / 2);
- }
-}
-```
-
-**Pros:**
-- Fully automated
-- Can run on schedule
-- Customizable distribution logic
-
-**Cons:**
-- Requires deploying new contract
-- Needs keeper to trigger
-- Gas costs
-
-### Option 4: Set Fee Receiver to StabilityPoolManager
-
-If `feeReceiver` is set to `StabilityPoolManager`, fees would go there, but they still wouldn't be automatically deposited. You'd need to add logic to `StabilityPoolManager` to automatically deposit fees.
-
-**Note:** This would require modifying `StabilityPoolManager` contract.
-
-## Comparison: Harvest vs Fee Deposits
-
-| Aspect | Harvest | Fee Deposits |
-|--------|---------|--------------|
-| **Source** | Minter's harvestable amount | Mint/redeem fees |
-| **Automatic?** | No (requires `harvest()` call) | No (requires manual/automated deposit) |
-| **Who can do it?** | Anyone (public function) | Owner or `REWARD_DEPOSITOR_ROLE` |
-| **Vesting?** | Yes (7 days linear) | Yes (7 days linear) |
-| **Distribution** | Proportional to pool sizes | Manual (can choose pools) |
-
-## Current State
-
-**What exists:**
-- ✅ `depositReward()` function on stability pools
-- ✅ `REWARD_DEPOSITOR_ROLE` system
-- ✅ `StabilityPoolManager` has the role (for harvest)
-
-**What doesn't exist:**
-- ❌ Automatic fee distribution to pools
-- ❌ Built-in mechanism to route fees to pools
-- ❌ Fee receiver automatically depositing rewards
-
-## Summary
-
-**Can fees be deposited to stability pools?**
-- ✅ **Yes** - via `depositReward()` function
-
-**Is it automatic?**
-- ❌ **No** - requires manual action or automated contract
-
-**How to enable it:**
-1. Grant `REWARD_DEPOSITOR_ROLE` to fee receiver (or another address)
-2. Manually call `depositReward()` with collected fees
-3. Or deploy an automated contract to do it
-
-**Benefits:**
-- Fees become rewards for stability pool depositors
-- Encourages more deposits
-- Better alignment of incentives
-
-**Considerations:**
-- Fees vest over 7 days (same as harvest rewards)
-- Can choose which pools to deposit to
-- Can deposit any amount at any time
-- Requires active management or automation
-
-
-
-
-
diff --git a/doc/guides/FIX-END-GENESIS.md b/doc/guides/FIX-END-GENESIS.md
deleted file mode 100644
index 73fd1d32..00000000
--- a/doc/guides/FIX-END-GENESIS.md
+++ /dev/null
@@ -1,104 +0,0 @@
-# Fix: End Genesis Transaction Failing
-
-## Problem
-The "End Genesis" transaction is failing with error `0xd2159c14`.
-
-## Root Causes
-
-### 1. Frontend Using OLD Genesis Address
-The frontend is calling the **OLD** Genesis contract:
-- **OLD (wrong):** `0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82`
-- **NEW (correct):** `0xAD523115cd35a8d4E60B3C0953E0E0ac10418309`
-
-### 2. Missing ZERO_FEE_ROLE
-The `endGenesis()` function calls `freeMintPeggedToken()` and `freeMintLeveragedToken()` on the Minter, which requires Genesis to have `ZERO_FEE_ROLE` on the Minter contract.
-
-## Solutions
-
-### Solution 1: Update Frontend Genesis Address (REQUIRED)
-Update your frontend configuration to use the NEW Genesis address:
-```
-Genesis: 0xAD523115cd35a8d4E60B3C0953E0E0ac10418309
-```
-
-### Solution 2: Grant ZERO_FEE_ROLE to Genesis (if needed)
-If the NEW Genesis doesn't have ZERO_FEE_ROLE on the Minter, grant it:
-
-```bash
-MINTER="0x8A791620dd6260079BF849Dc5567aDC3F2FdC318"
-NEW_GENESIS="0xAD523115cd35a8d4E60B3C0953E0E0ac10418309"
-ZERO_FEE_ROLE=$(cast keccak "ZERO_FEE_ROLE()")
-OWNER_PK="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
-
-cast send $MINTER "grantRoles(address,uint256)" $NEW_GENESIS $ZERO_FEE_ROLE \
- --rpc-url http://localhost:8545 \
- --private-key $OWNER_PK
-```
-
-## Verification
-
-After fixing, verify:
-1. Frontend is using NEW Genesis address
-2. NEW Genesis has ZERO_FEE_ROLE on Minter
-3. Owner account can call `endGenesis()` on NEW Genesis
-
-## Current Contract Addresses
-
-- **Genesis (NEW):** `0xAD523115cd35a8d4E60B3C0953E0E0ac10418309`
-- **Minter:** `0x8A791620dd6260079BF849Dc5567aDC3F2FdC318`
-- **Owner:** `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`
-
-
-
-## Problem
-The "End Genesis" transaction is failing with error `0xd2159c14`.
-
-## Root Causes
-
-### 1. Frontend Using OLD Genesis Address
-The frontend is calling the **OLD** Genesis contract:
-- **OLD (wrong):** `0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82`
-- **NEW (correct):** `0xAD523115cd35a8d4E60B3C0953E0E0ac10418309`
-
-### 2. Missing ZERO_FEE_ROLE
-The `endGenesis()` function calls `freeMintPeggedToken()` and `freeMintLeveragedToken()` on the Minter, which requires Genesis to have `ZERO_FEE_ROLE` on the Minter contract.
-
-## Solutions
-
-### Solution 1: Update Frontend Genesis Address (REQUIRED)
-Update your frontend configuration to use the NEW Genesis address:
-```
-Genesis: 0xAD523115cd35a8d4E60B3C0953E0E0ac10418309
-```
-
-### Solution 2: Grant ZERO_FEE_ROLE to Genesis (if needed)
-If the NEW Genesis doesn't have ZERO_FEE_ROLE on the Minter, grant it:
-
-```bash
-MINTER="0x8A791620dd6260079BF849Dc5567aDC3F2FdC318"
-NEW_GENESIS="0xAD523115cd35a8d4E60B3C0953E0E0ac10418309"
-ZERO_FEE_ROLE=$(cast keccak "ZERO_FEE_ROLE()")
-OWNER_PK="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
-
-cast send $MINTER "grantRoles(address,uint256)" $NEW_GENESIS $ZERO_FEE_ROLE \
- --rpc-url http://localhost:8545 \
- --private-key $OWNER_PK
-```
-
-## Verification
-
-After fixing, verify:
-1. Frontend is using NEW Genesis address
-2. NEW Genesis has ZERO_FEE_ROLE on Minter
-3. Owner account can call `endGenesis()` on NEW Genesis
-
-## Current Contract Addresses
-
-- **Genesis (NEW):** `0xAD523115cd35a8d4E60B3C0953E0E0ac10418309`
-- **Minter:** `0x8A791620dd6260079BF849Dc5567aDC3F2FdC318`
-- **Owner:** `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`
-
-
-
-
-
diff --git a/doc/guides/FRONTEND-ADDRESSES-CLEAN-CHAIN.txt b/doc/guides/FRONTEND-ADDRESSES-CLEAN-CHAIN.txt
deleted file mode 100644
index b635a66a..00000000
--- a/doc/guides/FRONTEND-ADDRESSES-CLEAN-CHAIN.txt
+++ /dev/null
@@ -1,61 +0,0 @@
-=== Harbor Frontend Contract Addresses (Clean Chain) ===
-
-Network Configuration:
-- Network Name: Local Anvil
-- Chain ID: 31337
-- RPC URL: http://localhost:8545
-
-=== Core Contracts ===
-Genesis: 0xA4899D35897033b927acFCf422bc745916139776 (PROXY - use this!)
-Genesis Implementation: 0x99dBE4AEa58E518C50a1c04aE9b48C9F6354612f (do not use)
-Minter: 0x34B40BA116d5Dec75548a9e9A8f15411461E8c70
-StabilityPoolManager: 0xb9bEECD1A582768711dE1EE7B0A1d582D9d72a6C
-
-=== Tokens ===
-PeggedToken (haPB): 0x1c85638e118b37167e9298c2268758e058DdfDA0
-LeveragedToken (hsPB): 0x367761085BF3C12e5DA2Df99AC6E1a824612b8fb
-wstETH: 0x0165878A594ca255338adfa4d48449f69242Eb8F
-stETH: 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
-
-=== Stability Pools ===
-StabilityPoolCollateral: 0x3aAde2dCD2Df6a8cAc689EE797591b2913658659
-StabilityPoolLeveraged: 0x525C7063E7C20997BaaE9bDa922159152D0e8417
-
-=== Price Feeds ===
-stETH/USD: 0xa513E6E4b8f2a923D98304ec87F64353C4D5C853
-stETH/ETH: 0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6
-wstETH/USD: 0x8A791620dd6260079BF849Dc5567aDC3F2FdC318
-PriceOracle: 0x0000000000000000000000000000000000000000
-
-=== GraphQL Endpoint ===
-http://localhost:8000/subgraphs/name/harbor-marks-local
-
-=== Developer Account ===
-Address: 0xAE7Dbb17bc40D53A6363409c6B1ED88d3cFdc31e
-Balance: 1000 wstETH, 1000 stETH
-
-=== Environment Variables Format ===
-NEXT_PUBLIC_CHAIN_ID=31337
-NEXT_PUBLIC_RPC_URL=http://localhost:8545
-NEXT_PUBLIC_GENESIS=0xA4899D35897033b927acFCf422bc745916139776
-NEXT_PUBLIC_MINTER=0x34B40BA116d5Dec75548a9e9A8f15411461E8c70
-NEXT_PUBLIC_PEGGED_TOKEN=0x1c85638e118b37167e9298c2268758e058DdfDA0
-NEXT_PUBLIC_LEVERAGED_TOKEN=0x367761085BF3C12e5DA2Df99AC6E1a824612b8fb
-NEXT_PUBLIC_WSTETH=0x0165878A594ca255338adfa4d48449f69242Eb8F
-NEXT_PUBLIC_STETH=0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
-NEXT_PUBLIC_STABILITY_POOL_COLLATERAL=0x3aAde2dCD2Df6a8cAc689EE797591b2913658659
-NEXT_PUBLIC_STABILITY_POOL_LEVERAGED=0x525C7063E7C20997BaaE9bDa922159152D0e8417
-NEXT_PUBLIC_STABILITY_POOL_MANAGER=0xb9bEECD1A582768711dE1EE7B0A1d582D9d72a6C
-NEXT_PUBLIC_GRAPHQL_ENDPOINT=http://localhost:8000/subgraphs/name/harbor-marks-local
-
-=== CORRECTED: Genesis Proxy Address ===
-⚠️ IMPORTANT: The Genesis address above is the IMPLEMENTATION, not the proxy!
-
-Correct Genesis Proxy Address: 0xA4899D35897033b927acFCf422bc745916139776
-
-Use this address in your frontend, not 0x99dBE4AEa58E518C50a1c04aE9b48C9F6354612f
-
-Updated Environment Variable:
-NEXT_PUBLIC_GENESIS=0xA4899D35897033b927acFCf422bc745916139776
-
-
diff --git a/doc/guides/FRONTEND-APR-CALCULATION.md b/doc/guides/FRONTEND-APR-CALCULATION.md
deleted file mode 100644
index bbce3b64..00000000
--- a/doc/guides/FRONTEND-APR-CALCULATION.md
+++ /dev/null
@@ -1,519 +0,0 @@
-# Frontend APR Calculation for Stability Pools - Next Period Projection
-
-## Overview
-
-This guide explains how to calculate a **projected APR for the NEXT reward period** (after a harvest at the end of the current 7-day period) for stability pool deposits.
-
-**Key Point:** This calculates what the APR would be **if a harvest happens at the end of the current 7-day period**, using current harvestable amount as a projection. This is useful for showing users what they can expect after the next harvest, especially at launch when no harvests have happened yet.
-
-**Use Case:** At launch, there are no rewards yet. You want to show depositors: "Based on current conditions, if we harvest at the end of this 7-day period, here's the projected APR you'll earn."
-
-## The Calculation Flow
-
-**Assumption:** We use the **current harvestable amount** as a projection of what will be available at the end of the current 7-day period. This is a reasonable estimate since harvestable accumulates over time, and we're projecting forward 7 days.
-
-### Step 1: Get Current State and Last Harvest Info
-
-```typescript
-// Get current harvestable amount
-const currentHarvestable = await minter.harvestable();
-
-// Get current wstETH balance in Minter
-const wrappedCollateralToken = await minter.WRAPPED_COLLATERAL_TOKEN();
-const currentBalance = await IERC20(wrappedCollateralToken).balanceOf(minter.address);
-
-// Get wstETH contract to query rate
-const wstETH = new ethers.Contract(wrappedCollateralToken, WSTETH_ABI, provider);
-const currentRate = await wstETH.stEthPerToken();
-
-// Calculate underlying collateral
-// underlyingCollateral = (balance - harvestable) * rate / 1e18
-const underlyingCollateral = ((currentBalance - currentHarvestable) * currentRate) / 1e18;
-
-// Get current block timestamp
-const currentTimestamp = (await provider.getBlock("latest")).timestamp;
-
-// Get reward period info from stability pool
-// This tells us when the current reward period ends (finishAt)
-const rewardData = await stabilityPool.rewardData(wrappedCollateralToken);
-const { finishAt, lastUpdate } = rewardData;
-const REWARD_PERIOD_LENGTH = await stabilityPool.REWARD_PERIOD_LENGTH(); // Typically 604800 (7 days)
-```
-
-### Step 2: Calculate Remaining Time Until Period End
-
-```typescript
-// Determine how much time is left in the current period
-let remainingSeconds = 0n;
-let timeSinceLastHarvest = 0n;
-
-if (finishAt > currentTimestamp) {
- // Active reward period exists - calculate remaining time
- remainingSeconds = BigInt(finishAt) - BigInt(currentTimestamp);
- // Time since last harvest (when this period started)
- timeSinceLastHarvest = BigInt(currentTimestamp) - BigInt(lastUpdate);
-} else {
- // No active period (period ended or never started)
- // Project for full 7-day period
- remainingSeconds = BigInt(REWARD_PERIOD_LENGTH);
- timeSinceLastHarvest = 0n;
-}
-
-// Convert to days for calculation
-const remainingDays = Number(remainingSeconds) / 86400;
-const daysSinceLastHarvest = Number(timeSinceLastHarvest) / 86400;
-```
-
-### Step 3: Project Additional Yield for Remaining Days
-
-```typescript
-// Calculate additional yield that will accumulate over remaining days
-// wstETH rate increases due to staking rewards (~3-4% APR typically)
-const STAKING_APR = 0.035; // 3.5% (adjust based on actual stETH staking rate)
-const dailyRate = STAKING_APR / 365;
-
-// Project rate forward by remaining days
-const rateGrowthFactor = 1 + dailyRate * remainingDays;
-const projectedRate = (currentRate * BigInt(Math.floor(rateGrowthFactor * 1e18))) / 1e18;
-
-// Calculate additional harvestable from remaining yield
-// The underlying collateral stays the same, but rate increases
-const currentValue = (underlyingCollateral * 1e18) / currentRate; // Current value in wstETH
-const projectedValue = (underlyingCollateral * 1e18) / projectedRate; // Future value in wstETH
-const additionalYield = currentValue > projectedValue ? currentValue - projectedValue : 0n;
-
-// Total projected harvestable = current + additional yield
-const projectedHarvestable = currentHarvestable + additionalYield;
-```
-
-**Note:**
-
-- If 3 days have passed since last harvest, we calculate yield for the remaining 4 days
-- If no harvest has happened yet, we project for the full 7-day period
-- The harvestable grows because the wstETH rate increases (staking rewards), making the same underlying collateral worth more in wstETH terms
-
-### Step 3: Calculate What Would Go to Pools
-
-```typescript
-// Get harvest ratios from StabilityPoolManager
-const harvestBountyRatio = await stabilityPoolManager.harvestBountyRatio();
-const harvestCutRatio = await stabilityPoolManager.harvestCutRatio();
-
-// Calculate deductions
-const bountyAmount = (harvestableAmount * harvestBountyRatio) / 1e18;
-const cutAmount = (harvestableAmount * harvestCutRatio) / 1e18;
-
-// Calculate what remains for pools
-const harvestableRemaining = harvestableAmount - bountyAmount - cutAmount;
-```
-
-### Step 4: Calculate Split Between Pools
-
-```typescript
-// Get pool holdings (total deposits in each pool)
-const poolCollateral = await stabilityPoolCollateral.totalAssetSupply();
-const poolLeveraged = await stabilityPoolLeveraged.totalAssetSupply();
-const totalPoolHolding = poolCollateral + poolLeveraged;
-
-// Calculate how much would go to each pool
-let harvestedToCollateral = 0n;
-if (totalPoolHolding > 0) {
- harvestedToCollateral = (harvestableRemaining * poolCollateral) / totalPoolHolding;
- // harvestedToLeveraged = harvestableRemaining - harvestedToCollateral
-}
-```
-
-### Step 5: Calculate Projected Reward Rate
-
-```typescript
-// Get current reward data to check for queued rewards
-const currentRewardData = await stabilityPool.rewardData(rewardTokenAddress);
-const { queued } = currentRewardData;
-
-// The amount that would be deposited to this pool
-const newRewardsAmount = harvestedToCollateral; // or harvestedToLeveraged for leveraged pool
-
-// Total rewards for next period = new rewards + any queued rewards
-const totalRewardsForNextPeriod = newRewardsAmount + queued;
-
-// REWARD_PERIOD_LENGTH = 7 days = 604,800 seconds (1 week)
-const REWARD_PERIOD_LENGTH = 7 * 24 * 60 * 60; // 604,800
-
-// Calculate the new rate that would be set
-// rate = totalRewards / periodLength (rewards per second)
-const projectedRate = totalRewardsForNextPeriod / BigInt(REWARD_PERIOD_LENGTH);
-```
-
-### Step 6: Calculate Rate Per Token
-
-```typescript
-// Get current pool supply (total deposits)
-const totalSupply = await stabilityPool.totalAssetSupply();
-
-// Calculate rate per token per second
-const ratePerTokenPerSecond = totalSupply > 0 ? Number(projectedRate) / Number(totalSupply) : 0;
-```
-
-### Step 7: Project Rewards for 7 Days
-
-```typescript
-// Get user's deposit
-const userBalance = await stabilityPool.assetBalanceOf(userAddress);
-
-// Project rewards for 7 days
-const SECONDS_IN_7_DAYS = 7 * 24 * 60 * 60; // 604,800
-const projectedRewards7Days = ratePerTokenPerSecond * Number(userBalance) * SECONDS_IN_7_DAYS;
-```
-
-### Step 8: Calculate APR
-
-```typescript
-// Get token prices (implement based on your price oracle)
-const rewardTokenPrice = await getTokenPrice(rewardTokenAddress); // USD per token
-const depositTokenPrice = await getTokenPrice(depositTokenAddress); // USD per token
-
-// Calculate values in USD
-const userDepositValueUSD = (Number(userBalance) * depositTokenPrice) / 1e18;
-const projectedRewardsValueUSD = (projectedRewards7Days * rewardTokenPrice) / 1e18;
-
-// Calculate APR (annualized from 7-day projection)
-if (userDepositValueUSD === 0) return 0;
-const apr = (projectedRewardsValueUSD / userDepositValueUSD) * (365 / 7) * 100;
-```
-
-## Complete Example Function
-
-```typescript
-async function calculateProjectedAPRForNextPeriod(
- minter: Contract,
- stabilityPoolManager: Contract,
- stabilityPool: Contract, // The specific pool (collateral or leveraged)
- stabilityPoolCollateral: Contract,
- stabilityPoolLeveraged: Contract,
- rewardTokenAddress: string,
- depositTokenAddress: string,
- userAddress: string,
-): Promise {
- // Step 1: Get harvestable amount
- const harvestableAmount = await minter.harvestable();
-
- if (harvestableAmount === 0n) {
- return 0; // No harvestable = no projected APR
- }
-
- // Step 2: Calculate what would go to pools
- const harvestBountyRatio = await stabilityPoolManager.harvestBountyRatio();
- const harvestCutRatio = await stabilityPoolManager.harvestCutRatio();
-
- const bountyAmount = (harvestableAmount * harvestBountyRatio) / 1e18;
- const cutAmount = (harvestableAmount * harvestCutRatio) / 1e18;
- const harvestableRemaining = harvestableAmount - bountyAmount - cutAmount;
-
- // Step 3: Calculate split between pools
- const poolCollateral = await stabilityPoolCollateral.totalAssetSupply();
- const poolLeveraged = await stabilityPoolLeveraged.totalAssetSupply();
- const totalPoolHolding = poolCollateral + poolLeveraged;
-
- if (totalPoolHolding === 0n) {
- return 0; // No deposits = no APR
- }
-
- // Determine which pool we're calculating for
- const poolAddress = stabilityPool.address;
- const collateralPoolAddress = stabilityPoolCollateral.address;
- const isCollateralPool = poolAddress.toLowerCase() === collateralPoolAddress.toLowerCase();
-
- // Calculate how much would go to this specific pool
- const harvestedToThisPool = isCollateralPool
- ? (harvestableRemaining * poolCollateral) / totalPoolHolding
- : (harvestableRemaining * poolLeveraged) / totalPoolHolding;
-
- // Step 4: Get queued rewards and calculate projected rate
- const currentRewardData = await stabilityPool.rewardData(rewardTokenAddress);
- const queued = currentRewardData.queued;
-
- // Total rewards for next period
- const totalRewardsForNextPeriod = harvestedToThisPool + queued;
-
- // REWARD_PERIOD_LENGTH = 1 week = 604,800 seconds
- const REWARD_PERIOD_LENGTH = 7 * 24 * 60 * 60;
-
- // Projected rate (rewards per second)
- const projectedRate = totalRewardsForNextPeriod / BigInt(REWARD_PERIOD_LENGTH);
-
- // Step 5: Calculate rate per token
- const totalSupply = await stabilityPool.totalAssetSupply();
-
- if (totalSupply === 0n) {
- return 0;
- }
-
- const ratePerTokenPerSecond = Number(projectedRate) / Number(totalSupply);
-
- // Step 6: Get user balance and project 7 days
- const userBalance = await stabilityPool.assetBalanceOf(userAddress);
-
- if (userBalance === 0n) {
- return 0;
- }
-
- const SECONDS_IN_7_DAYS = 604800;
- const projectedRewards7Days = ratePerTokenPerSecond * Number(userBalance) * SECONDS_IN_7_DAYS;
-
- // Step 7: Calculate APR
- const rewardTokenPrice = await getTokenPrice(rewardTokenAddress);
- const depositTokenPrice = await getTokenPrice(depositTokenAddress);
-
- const userDepositValueUSD = (Number(userBalance) * depositTokenPrice) / 1e18;
- const projectedRewardsValueUSD = (projectedRewards7Days * rewardTokenPrice) / 1e18;
-
- if (userDepositValueUSD === 0) {
- return 0;
- }
-
- // Annualized APR from 7-day projection
- const apr = (projectedRewardsValueUSD / userDepositValueUSD) * (365 / 7) * 100;
-
- return apr;
-}
-```
-
-## Simplified Version (Recommended)
-
-```typescript
-async function getProjectedAPRNextPeriod(
- minter: Contract,
- stabilityPoolManager: Contract,
- stabilityPool: Contract,
- stabilityPoolCollateral: Contract,
- stabilityPoolLeveraged: Contract,
- rewardToken: string,
- userAddress: string,
- wstETHContract: Contract,
- stakingAPR: number = 0.035, // 3.5% default (adjust based on actual stETH rate)
-): Promise {
- // 1. Get current state
- const currentHarvestable = await minter.harvestable();
- const wrappedCollateralToken = await minter.WRAPPED_COLLATERAL_TOKEN();
- const currentBalance = await IERC20(wrappedCollateralToken).balanceOf(minter.address);
- const currentRate = await wstETHContract.stEthPerToken();
- const underlyingCollateral = ((currentBalance - currentHarvestable) * currentRate) / 1e18;
-
- // 2. Get reward period info to find remaining time
- const rewardData = await stabilityPool.rewardData(wrappedCollateralToken);
- const { finishAt } = rewardData;
- const REWARD_PERIOD_LENGTH = await stabilityPool.REWARD_PERIOD_LENGTH();
- const provider = minter.provider;
- const currentTimestamp = BigInt((await provider.getBlock("latest")).timestamp);
-
- // 3. Calculate remaining time until period end
- const remainingSeconds =
- finishAt > currentTimestamp ? BigInt(finishAt) - currentTimestamp : BigInt(REWARD_PERIOD_LENGTH);
- const remainingDays = Number(remainingSeconds) / 86400;
-
- // 4. Project additional yield for remaining days
- const dailyRate = stakingAPR / 365;
- const rateGrowthFactor = 1 + dailyRate * remainingDays;
- const projectedRate = (currentRate * BigInt(Math.floor(rateGrowthFactor * 1e18))) / 1e18;
- const currentValue = (underlyingCollateral * 1e18) / currentRate;
- const projectedValue = (underlyingCollateral * 1e18) / projectedRate;
- const additionalYield = currentValue > projectedValue ? currentValue - projectedValue : 0n;
- const harvestable = currentHarvestable + additionalYield;
-
- if (harvestable === 0n) return 0;
-
- // 2. Calculate pool allocation
- const bountyRatio = await stabilityPoolManager.harvestBountyRatio();
- const cutRatio = await stabilityPoolManager.harvestCutRatio();
- const remaining = harvestable - (harvestable * bountyRatio) / 1e18 - (harvestable * cutRatio) / 1e18;
-
- // 3. Get pool split
- const poolCollateral = await stabilityPoolCollateral.totalAssetSupply();
- const poolLeveraged = await stabilityPoolLeveraged.totalAssetSupply();
- const totalHolding = poolCollateral + poolLeveraged;
- if (totalHolding === 0n) return 0;
-
- // 4. Determine which pool
- const isCollateral = stabilityPool.address.toLowerCase() === stabilityPoolCollateral.address.toLowerCase();
- const toThisPool = isCollateral
- ? (remaining * poolCollateral) / totalHolding
- : (remaining * poolLeveraged) / totalHolding;
-
- // 5. Get queued and calculate rate
- const { queued } = await stabilityPool.rewardData(rewardToken);
- const totalRewards = toThisPool + queued;
- const REWARD_PERIOD_LENGTH = 604800n; // 7 days
- const projectedRate = totalRewards / REWARD_PERIOD_LENGTH;
-
- // 6. Calculate per-token rate
- const totalSupply = await stabilityPool.totalAssetSupply();
- if (totalSupply === 0n) return 0;
- const ratePerToken = Number(projectedRate) / Number(totalSupply);
-
- // 7. Project 7 days for user
- const userBalance = await stabilityPool.assetBalanceOf(userAddress);
- if (userBalance === 0n) return 0;
- const rewards7Days = ratePerToken * Number(userBalance) * 604800;
-
- // 8. Calculate APR
- const rewardPrice = await getTokenPrice(rewardToken);
- const depositPrice = await getTokenPrice(await stabilityPool.ASSET_TOKEN());
- const depositValue = (Number(userBalance) * depositPrice) / 1e18;
- const rewardValue = (rewards7Days * rewardPrice) / 1e18;
-
- return depositValue > 0 ? (rewardValue / depositValue) * (365 / 7) * 100 : 0;
-}
-```
-
-## Important Considerations
-
-### 1. Queued Rewards
-
-The calculation includes `queued` rewards that are waiting to be distributed. These will be part of the next period.
-
-### 2. Period Transition Logic
-
-The actual rate calculation in the contract has logic for:
-
-- If current period has ended: `rate = amount / periodLength`
-- If current period hasn't ended: May queue rewards or recalculate rate
-
-**For projection:** We assume the period has ended or will end, so we use the simple formula: `rate = totalRewards / periodLength`
-
-### 3. Pool Holdings
-
-The split between pools is based on **current** pool holdings. If deposits change before harvest, the split will change.
-
-### 4. Multiple Reward Tokens
-
-If there are multiple reward tokens, calculate APR for each and sum them:
-
-```typescript
-const activeRewardTokens = await stabilityPool.activeRewardTokens();
-let totalAPR = 0;
-
-for (const token of activeRewardTokens) {
- const apr = await getProjectedAPRNextPeriod(
- minter,
- stabilityPoolManager,
- stabilityPool,
- stabilityPoolCollateral,
- stabilityPoolLeveraged,
- token,
- userAddress,
- );
- totalAPR += apr;
-}
-```
-
-### 5. Edge Cases
-
-- **No harvestable:** Return 0 (no projected APR)
-- **No deposits:** Return 0 (can't calculate rate)
-- **Empty pool:** Return 0 (no rewards to distribute)
-
-## Display on Frontend
-
-```typescript
-// React hook example
-function useProjectedAPR(poolAddress: string, userAddress: string) {
- const [apr, setApr] = useState(null);
-
- useEffect(() => {
- async function fetchAPR() {
- const apr = await getProjectedAPRNextPeriod(
- minter,
- stabilityPoolManager,
- stabilityPool,
- stabilityPoolCollateral,
- stabilityPoolLeveraged,
- rewardToken,
- userAddress,
- );
- setApr(apr);
- }
-
- if (userAddress && poolAddress) {
- fetchAPR();
- // Refresh periodically or on block updates
- const interval = setInterval(fetchAPR, 30000);
- return () => clearInterval(interval);
- }
- }, [poolAddress, userAddress]);
-
- return apr;
-}
-```
-
-## Summary
-
-**What this calculates:**
-
-- APR for the **next reward period** that would start after a harvest
-- Based on **current harvestable amount** (projected to period end)
-- Assumes harvest happens at the **end of current 7-day period**
-- Projects the **next 7-day period** after that harvest
-
-**Perfect for:**
-
-- Launch scenarios where no harvests have happened yet
-- Showing users what APR to expect after the first harvest
-- Providing forward-looking projections based on current conditions
-
-**Key formula:**
-
-```
-1. Get remaining time until period end:
- - rewardData = stabilityPool.rewardData(token)
- - remainingSeconds = finishAt > currentTimestamp
- ? finishAt - currentTimestamp
- : REWARD_PERIOD_LENGTH
- - remainingDays = remainingSeconds / 86400
-
-2. Project additional yield for remaining days:
- - currentRate = wstETH.stEthPerToken()
- - underlyingCollateral = (balance - harvestable) * currentRate
- - projectedRate = currentRate * (1 + stakingAPR/365 * remainingDays)
- - currentValue = underlyingCollateral / currentRate
- - projectedValue = underlyingCollateral / projectedRate
- - additionalYield = currentValue - projectedValue
- - projectedHarvestable = currentHarvestable + additionalYield
-
-3. Calculate pool allocation:
- - harvestableRemaining = projectedHarvestable - bounty - cut
- - toThisPool = harvestableRemaining * (poolSize / totalPoolSize)
-
-4. Calculate reward rate:
- - totalRewards = toThisPool + queued
- - rate = totalRewards / 604800 (rewards per second)
-
-5. Project user rewards:
- - ratePerToken = rate / totalSupply
- - rewards7Days = ratePerToken * userBalance * 604800
-
-6. Calculate APR:
- - APR = (rewardsValue / depositValue) * (365/7) * 100
-```
-
-This gives users a projection of what APR they can expect after the next harvest!
-
-## Launch Scenario Example
-
-**At Launch:**
-
-- No harvests have happened yet
-- No rewards are currently being distributed
-- `harvestable()` shows some amount (e.g., 10 wstETH)
-- You want to show users: "If we harvest in 7 days, projected APR is X%"
-
-**Calculation:**
-
-1. Get current `harvestable()` = 10 wstETH
-2. Calculate: after bounty (5%) + cut (10%) = 8.5 wstETH to pools
-3. Split between pools based on current deposits
-4. Calculate rate for next 7-day period
-5. Project APR based on that rate
-
-**Result:** Users see "Projected APR: 12.5%" (or whatever the calculation yields)
-
-This helps users understand what to expect even before the first harvest happens!
diff --git a/doc/guides/FRONTEND-BASIC-CLAIM-DETAILED.md b/doc/guides/FRONTEND-BASIC-CLAIM-DETAILED.md
deleted file mode 100644
index baed2a9b..00000000
--- a/doc/guides/FRONTEND-BASIC-CLAIM-DETAILED.md
+++ /dev/null
@@ -1,549 +0,0 @@
-# Frontend: Basic Claim - Detailed Step-by-Step Guide
-
-This guide provides detailed instructions for implementing the basic claim functionality for stability pool rewards.
-
-## Important: Which Contract to Call
-
-**You call `claim()` directly on the Stability Pool contract itself.**
-
-- **NOT** on a separate rewards contract
-- **NOT** on the StabilityPoolManager
-- **YES** directly on the StabilityPool contract (e.g., Collateral Pool or Leveraged Pool)
-
-The Stability Pool contract implements `IMultipleRewardAccumulator`, which includes the `claim()` function.
-
-## Contract Addresses
-
-You need the addresses of your stability pools:
-
-```typescript
-// Example addresses (replace with your actual addresses)
-const COLLATERAL_POOL_ADDRESS = "0x..."; // Your collateral stability pool
-const LEVERAGED_POOL_ADDRESS = "0x..."; // Your leveraged stability pool
-```
-
-## Required ABI
-
-You need the `IMultipleRewardAccumulator` interface functions. Here's the minimal ABI:
-
-```typescript
-const STABILITY_POOL_REWARDS_ABI = [
- // Get active reward tokens
- "function activeRewardTokens() view returns (address[])",
-
- // Get claimable amount for a user and token
- "function claimable(address account, address token) view returns (uint256)",
-
- // Claim functions
- "function claim() external",
- "function claim(address account) external",
- "function claim(address account, address receiver) external",
-
- // Events
- "event Claim(address indexed account, address indexed token, address indexed receiver, uint256 amount)",
-] as const;
-```
-
-## Step 1: Check What's Claimable
-
-Before claiming, check what rewards are available:
-
-```typescript
-import { Contract, formatEther } from "ethers";
-
-async function checkClaimableRewards(
- poolAddress: string,
- userAddress: string,
- provider: any,
-): Promise<
- {
- token: string;
- symbol: string;
- amount: bigint;
- amountFormatted: string;
- }[]
-> {
- const pool = new Contract(poolAddress, STABILITY_POOL_REWARDS_ABI, provider);
-
- // Step 1: Get all active reward tokens
- const rewardTokens = await pool.activeRewardTokens();
- console.log("Active reward tokens:", rewardTokens);
-
- // Step 2: Check claimable amount for each token
- const claimableRewards = [];
-
- for (const tokenAddress of rewardTokens) {
- // Get claimable amount
- const claimable = await pool.claimable(userAddress, tokenAddress);
- console.log(`Token ${tokenAddress}: claimable = ${claimable.toString()}`);
-
- if (claimable > 0n) {
- // Get token symbol (optional, for display)
- const tokenContract = new Contract(tokenAddress, ["function symbol() view returns (string)"], provider);
- const symbol = await tokenContract.symbol();
-
- claimableRewards.push({
- token: tokenAddress,
- symbol,
- amount: claimable,
- amountFormatted: formatEther(claimable),
- });
- }
- }
-
- return claimableRewards;
-}
-```
-
-**Usage:**
-
-```typescript
-const rewards = await checkClaimableRewards(COLLATERAL_POOL_ADDRESS, userAddress, provider);
-
-console.log("Claimable rewards:", rewards);
-// Example output:
-// [
-// {
-// token: "0x0165878A594ca255338adfa4d48449f69242Eb8F",
-// symbol: "haPB",
-// amount: 123230000000000000000n,
-// amountFormatted: "123.23"
-// }
-// ]
-```
-
-## Step 2: Basic Claim - Simplest Form
-
-The simplest way to claim is to call `claim()` with no parameters. This claims all rewards for the connected wallet:
-
-```typescript
-import { Contract } from "ethers";
-
-async function claimRewardsSimple(
- poolAddress: string,
- signer: any, // Must be a signer, not a provider
-): Promise<{
- tx: any;
- receipt: any;
-}> {
- // Create contract instance with signer (for sending transactions)
- const pool = new Contract(poolAddress, STABILITY_POOL_REWARDS_ABI, signer);
-
- // Call claim() - claims all active reward tokens for the signer's address
- console.log("Calling claim() on pool:", poolAddress);
- const tx = await pool.claim();
-
- console.log("Transaction sent:", tx.hash);
-
- // Wait for confirmation
- const receipt = await tx.wait();
- console.log("Transaction confirmed:", receipt);
-
- return { tx, receipt };
-}
-```
-
-**Usage:**
-
-```typescript
-// Assuming you have a signer from wagmi or ethers
-const { data: signer } = useSigner();
-
-await claimRewardsSimple(COLLATERAL_POOL_ADDRESS, signer);
-```
-
-## Step 3: Claim with Specific Account
-
-If you want to claim for a specific account (must be the caller):
-
-```typescript
-async function claimRewardsForAccount(poolAddress: string, accountAddress: string, signer: any): Promise {
- const pool = new Contract(poolAddress, STABILITY_POOL_REWARDS_ABI, signer);
-
- // Claim for specific account (account must be the signer's address)
- const tx = await pool.claim(accountAddress);
- const receipt = await tx.wait();
-
- return receipt;
-}
-```
-
-## Step 4: Claim to Different Receiver
-
-If you want to claim rewards but send them to a different address:
-
-```typescript
-async function claimRewardsToReceiver(
- poolAddress: string,
- accountAddress: string, // Account that earned the rewards
- receiverAddress: string, // Address to receive the rewards
- signer: any,
-): Promise {
- const pool = new Contract(poolAddress, STABILITY_POOL_REWARDS_ABI, signer);
-
- // Claim for account and send to receiver
- // Note: accountAddress must be the signer's address (you can't claim for others to a different receiver)
- const tx = await pool.claim(accountAddress, receiverAddress);
- const receipt = await tx.wait();
-
- return receipt;
-}
-```
-
-## Step 5: Complete React Hook Example
-
-Here's a complete React hook using wagmi:
-
-```typescript
-import { useContractWrite, useWaitForTransaction, useAccount } from "wagmi";
-import { formatEther } from "ethers";
-
-export function useClaimRewards(poolAddress: string) {
- const { address } = useAccount();
-
- // Write function to claim rewards
- const {
- write: claim,
- data: claimData,
- isLoading: isClaiming,
- error: claimError,
- } = useContractWrite({
- address: poolAddress as `0x${string}`,
- abi: STABILITY_POOL_REWARDS_ABI,
- functionName: "claim",
- // No args - claims for the connected wallet
- });
-
- // Wait for transaction
- const {
- isLoading: isWaiting,
- isSuccess,
- error: waitError,
- } = useWaitForTransaction({
- hash: claimData?.hash,
- });
-
- return {
- claim,
- isClaiming: isClaiming || isWaiting,
- isSuccess,
- error: claimError || waitError,
- txHash: claimData?.hash,
- };
-}
-```
-
-**Usage in component:**
-
-```typescript
-function ClaimButton({ poolAddress, poolName }: { poolAddress: string; poolName: string }) {
- const { claim, isClaiming, isSuccess, error } = useClaimRewards(poolAddress);
-
- const handleClaim = () => {
- claim();
- };
-
- if (isSuccess) {
- return
✅ Rewards claimed successfully!
;
- }
-
- return (
-
- );
-}
-```
-
-## Step 6: Claim from Multiple Pools
-
-If you want to claim from multiple pools:
-
-```typescript
-async function claimFromMultiplePools(poolAddresses: string[], signer: any): Promise {
- const receipts = [];
-
- for (const poolAddress of poolAddresses) {
- const pool = new Contract(poolAddress, STABILITY_POOL_REWARDS_ABI, signer);
- const tx = await pool.claim();
- const receipt = await tx.wait();
- receipts.push(receipt);
- }
-
- return receipts;
-}
-```
-
-Or using Promise.all for parallel execution:
-
-```typescript
-async function claimFromMultiplePoolsParallel(poolAddresses: string[], signer: any): Promise {
- const pools = poolAddresses.map((address) => new Contract(address, STABILITY_POOL_REWARDS_ABI, signer));
-
- // Send all transactions
- const txs = await Promise.all(pools.map((pool) => pool.claim()));
-
- // Wait for all confirmations
- const receipts = await Promise.all(txs.map((tx) => tx.wait()));
-
- return receipts;
-}
-```
-
-## Step 7: Listen for Claim Events
-
-To verify rewards were claimed, listen for the `Claim` event:
-
-```typescript
-import { Contract } from "ethers";
-
-async function listenForClaimEvents(poolAddress: string, userAddress: string, provider: any): Promise {
- const pool = new Contract(poolAddress, STABILITY_POOL_REWARDS_ABI, provider);
-
- // Listen for Claim events
- pool.on("Claim", (account, token, receiver, amount, event) => {
- if (account.toLowerCase() === userAddress.toLowerCase()) {
- console.log("Rewards claimed!");
- console.log("Token:", token);
- console.log("Receiver:", receiver);
- console.log("Amount:", amount.toString());
- console.log("Event:", event);
- }
- });
-
- // To stop listening:
- // pool.removeAllListeners("Claim");
-}
-```
-
-Or query past events:
-
-```typescript
-async function getPastClaimEvents(
- poolAddress: string,
- userAddress: string,
- fromBlock: number,
- toBlock: number,
- provider: any,
-): Promise {
- const pool = new Contract(poolAddress, STABILITY_POOL_REWARDS_ABI, provider);
-
- const filter = pool.filters.Claim(userAddress);
- const events = await pool.queryFilter(filter, fromBlock, toBlock);
-
- return events;
-}
-```
-
-## Step 8: Verify Rewards After Claim
-
-After claiming, verify the rewards were received:
-
-```typescript
-import { Contract } from "ethers";
-
-async function verifyClaimedRewards(
- poolAddress: string,
- userAddress: string,
- rewardTokenAddress: string,
- provider: any,
-): Promise<{
- claimableBefore: bigint;
- claimableAfter: bigint;
- tokenBalanceBefore: bigint;
- tokenBalanceAfter: bigint;
-}> {
- const pool = new Contract(poolAddress, STABILITY_POOL_REWARDS_ABI, provider);
- const token = new Contract(rewardTokenAddress, ["function balanceOf(address) view returns (uint256)"], provider);
-
- // Check before
- const claimableBefore = await pool.claimable(userAddress, rewardTokenAddress);
- const tokenBalanceBefore = await token.balanceOf(userAddress);
-
- // ... perform claim ...
-
- // Check after
- const claimableAfter = await pool.claimable(userAddress, rewardTokenAddress);
- const tokenBalanceAfter = await token.balanceOf(userAddress);
-
- return {
- claimableBefore,
- claimableAfter,
- tokenBalanceBefore,
- tokenBalanceAfter,
- };
-}
-```
-
-## Common Issues and Solutions
-
-### Issue 1: "No claimable rewards"
-
-**Problem:** `claimable()` returns 0 for all tokens.
-
-**Solutions:**
-
-- Check if user has any deposits: `assetBalanceOf(userAddress)`
-- Check if rewards have been deposited to the pool
-- Check if rewards are still vesting (use `rewardData()` to see vesting period)
-- Verify you're checking the correct pool address
-
-### Issue 2: "Transaction reverted"
-
-**Problem:** Transaction fails when calling `claim()`.
-
-**Possible causes:**
-
-- No claimable rewards (check with `claimable()` first)
-- Wrong contract address
-- Wrong ABI (missing functions)
-- Network mismatch
-
-**Solution:**
-
-```typescript
-// Always check claimable first
-const claimable = await pool.claimable(userAddress, tokenAddress);
-if (claimable === 0n) {
- console.log("No rewards to claim");
- return;
-}
-
-// Then claim
-await pool.claim();
-```
-
-### Issue 3: "Wrong contract address"
-
-**Problem:** Calling claim on wrong contract.
-
-**Solution:**
-
-- Verify you're using the stability pool address, not the manager
-- Check your contract deployment addresses
-- Use `activeRewardTokens()` to verify - if it works, you have the right contract
-
-### Issue 4: "Insufficient gas"
-
-**Problem:** Transaction runs out of gas.
-
-**Solution:**
-
-- Estimate gas first: `const gasEstimate = await pool.claim.estimateGas();`
-- Add buffer: `const tx = await pool.claim({ gasLimit: gasEstimate * 120n / 100n });`
-
-## Complete Example: Full Claim Flow
-
-```typescript
-import { Contract, formatEther } from "ethers";
-
-interface ClaimResult {
- success: boolean;
- claimedTokens: Array<{
- token: string;
- symbol: string;
- amount: string;
- }>;
- error?: string;
-}
-
-async function claimRewardsComplete(
- poolAddress: string,
- userAddress: string,
- signer: any,
- provider: any,
-): Promise {
- try {
- const pool = new Contract(poolAddress, STABILITY_POOL_REWARDS_ABI, provider);
-
- // Step 1: Check what's claimable
- const rewardTokens = await pool.activeRewardTokens();
- const claimableBefore: Array<{ token: string; amount: bigint }> = [];
-
- for (const token of rewardTokens) {
- const amount = await pool.claimable(userAddress, token);
- if (amount > 0n) {
- claimableBefore.push({ token, amount });
- }
- }
-
- if (claimableBefore.length === 0) {
- return {
- success: false,
- claimedTokens: [],
- error: "No claimable rewards",
- };
- }
-
- // Step 2: Claim rewards
- const poolWithSigner = new Contract(poolAddress, STABILITY_POOL_REWARDS_ABI, signer);
- const tx = await poolWithSigner.claim();
- const receipt = await tx.wait();
-
- // Step 3: Verify claim
- const claimedTokens = [];
- for (const { token, amount } of claimableBefore) {
- const tokenContract = new Contract(token, ["function symbol() view returns (string)"], provider);
- const symbol = await tokenContract.symbol();
-
- claimedTokens.push({
- token,
- symbol,
- amount: formatEther(amount),
- });
- }
-
- return {
- success: true,
- claimedTokens,
- };
- } catch (error: any) {
- return {
- success: false,
- claimedTokens: [],
- error: error.message || "Unknown error",
- };
- }
-}
-```
-
-## Testing with cast (Command Line)
-
-You can test the claim function using `cast`:
-
-```bash
-# Check claimable amount
-cast call "claimable(address,address)(uint256)" --rpc-url http://localhost:8545
-
-# Claim rewards (requires private key or unlocked account)
-cast send "claim()" --private-key --rpc-url http://localhost:8545
-
-# Or with unlocked account
-cast send "claim()" --unlocked --rpc-url http://localhost:8545
-```
-
-## Summary
-
-**Key Points:**
-
-1. ✅ Call `claim()` **directly on the Stability Pool contract**
-2. ✅ Use the `IMultipleRewardAccumulator` ABI functions
-3. ✅ Check `claimable()` before claiming
-4. ✅ Use a **signer** (not provider) to send transactions
-5. ✅ Listen for `Claim` events to verify success
-
-**Function Signature:**
-
-```solidity
-function claim() external;
-```
-
-**What it does:**
-
-- Claims all active reward tokens for the caller
-- Sends rewards to the caller's address (or their `rewardReceiver` if set)
-- Updates internal reward tracking
-
-**No parameters needed** - just call `claim()` on the pool contract!
-
-
diff --git a/doc/guides/FRONTEND-CLAIM-AND-COMPOUND.md b/doc/guides/FRONTEND-CLAIM-AND-COMPOUND.md
deleted file mode 100644
index d15ad51f..00000000
--- a/doc/guides/FRONTEND-CLAIM-AND-COMPOUND.md
+++ /dev/null
@@ -1,986 +0,0 @@
-# Frontend Guide: Claim and Compound Rewards
-
-This guide explains how to implement the claim and compound functionality for stability pool rewards.
-
-## Overview
-
-Users can claim rewards from stability pools and choose to:
-
-1. **Basic Claim**: Receive rewards directly to wallet
-2. **Compound**: Automatically reinvest rewards back into stability pools
-3. **Buy $TIDE**: Acquire governance tokens (future feature)
-
-## 1. Claim Function Interface
-
-### Claim All Rewards
-
-```solidity
-// Claim all active reward tokens for the caller
-function claim() external;
-
-// Claim all active reward tokens for a specific account
-function claim(address account) external;
-
-// Claim all active reward tokens for account and send to receiver
-function claim(address account, address receiver) external;
-```
-
-### Claim Specific Tokens
-
-```solidity
-// Claim specific historical reward tokens
-function claimHistorical(address[] memory tokens) external;
-function claimHistorical(address account, address[] memory tokens) external;
-```
-
-## 2. Getting Claimable Rewards by Pool
-
-### Query All Pools for User
-
-```typescript
-interface PoolRewards {
- poolAddress: string;
- poolName: string;
- rewards: Array<{
- token: string;
- symbol: string;
- amount: bigint;
- amountFormatted: string;
- usdValue: number;
- }>;
- totalUSD: number;
-}
-
-async function getClaimableRewardsByPool(
- userAddress: string,
- pools: Array<{ address: string; name: string; type: "collateral" | "leveraged" }>,
- tokenPriceMap: Map,
-): Promise {
- const poolRewards: PoolRewards[] = [];
-
- for (const pool of pools) {
- const poolContract = new Contract(pool.address, STABILITY_POOL_ABI, provider);
-
- // Get active reward tokens
- const rewardTokens = await poolContract.activeRewardTokens();
-
- const rewards = await Promise.all(
- rewardTokens.map(async (token: string) => {
- const claimable = await poolContract.claimable(userAddress, token);
-
- if (claimable > 0n) {
- const tokenContract = new Contract(token, ERC20_ABI, provider);
- const symbol = await tokenContract.symbol();
- const price = tokenPriceMap.get(token.toLowerCase()) || 0;
- const amountFormatted = formatEther(claimable);
- const usdValue = parseFloat(amountFormatted) * price;
-
- return {
- token,
- symbol,
- amount: claimable,
- amountFormatted,
- usdValue,
- };
- }
- return null;
- }),
- );
-
- const validRewards = rewards.filter((r) => r !== null) as any[];
- const totalUSD = validRewards.reduce((sum, r) => sum + r.usdValue, 0);
-
- if (validRewards.length > 0) {
- poolRewards.push({
- poolAddress: pool.address,
- poolName: pool.name,
- rewards: validRewards,
- totalUSD,
- });
- }
- }
-
- return poolRewards;
-}
-```
-
-## 3. Basic Claim Implementation
-
-### Claim from Single Pool
-
-```typescript
-async function claimRewards(poolAddress: string, userAddress: string, receiver?: string): Promise {
- const pool = new Contract(poolAddress, STABILITY_POOL_ABI, signer);
-
- // Use receiver if provided, otherwise send to user
- const claimReceiver = receiver || userAddress;
-
- // Claim all active reward tokens
- const tx = await pool.claim(userAddress, claimReceiver);
- return tx;
-}
-```
-
-### Claim from Multiple Pools
-
-```typescript
-async function claimRewardsFromPools(
- poolAddresses: string[],
- userAddress: string,
- receiver?: string,
-): Promise {
- const claimReceiver = receiver || userAddress;
- const transactions: Promise[] = [];
-
- for (const poolAddress of poolAddresses) {
- const pool = new Contract(poolAddress, STABILITY_POOL_ABI, signer);
- transactions.push(pool.claim(userAddress, claimReceiver));
- }
-
- // Execute all claims (can be batched if needed)
- return Promise.all(transactions);
-}
-```
-
-## 4. Compound Implementation
-
-### Compound Flow Overview
-
-**For Collateral Tokens (wstETH):**
-
-1. Claim rewards (wstETH) to contract/temporary address
-2. Mint ha tokens using wstETH
-3. Deposit ha tokens to selected stability pool(s)
-
-**For ha Tokens:**
-
-1. Claim rewards (ha tokens) to contract/temporary address
-2. Deposit ha tokens directly to selected stability pool(s)
-
-### Step 1: Determine Reward Token Types
-
-```typescript
-interface RewardTokenInfo {
- token: string;
- symbol: string;
- amount: bigint;
- isCollateral: boolean; // true if wstETH, false if ha token
- isHaToken: boolean; // true if ha token
-}
-
-async function categorizeRewardTokens(
- rewards: PoolRewards[],
- wstETHAddress: string,
- haTokenAddress: string,
-): Promise<{
- collateralRewards: RewardTokenInfo[];
- haTokenRewards: RewardTokenInfo[];
- otherRewards: RewardTokenInfo[];
-}> {
- const collateralRewards: RewardTokenInfo[] = [];
- const haTokenRewards: RewardTokenInfo[] = [];
- const otherRewards: RewardTokenInfo[] = [];
-
- for (const pool of rewards) {
- for (const reward of pool.rewards) {
- const tokenLower = reward.token.toLowerCase();
- const isCollateral = tokenLower === wstETHAddress.toLowerCase();
- const isHaToken = tokenLower === haTokenAddress.toLowerCase();
-
- const info: RewardTokenInfo = {
- token: reward.token,
- symbol: reward.symbol,
- amount: reward.amount,
- isCollateral,
- isHaToken,
- };
-
- if (isCollateral) {
- collateralRewards.push(info);
- } else if (isHaToken) {
- haTokenRewards.push(info);
- } else {
- otherRewards.push(info);
- }
- }
- }
-
- return { collateralRewards, haTokenRewards, otherRewards };
-}
-```
-
-### Step 2: Get User's Active Pool Deposits
-
-```typescript
-async function getUserActivePools(
- userAddress: string,
- pools: Array<{ address: string; name: string; type: "collateral" | "leveraged" }>,
-): Promise> {
- const activePools = [];
-
- for (const pool of pools) {
- const poolContract = new Contract(pool.address, STABILITY_POOL_ABI, provider);
- const balance = await poolContract.assetBalanceOf(userAddress);
-
- if (balance > 0n) {
- activePools.push({
- address: pool.address,
- name: pool.name,
- type: pool.type,
- balance,
- });
- }
- }
-
- return activePools;
-}
-```
-
-### Step 3: Compound Collateral Tokens (wstETH)
-
-```typescript
-async function compoundCollateralRewards(
- poolAddress: string,
- userAddress: string,
- rewardAmount: bigint,
- targetPools: string[], // Pool addresses to compound into
- minterAddress: string,
- wstETHAddress: string,
- haTokenAddress: string,
-): Promise {
- const pool = new Contract(poolAddress, STABILITY_POOL_ABI, signer);
- const minter = new Contract(minterAddress, MINTER_ABI, signer);
- const wstETH = new Contract(wstETHAddress, ERC20_ABI, signer);
-
- // Step 1: Claim rewards to this contract (or use multicall)
- // For simplicity, we'll claim to a temporary address first
- // In production, you might use a compound helper contract
-
- // Option A: Use multicall to batch operations
- // Option B: Use a helper contract that handles the flow
- // Option C: Do it in separate transactions (simpler but more gas)
-
- // For this guide, we'll show the step-by-step approach:
-
- // 1. Claim rewards to user (or to compound helper contract)
- const claimTx = await pool.claim(userAddress, userAddress);
- await claimTx.wait();
-
- // 2. Approve minter to spend wstETH
- const approveTx = await wstETH.approve(minterAddress, rewardAmount);
- await approveTx.wait();
-
- // 3. Mint ha tokens with wstETH
- // Get expected ha tokens (for slippage protection)
- const { peggedOut } = await minter.mintPeggedTokenDryRun(rewardAmount);
- const minPeggedOut = (peggedOut * 95n) / 100n; // 5% slippage tolerance
-
- const mintTx = await minter.mintPeggedToken(
- rewardAmount,
- userAddress, // receiver of ha tokens
- minPeggedOut,
- );
- const mintReceipt = await mintTx.wait();
-
- // 4. Get actual ha tokens minted (from event or balance change)
- const haToken = new Contract(haTokenAddress, ERC20_ABI, provider);
- const haTokensMinted = await haToken.balanceOf(userAddress);
-
- // 5. Distribute ha tokens to selected pools
- const depositPromises = targetPools.map(async (targetPoolAddress) => {
- const targetPool = new Contract(targetPoolAddress, STABILITY_POOL_ABI, signer);
-
- // Calculate amount per pool (equal split, or user can specify)
- const amountPerPool = haTokensMinted / BigInt(targetPools.length);
-
- // Approve pool to spend ha tokens
- await haToken.approve(targetPoolAddress, amountPerPool);
-
- // Deposit to pool
- return targetPool.deposit(
- amountPerPool,
- userAddress, // receiver of shares
- amountPerPool, // minAmount (no slippage for direct deposit)
- );
- });
-
- const depositTxs = await Promise.all(depositPromises);
-
- // Return the last transaction (or you could return all)
- return depositTxs[depositTxs.length - 1];
-}
-```
-
-### Step 4: Compound ha Tokens
-
-```typescript
-async function compoundHaTokenRewards(
- poolAddress: string,
- userAddress: string,
- rewardAmount: bigint,
- targetPools: string[],
- haTokenAddress: string,
-): Promise {
- const pool = new Contract(poolAddress, STABILITY_POOL_ABI, signer);
- const haToken = new Contract(haTokenAddress, ERC20_ABI, signer);
-
- // 1. Claim ha token rewards
- const claimTx = await pool.claim(userAddress, userAddress);
- await claimTx.wait();
-
- // 2. Get actual ha tokens received (from balance change)
- const haTokensReceived = await haToken.balanceOf(userAddress);
-
- // 3. Distribute to selected pools
- const depositPromises = targetPools.map(async (targetPoolAddress) => {
- const targetPool = new Contract(targetPoolAddress, STABILITY_POOL_ABI, signer);
- const amountPerPool = haTokensReceived / BigInt(targetPools.length);
-
- await haToken.approve(targetPoolAddress, amountPerPool);
-
- return targetPool.deposit(amountPerPool, userAddress, amountPerPool);
- });
-
- const depositTxs = await Promise.all(depositPromises);
- return depositTxs[depositTxs.length - 1];
-}
-```
-
-### Step 5: Complete Compound Function
-
-```typescript
-interface CompoundOptions {
- selectedPools: string[]; // Pool addresses to claim from
- targetPools: string[]; // Pool addresses to compound into
- splitStrategy: "equal" | "proportional" | "custom";
- customSplit?: Map; // pool address -> percentage
-}
-
-async function compoundRewards(
- userAddress: string,
- options: CompoundOptions,
- wstETHAddress: string,
- haTokenAddress: string,
- minterAddress: string,
- allPools: Array<{ address: string; name: string; type: string }>,
-): Promise {
- const transactions: TransactionResponse[] = [];
-
- // 1. Get all claimable rewards from selected pools
- const poolRewards = await getClaimableRewardsByPool(
- userAddress,
- allPools.filter((p) => options.selectedPools.includes(p.address)),
- tokenPriceMap,
- );
-
- // 2. Categorize reward tokens
- const { collateralRewards, haTokenRewards, otherRewards } = await categorizeRewardTokens(
- poolRewards,
- wstETHAddress,
- haTokenAddress,
- );
-
- // 3. Handle other rewards (can't compound, must claim)
- if (otherRewards.length > 0) {
- // Claim other rewards to wallet (can't compound)
- for (const pool of poolRewards) {
- const poolContract = new Contract(pool.poolAddress, STABILITY_POOL_ABI, signer);
- const tx = await poolContract.claim(userAddress, userAddress);
- transactions.push(tx);
- }
- }
-
- // 4. Compound collateral rewards
- for (const reward of collateralRewards) {
- // Find which pool this reward came from
- const sourcePool = poolRewards.find((p) => p.rewards.some((r) => r.token === reward.token));
-
- if (sourcePool) {
- const tx = await compoundCollateralRewards(
- sourcePool.poolAddress,
- userAddress,
- reward.amount,
- options.targetPools,
- minterAddress,
- wstETHAddress,
- haTokenAddress,
- );
- transactions.push(tx);
- }
- }
-
- // 5. Compound ha token rewards
- for (const reward of haTokenRewards) {
- const sourcePool = poolRewards.find((p) => p.rewards.some((r) => r.token === reward.token));
-
- if (sourcePool) {
- const tx = await compoundHaTokenRewards(
- sourcePool.poolAddress,
- userAddress,
- reward.amount,
- options.targetPools,
- haTokenAddress,
- );
- transactions.push(tx);
- }
- }
-
- return transactions;
-}
-```
-
-## 5. React Hook Implementation
-
-### Complete Compound Hook
-
-```typescript
-import { useState, useCallback } from "react";
-import { Contract, TransactionResponse } from "ethers";
-
-interface UseCompoundRewards {
- compound: (options: CompoundOptions) => Promise;
- loading: boolean;
- error: string | null;
-}
-
-export function useCompoundRewards(
- userAddress: string | null,
- wstETHAddress: string,
- haTokenAddress: string,
- minterAddress: string,
- pools: Array<{ address: string; name: string; type: string }>,
-): UseCompoundRewards {
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
-
- const compound = useCallback(
- async (options: CompoundOptions) => {
- if (!userAddress) {
- setError("User not connected");
- return [];
- }
-
- setLoading(true);
- setError(null);
-
- try {
- const transactions = await compoundRewards(
- userAddress,
- options,
- wstETHAddress,
- haTokenAddress,
- minterAddress,
- pools,
- );
-
- // Wait for all transactions
- await Promise.all(transactions.map((tx) => tx.wait()));
-
- setLoading(false);
- return transactions;
- } catch (err: any) {
- setError(err.message || "Compound failed");
- setLoading(false);
- return [];
- }
- },
- [userAddress, wstETHAddress, haTokenAddress, minterAddress, pools],
- );
-
- return { compound, loading, error };
-}
-```
-
-## 6. UI Component Example
-
-### Claim Modal Component
-
-```typescript
-function ClaimRewardsModal({
- isOpen,
- onClose,
- poolRewards,
- userActivePools,
- onClaim,
- onCompound,
-}: {
- isOpen: boolean;
- onClose: () => void;
- poolRewards: PoolRewards[];
- userActivePools: Array<{ address: string; name: string; type: string }>;
- onClaim: (selectedPools: string[]) => Promise;
- onCompound: (options: CompoundOptions) => Promise;
-}) {
- const [selectedPools, setSelectedPools] = useState>(new Set());
- const [compoundMode, setCompoundMode] = useState<'basic' | 'compound' | 'tide'>('basic');
- const [targetPools, setTargetPools] = useState>(new Set());
-
- // Calculate total selected rewards
- const totalSelected = poolRewards
- .filter(p => selectedPools.has(p.poolAddress))
- .reduce((sum, p) => sum + p.totalUSD, 0);
-
- // Initialize: select all pools with rewards
- useEffect(() => {
- if (isOpen) {
- setSelectedPools(new Set(poolRewards.map(p => p.poolAddress)));
- // Default: compound into pools user is already in
- setTargetPools(new Set(userActivePools.map(p => p.address)));
- }
- }, [isOpen, poolRewards, userActivePools]);
-
- const handleClaim = async () => {
- await onClaim(Array.from(selectedPools));
- onClose();
- };
-
- const handleCompound = async () => {
- await onCompound({
- selectedPools: Array.from(selectedPools),
- targetPools: Array.from(targetPools),
- splitStrategy: 'equal',
- });
- onClose();
- };
-
- return (
-
-