Overview

Two contracts: RGTToken (basic ERC20) and AssetManager (the main logic). Users deposit RGT tokens, get "assets" in return, and earn 0.1 RWT per asset per day.

Contract Addresses

RGT_TOKEN_ADDRESS: 0x...
ASSET_MANAGER_ADDRESS: 0x...
REWARD_TOKEN_ADDRESS: 0x...

RGTToken Contract

mint()

Daily faucet — anyone can grab 100 RGT every 24 hours.

  • Cooldown is per wallet
  • UI: RGT Token tab → "Daily Mint"
function mint() public {
    require(block.timestamp >= lastMintTime[msg.sender] + 1 days, "Wait 24 hours");
    _mint(msg.sender, 100 * 10**decimals());
}

adminMint(address to, uint256 amount)

Owner can mint any amount to any address for testing.

function adminMint(address to, uint256 amount) public {
    require(msg.sender == owner, "Only owner");
    _mint(to, amount);
}

AssetManager Contract

deposit(uint256 tokenAmount, address assetholder)

Allows users to deposit RGT tokens and receive corresponding asset units (10 tokens = 1 asset). Tokens are transferred to the contract and converted to tracked assets.

Reasoning: The function enforces deposits in multiples of 10 tokens to standardize asset units and simplify reward calculation
(1 asset = 10 tokens). This avoids fractional assets and ensures predictable behavior. Using transferFrom also ensures proper token custody by verifying that the user has approved the contract to spend the tokens on their behalf. daily.

  • Must be multiples of 10 tokens
  • User must approve the contract first
  • Creates asset record if first deposit
function deposit(uint256 tokenAmount, address assetholder) public {
    // Must be at least 10 tokens and multiple of 10
    require(tokenAmount >= (10 * 1e18), "invalid amount");
    require(tokenAmount % (10 * 1e18) == 0, "Amount should be multiples of 10");
    
    // Transfer tokens from user to contract
    require(rgtToken.transferFrom(msg.sender, address(this), tokenAmount), "Transfer failed");
    
    // Convert to assets: 10 tokens = 1 asset
    uint256 assetAmount = tokenAmount / (10 * 1e18);
    createAsset(assetholder, assetAmount);
}

claimReward()

Allows users to claim accumulated reward tokens based on their deposited assets and the time elapsed.

Reasoning: Before claiming, the function calls rewardCalculator to ensure rewards are up to date based on the latest time difference. It checks if the user has a non-zero claimable balance and ensures that the contract has enough reward tokens to fulfill the claim. Resetting claimableAmount before the transfer prevents reentrancy risks. This logic ensures fairness, avoids overclaims, and maintains the contract’s internal accounting integrity.

function claimReward() external {
    // Calculate and update rewards first
    rewardCalculator(msg.sender);
    
    uint256 claimable = assetStorage[msg.sender].claimableAmount;
    require(claimable > 0, "Nothing to claim");
    
    // Check if pool has enough balance
    uint256 rewardBalance = rewardToken.balanceOf(address(this));
    require(rewardBalance >= claimable, "Insufficient pool");
    
    // Reset claimable and transfer
    assetStorage[msg.sender].claimableAmount = 0;
    rewardToken.transfer(msg.sender, claimable);
}

pendingRewards(address holder)

This shows users the amount of RWT tokens they have acquired overtime.

Reasoning: This function is to help users know how much they have without them having to interact with the contract. Without it users won't know how much they have until they are ready to claim.

function pendingRewards(address holder) public view returns (uint256) {
    uint256 last = assetStorage[holder].lastRewardTime;
    if (block.timestamp < last + 1 days) return 0; // Too early
    
    uint256 daysPassed = (block.timestamp - last) / 1 days;
    return assetStorage[holder].totalAssets * daysPassed * 0.1 ether;
}

nextClaimTime(address user)

This shows when next a user can claim after they have claimed all the claimable rewards.

Reasoning: This helps the user track the next time they can claim i.e if they want to be consitent with daily claims. It also takes away the confusion of not know what is going on in the background and helps build user trust.

function nextClaimTime(address user) public view returns (uint256) {
    uint256 last = assetStorage[user].lastRewardTime;
    if (last == 0) return 0; // Never deposited
    return last + 1 days;
}

isFirstDeposit(address user)

Check if user has any assets or their first time interacting with contract succesfully.

Reasoning: This function was introduced to help improve the user experience in providing detailed information on what to do and expect as a first time user.

function isFirstDeposit(address user) public view returns (bool) {
    return assetStorage[user].totalAssets == 0;
}

withdrawPool(uint256 amount)

Allows the contract owner to withdraw a specific amount of tokens from the reward pool.

Reasoning: This function is useful for managing excess or unused rewards in the pool, allowing the owner to reclaim unused tokens.

function withdrawPool(uint256 amount) external onlyOwner {
            uint256 rewardBalance = rewardToken.balanceOf(address(this));
            require(rewardBalance >= amount, "Insufficient balance");

            bool success = rewardToken.transfer(msg.sender, amount);
            require(success, "Withdraw failed");

            emit PoolWithdrawn(amount);
        }

withdrawDeposits(address to, uint256 amount)

Allows the owner to withdraw a specific amount of RGT tokens that were deposited by users.

Reasoning: In case tokens need to be migrated, refunded, or repurposed, this function provides controlled access for withdrawal by the owner.

function withdrawDeposits(address to, uint256 amount) external onlyOwner {
            require(rgtToken.balanceOf(address(this)) >= amount, "Insufficient balance");
            rgtToken.transfer(to, amount);
        }

emergencyWithdrawAllDeposits(address to)

Allows the owner to withdraw all RGT tokens from the contract in case of an emergency.

Reasoning: This function is designed for safety and recovery purposes. In case of critical failure or shutdown, the owner can recover all user deposits from the contract.

function emergencyWithdrawAllDeposits(address to) external onlyOwner {
            uint256 balance = rgtToken.balanceOf(address(this));
            require(balance > 0, "Nothing to withdraw");
            rgtToken.transfer(to, balance);
        }

fundRewardPool(uint256 amount)

Admin uses this function to deposit poolRewards in the contract.

function fundRewardPool(uint256 amount) external onlyOwner {
    require(amount > 0, "Amount must be > 0");
    // Transfer RWT from owner to contract
    require(rewardToken.transferFrom(msg.sender, address(this), amount), "Funding failed");
}

Testing

Quick Test Flow

  1. Connect wallet (Avalanche Fuji testnet)
  2. Mint 100 RGT from the faucet
  3. Go to Asset Manager, approve 20 RGT
  4. Deposit 20 RGT (gets you 2 assets)
  5. Wait 24 hours
  6. Check pending rewards (should be 0.2 RWT after 1 day)
  7. Claim rewards

Owner Testing

  • Admin mint shows up in RGT Token tab
  • Reward pool funding shows up in Asset Manager tab
  • Fund the pool first, otherwise claims will fail

⚠️ Common Issues

Deposit Problems

  • Make sure to approve RGT tokens first before depositing (two-step process)
  • Only amounts in multiples of 10 (like 10, 20, 50…) are accepted.
  • Check that you have enough RGT balance before depositing.

Claim Problems

  • You need to wait 24 hours after your first deposit or last claim.
  • If the reward pool is empty, the owner must fund it first.
  • If you have never deposited, nextClaimTime will show 0

How Rewards Work

// Every day, each asset earns 0.1 RWT
daysPassed = (now - lastRewardTime) / 1 day
reward = totalAssets * daysPassed * 0.1

// Example: 5 assets, 3 days passed
reward = 5 * 3 * 0.1 = 1.5 RWT

The contract tracks when you last claimed, calculates days since then, and gives you that many days worth of rewards.

Asset Struct

struct Asset {
    address holder;           // Who owns this
    uint256 totalAssets;      // How many asset units they have
    uint256 lastRewardTime;   // When rewards were last calculated
    uint256 lastClaimed;      // When they last claimed (unused currently)
    uint256 claimableAmount;  // Rewards ready to claim
}