OUSG Holders: Implement A Yield Distribution System

by SLV Team 52 views
OUSG Token Holders: Implementing a Yield Distribution System

Hey guys! Let's dive into a plan to implement a yield distribution system for all you OUSG token holders. This is all about making things transparent and user-friendly so you can see your earnings in real-time. Let's break it down.

Problem: Showing You Your Yield

Right now, if you're holding any amount of OUSG tokens—whether it's a tiny fraction or a huge chunk—you can't easily see the yield you've earned. There isn't a system in place to track and display individual yield based on how much OUSG you hold. This needs to change, and we are going to discuss how.

Understanding the Current Situation

Currently:

  • You mint OUSG tokens when you deposit ckBTC.
  • The OUSG price increases daily, reflecting yield from the Ethereum OUSG token.
  • There's no tracking of your initial purchase price compared to the current price.
  • You can't see your yield earned or your portfolio value with the added yield.
  • Basically, you're in the dark about your earnings until you decide to redeem your tokens.

What We Need to Do

To fix this, we need a system that does the following:

  1. Store User Holdings with Purchase Price: When you mint tokens, we need to record the amount, purchase price, purchase date, and initial investment.
  2. Daily Price Update: We need to fetch the OUSG price from Ethereum daily and store this price history.
  3. Real-Time Yield Calculation: On demand, calculate your current portfolio value, yield earned, yield percentage, and APY.
  4. User Dashboard Display: Show your current token balance, OUSG price, portfolio value, yield earned, and APY in an easy-to-understand format.

Detailed Requirements

Let's get into the nitty-gritty of what this system needs to do. Here's the breakdown:

1. Storing User Holdings

When you mint OUSG tokens, the system needs to capture specific details about your transaction. This includes:

  • Tokens Amount: The exact number of OUSG tokens you've minted. This allows precise calculation of your yield based on your holdings.
  • Purchase Price: The price of OUSG at the moment you minted the tokens. This is crucial for calculating your profit (or loss) over time.
  • Purchase Date: The timestamp of when you made the purchase. This helps in tracking the duration of your investment and calculating accurate APY figures.
  • Initial Investment: The USD value of your investment at the time of purchase. This serves as the baseline for determining the yield earned.

Additionally, the system should maintain a record of each user's purchase history, accommodating multiple minting events over time. This comprehensive tracking ensures accurate yield calculation, irrespective of how often you add to your OUSG holdings.

2. Daily Price Updates

To keep the yield calculations accurate, the system needs to fetch the OUSG price from Ethereum on a daily basis. This daily update is essential for reflecting the real-time yield earned from holding OUSG tokens. Here's how it should work:

  • Price Fetching: The system should automatically fetch the OUSG price from Ethereum once per day, specifically on business days between 4-6 pm ET. This ensures the price is up-to-date, reflecting market changes and yield accrual.
  • Price History: The fetched price should be stored in a price history log. This log should retain data for at least 30 days to facilitate accurate APY calculations. Historical data is vital for calculating the annualized percentage yield, providing users with insights into their investment's performance over time.
  • Global Price Tracker: The system should maintain a global price tracker that reflects the latest OUSG price. This tracker serves as the reference point for all yield calculations, ensuring consistency and accuracy across the platform.

3. Real-Time Yield Calculation

This is where the magic happens! The system needs to calculate your yield in real-time, providing you with an up-to-the-minute view of your earnings. Here's what needs to be calculated:

  • Current Portfolio Value: Calculated as tokens × current_price. This reflects the current market value of your OUSG holdings.
  • Yield Earned: Determined by subtracting your initial investment from the current portfolio value (current_value - initial_investment). This shows the profit you've made on your investment.
  • Yield Percentage: Calculated as (yield / initial_investment) × 100. This provides a percentage view of your earnings, making it easier to understand the return on your investment.
  • APY (Annual Percentage Yield): This is calculated from the price history, giving you an annualized view of your yield. It provides a standardized measure of investment return, considering the effects of compounding.

4. User Dashboard Display

Finally, all this information needs to be presented to you in an intuitive and user-friendly way. Your dashboard should display:

  • Current Token Balance: The total amount of OUSG tokens you currently hold.
  • Current OUSG Price: The latest price of OUSG, fetched from Ethereum.
  • Current Portfolio Value: The total value of your OUSG holdings, based on the current price.
  • Yield Earned: Both in USD and as a percentage, showing your profit from holding OUSG.
  • APY: Displayed as both a 7-day and 30-day average, providing insights into short-term and medium-term performance.

Implementation Details

Okay, let's peek under the hood and see how this might be implemented.

Code Structure

We'll need a new module in our backend:

New Module: src/backend/src/yield.rs

Here's some Rust code to get us started:

pub struct YieldTracker {
    pub current_ousg_price: f64,
    pub last_price_update: u64,
    pub price_history: Vec<PricePoint>,
}

pub struct PricePoint {
    pub price: f64,
    pub timestamp: u64,
}

pub struct UserHolding {
    pub user_principal: Principal,
    pub ousg_tokens: u64,           // In 18 decimals
    pub purchase_price: f64,         // OUSG price when purchased
    pub purchase_date: u64,          // Timestamp
    pub initial_investment_usd: f64, // USD value at purchase
}

pub struct UserYieldInfo {
    pub total_tokens: u64,
    pub current_price: f64,
    pub current_value_usd: f64,
    pub initial_investment_usd: f64,
    pub yield_earned_usd: f64,
    pub yield_percentage: f64,
    pub apy: f64,
}

Key Functions

Let's look at some key functions we'll need.

1. Store Holdings When Minting

// In notify_deposit(), after minting tokens:
fn store_user_holding(
    user: Principal,
    tokens_minted: u64,
    purchase_price: f64,
    initial_investment: f64,
) -> Result<()> {
    let holding = UserHolding {
        user_principal: user,
        ousg_tokens: tokens_minted,
        purchase_price,
        purchase_date: ic_cdk::api::time(),
        initial_investment_usd: initial_investment,
    };
    UserHoldingStorage::insert(holding)
}

2. Daily Price Update (Heartbeat)

#[ic_cdk::heartbeat]
pub async fn update_ousg_price_daily() {
    // Fetch OUSG price from Ethereum
    let new_price = fetch_ousg_price_from_ethereum().await?;
    
    // Update tracker
    let mut tracker = YieldTrackerStorage::get();
    tracker.add_price_point(new_price);
    tracker.current_ousg_price = new_price;
    tracker.last_price_update = ic_cdk::api::time();
    YieldTrackerStorage::update(tracker);
    
    // No need to loop through users - price update benefits everyone automatically!
}

3. Calculate User Yield (Query Function)

#[query]
pub fn get_user_yield_info(user_principal: Option<Principal>) -> Result<UserYieldInfo> {
    let principal = user_principal.unwrap_or_else(|| ic_cdk::api::msg_caller());
    
    // Get all holdings for user
    let holdings = UserHoldingStorage::get_by_user(&principal);
    
    // Get current price
    let current_price = YieldTrackerStorage::get().current_ousg_price;
    
    // Calculate totals
    let mut total_tokens = 0u64;
    let mut total_investment = 0.0;
    let mut total_current_value = 0.0;
    
    for holding in holdings {
        total_tokens += holding.ousg_tokens;
        total_investment += holding.initial_investment_usd;
        
        let tokens_f64 = holding.ousg_tokens as f64 / 1_000_000_000_000_000_000.0;
        total_current_value += tokens_f64 * current_price;
    }
    
    // Calculate yield
    let yield_earned = total_current_value - total_investment;
    let yield_percentage = if total_investment > 0.0 {
        (yield_earned / total_investment) * 100.0
    } else {
        0.0
    };
    
    // Calculate APY from price history
    let tracker = YieldTrackerStorage::get();
    let apy = tracker.calculate_apy();
    
    Ok(UserYieldInfo {
        total_tokens,
        current_price,
        current_value_usd: total_current_value,
        initial_investment_usd: total_investment,
        yield_earned_usd: yield_earned,
        yield_percentage,
        apy,
    })
}

How Yield Distribution Works

Key Insight: Yield is distributed automatically and proportionally to all holders when price updates. No separate token distribution needed!

Example:

3 Users with different holdings:
- User A: 44.20 tokens
- User B: 0.884 tokens
- User C: 0.00001 tokens

Price increases: $113.13 → $121.50 (7.4%)

User A: 44.20 × $121.50 = $5,370 (yield: $370)
User B: 0.884 × $121.50 = $107.40 (yield: $7.40)
User C: 0.00001 × $121.50 = $0.00122 (yield: $0.00008)

All get same 7.4% yield percentage!

Examples

Let's walk through a couple of examples.

Example 1: Single User Journey

Day 1: User deposits $5,000
- Gets: 44.20 OUSG @ $113.13
- Stored: purchase_price = $113.13, tokens = 44.20

Day 365: Price updated to $121.50
- User checks portfolio:
  - Current value: 44.20 × $121.50 = $5,370.30
  - Initial investment: $5,000.00
  - Yield earned: $370.30 (7.4%)
  - Displayed to user ✅

Example 2: Multiple Purchases

User makes 3 deposits:
- Deposit 1: 44.20 tokens @ $113.13 = $5,000
- Deposit 2: 17.68 tokens @ $115.00 = $2,034
- Deposit 3: 0.884 tokens @ $118.50 = $104.75

Total: 62.764 tokens, Initial: $7,138.75

Current price: $121.50
Current value: 62.764 × $121.50 = $7,625.83
Yield: $487.08 (6.8%)

Acceptance Criteria

To make sure we've nailed it, here's what we need to check off:

  • [ ] Create YieldTracker struct with price history
  • [ ] Create UserHolding struct to store purchase details
  • [ ] Store holdings when user mints tokens (in notify_deposit())
  • [ ] Implement daily heartbeat to update OUSG price
  • [ ] Fetch OUSG price from Ethereum (via EVM RPC)
  • [ ] Implement get_user_yield_info() query function
  • [ ] Calculate current portfolio value (tokens × current_price)
  • [ ] Calculate yield earned (current_value - initial_investment)
  • [ ] Calculate APY from 30-day price history
  • [ ] Store price history (30+ days) for APY calculation
  • [ ] Update redemption function to use current price (not fixed)
  • [ ] Frontend: Display yield info in user dashboard
  • [ ] Test with multiple users and different token amounts
  • [ ] Test with fractional amounts (0.00001 tokens)
  • [ ] Test APY calculation accuracy

Related Issues

Don't forget to check out these related issues:

  • #28: Fix OUSG Token Decimals Mismatch (must be 18 decimals)
  • #29: Implement balanceOf Function (for price fetching)
  • #30: Implement Balance Validation (prerequisite)

Benefits

Why is this a great idea?

  1. Automatic Distribution: No token transfers needed - price update benefits everyone
  2. Proportional Yield: Same % yield for all, different absolute amounts
  3. Real-Time Visibility: Users see yield earned anytime
  4. Works for Any Amount: 0.00001 to 10,000+ tokens - same mechanism
  5. Low Gas Costs: No daily token minting/transfers

Risk Level

🟡 MEDIUM - Important for user experience and trust, but not critical for core functionality.

Technical Notes

  • Yield is calculated on-demand when user queries, not stored per user
  • Single price update benefits all holders automatically
  • Purchase price stored for yield calculation
  • APY calculated from 30-day rolling average

Alright, that's the plan! Let's get this implemented and make it super easy for you to see your OUSG yield in real-time. Stay tuned for updates, and feel free to drop any questions or suggestions!