<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Miracle656</title>
    <description>The latest articles on DEV Community by Miracle656 (@miracle656).</description>
    <link>https://dev.to/miracle656</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F974547%2F783fc96a-5163-4875-aac4-10295b6f77c4.jpeg</url>
      <title>DEV Community: Miracle656</title>
      <link>https://dev.to/miracle656</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/miracle656"/>
    <language>en</language>
    <item>
      <title>Octant v2 Developer Tutorial: Building Yield-Donating Strategies</title>
      <dc:creator>Miracle656</dc:creator>
      <pubDate>Sat, 08 Nov 2025 21:56:32 +0000</pubDate>
      <link>https://dev.to/miracle656/octant-v2-developer-tutorial-building-yield-donating-strategies-6o1</link>
      <guid>https://dev.to/miracle656/octant-v2-developer-tutorial-building-yield-donating-strategies-6o1</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;🎯 What You'll Learn&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;By the end of this tutorial, you'll understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How Octant v2 transforms yield into sustainable public goods funding&lt;/li&gt;
&lt;li&gt;The architecture of yield-donating strategies&lt;/li&gt;
&lt;li&gt;How to build and deploy your own strategy&lt;/li&gt;
&lt;li&gt;Real-world examples with code walkthroughs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Part 1: Understanding Octant v2&lt;/strong&gt;&lt;br&gt;
The Problem Octant Solves&lt;br&gt;
Traditional funding models require either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Depleting treasuries (unsustainable)&lt;/li&gt;
&lt;li&gt;Taking from users (creates friction)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Octant's Innovation:&lt;/strong&gt; Use ONLY the yield, keep the principal forever!&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;The Flow&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;User deposits 10,000 USDC&lt;br&gt;
    ↓&lt;br&gt;
Strategy invests in Aave (earning 5% APY)&lt;br&gt;
    ↓&lt;br&gt;
After 1 year: 500 USDC yield generated&lt;br&gt;
    ↓&lt;br&gt;
User still has 10,000 USDC (can withdraw anytime)&lt;br&gt;
500 USDC goes to public goods as donation shares&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Key Concept: Donation Shares&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;User gets shares representing their PRINCIPAL&lt;/li&gt;
&lt;li&gt;Strategy mints NEW shares for YIELD&lt;/li&gt;
&lt;li&gt;Yield shares go to dragonRouter (donation address)&lt;/li&gt;
&lt;li&gt;This is like giving interest to charity while keeping your savings account balance!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Part 2: Architecture Deep Dive&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Core Components&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. YieldDonatingTokenizedStrategy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The base contract you inherit from. Key functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// YOU IMPLEMENT THESE:
function _deployFunds(uint256 _amount) internal virtual;
function _freeFunds(uint256 _amount) internal virtual;
function _harvestAndReport() internal virtual returns (uint256);

// OCTANT HANDLES THESE:
function report() external onlyKeepers returns (uint256 profit, uint256 loss);
// ↑ This mints profit shares to dragonRouter automatically!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. The Report Mechanism&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// When keeper calls report():
1. Call _harvestAndReport() to get current totalAssets
2. Compare with previous totalAssets
3. If profit: mint shares to dragonRouter
4. If loss (optional): burn dragonRouter shares
5. Update accounting

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Factory Pattern&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract SkyCompounderStrategyFactory {
    function createStrategy(
        address _compounderVault,  // Yield source
        string memory _name,
        address _management,
        address _keeper,
        address _emergencyAdmin,
        address _donationAddress,  // Where yield goes!
        bool _enableBurning,
        address _tokenizedStrategyAddress
    ) external returns (address strategy);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Part 3: Building Your First Strategy&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Example: Aave v3 USDC Strategy&lt;/strong&gt;&lt;br&gt;
Let's build a strategy that deposits USDC into Aave and donates the yield!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Setup Your Interface&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.18;

interface IAaveV3Pool {
    function supply(
        address asset,
        uint256 amount,
        address onBehalfOf,
        uint16 referralCode
    ) external;

    function withdraw(
        address asset,
        uint256 amount,
        address to
    ) external returns (uint256);
}

interface IAToken {
    function balanceOf(address account) external view returns (uint256);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Implement Your Strategy&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {YieldDonatingTokenizedStrategy} from "./YieldDonatingTokenizedStrategy.sol";

contract AaveUsdcYieldDonating is YieldDonatingTokenizedStrategy {
    IAaveV3Pool public immutable aavePool;
    IAToken public immutable aUsdc;

    constructor(
        address _aavePool,
        address _aUsdc,
        address _asset,
        string memory _name,
        address _management,
        address _keeper,
        address _emergencyAdmin,
        address _donationAddress,
        bool _enableBurning,
        address _tokenizedStrategyAddress
    ) YieldDonatingTokenizedStrategy(
        _asset,
        _name,
        _management,
        _keeper,
        _emergencyAdmin,
        _donationAddress,
        _enableBurning,
        _tokenizedStrategyAddress
    ) {
        aavePool = IAaveV3Pool(_aavePool);
        aUsdc = IAToken(_aUsdc);

        // Approve Aave to spend our USDC
        ERC20(_asset).approve(_aavePool, type(uint256).max);
    }

    // REQUIRED: Deploy funds into Aave
    function _deployFunds(uint256 _amount) internal override {
        aavePool.supply(
            address(asset),
            _amount,
            address(this),
            0 // no referral code
        );
    }

    // REQUIRED: Withdraw funds from Aave
    function _freeFunds(uint256 _amount) internal override {
        aavePool.withdraw(
            address(asset),
            _amount,
            address(this)
        );
    }

    // REQUIRED: Calculate total assets
    function _harvestAndReport() internal override returns (uint256 _totalAssets) {
        // Assets in Aave (aUSDC represents our deposit + yield)
        uint256 aaveBalance = aUsdc.balanceOf(address(this));

        // Idle assets in strategy
        uint256 idleBalance = asset.balanceOf(address(this));

        // Total = deployed + idle
        _totalAssets = aaveBalance + idleBalance;

        // NOTE: Profit is calculated automatically!
        // If _totalAssets &amp;gt; lastTotalAssets, profit is minted to dragonRouter
    }

    // OPTIONAL: Set deposit limit based on Aave capacity
    function availableDepositLimit(address) public view override returns (uint256) {
        // Could check Aave's supply cap here
        return type(uint256).max; // unlimited for this example
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Understanding the Magic&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Initial State
deposit(10000 USDC) → user gets 10000 shares
totalAssets = 10000 USDC

// After 1 day (earned 10 USDC in Aave)
keeper.report() calls _harvestAndReport()
    → returns 10010 USDC
    → profit = 10010 - 10000 = 10 USDC
    → mint 10 shares to dragonRouter
    → user still has 10000 shares (unchanged!)
    → dragonRouter now has 10 shares

// User can still withdraw their full 10000 USDC
// dragonRouter has 10 shares worth 10 USDC to donate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Part 4: Testing Your Strategy&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Setup Test Environment&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// test/AaveStrategy.t.sol
pragma solidity 0.8.18;

import "forge-std/Test.sol";
import {AaveUsdcYieldDonating} from "../src/AaveStrategy.sol";

contract AaveStrategyTest is Test {
    AaveUsdcYieldDonating strategy;
    address user = address(0x1);
    address dragonRouter = address(0x2);

    function setUp() public {
        // Fork mainnet for real Aave interaction
        vm.createSelectFork(vm.envString("ETH_RPC_URL"));

        strategy = new AaveUsdcYieldDonating(
            0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2, // Aave V3 Pool
            0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c, // aUSDC
            0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, // USDC
            "Aave USDC YieldDonating",
            address(this), // management
            address(this), // keeper
            address(this), // emergency admin
            dragonRouter,
            false, // no burning
            TOKENIZED_STRATEGY_ADDRESS
        );
    }

    function testYieldDonation() public {
        // 1. User deposits
        deal(USDC, user, 10000e6);
        vm.startPrank(user);
        IERC20(USDC).approve(address(strategy), 10000e6);
        strategy.deposit(10000e6, user);
        vm.stopPrank();

        // 2. Simulate time passing + yield accrual
        vm.warp(block.timestamp + 365 days);

        // 3. Keeper reports (this mints profit to dragonRouter)
        (uint256 profit, ) = strategy.report();

        // 4. Verify
        assertGt(profit, 0, "Should have generated profit");
        assertGt(strategy.balanceOf(dragonRouter), 0, "Dragon should have shares");
        assertEq(strategy.balanceOf(user), 10000e6, "User shares unchanged");
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Run Tests&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;forge test --match-contract AaveStrategyTest -vv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;*&lt;em&gt;## Part 5: Deployment *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create Deployment Script&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// script/DeployAaveStrategy.s.sol
pragma solidity 0.8.18;

import "forge-std/Script.sol";
import {AaveUsdcYieldDonating} from "../src/AaveStrategy.sol";

contract DeployAaveStrategy is Script {
    function run() external {
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        address dragonRouter = vm.envAddress("DRAGON_ROUTER");

        vm.startBroadcast(deployerPrivateKey);

        AaveUsdcYieldDonating strategy = new AaveUsdcYieldDonating(
            0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2, // Aave V3
            0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c, // aUSDC
            0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, // USDC
            "Aave USDC YieldDonating",
            msg.sender,
            msg.sender,
            msg.sender,
            dragonRouter,
            false,
            TOKENIZED_STRATEGY_ADDRESS
        );

        console.log("Strategy deployed at:", address(strategy));

        vm.stopBroadcast();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Deploy&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;forge script script/DeployAaveStrategy.s.sol \
    --rpc-url $ETH_RPC_URL \
    --broadcast \
    --verify \
    --etherscan-api-key $ETHERSCAN_API_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Part 6: Interacting with Your Strategy&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Using ethers.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ethers } from 'ethers';

const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const strategy = new ethers.Contract(STRATEGY_ADDRESS, STRATEGY_ABI, provider);

// 1. Check strategy info
const totalAssets = await strategy.totalAssets();
const dragonRouter = await strategy.dragonRouter();
const pricePerShare = await strategy.pricePerShare();

console.log(`TVL: ${ethers.utils.formatUnits(totalAssets, 6)} USDC`);

// 2. Deposit (as user)
const signer = new ethers.Wallet(PRIVATE_KEY, provider);
const strategyWithSigner = strategy.connect(signer);

const usdc = new ethers.Contract(USDC_ADDRESS, ERC20_ABI, signer);
await usdc.approve(STRATEGY_ADDRESS, ethers.utils.parseUnits("1000", 6));
await strategyWithSigner.deposit(ethers.utils.parseUnits("1000", 6), signer.address);

// 3. Report yield (as keeper)
const tx = await strategyWithSigner.report();
const receipt = await tx.wait();
console.log(`Yield reported! Gas used: ${receipt.gasUsed}`);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  *&lt;em&gt;Part 7: Advanced Patterns *&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Multi-Protocol Strategy&lt;/strong&gt;&lt;br&gt;
Deploy across multiple yield sources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract MultiProtocolStrategy is YieldDonatingTokenizedStrategy {
    IAaveV3Pool public aave;
    ICompound public compound;

    // Split deposits 50/50
    function _deployFunds(uint256 _amount) internal override {
        uint256 half = _amount / 2;
        aave.supply(address(asset), half, address(this), 0);
        compound.supply(address(asset), half);
    }

    function _harvestAndReport() internal override returns (uint256) {
        return aave.balanceOf(address(this)) 
             + compound.balanceOf(address(this))
             + asset.balanceOf(address(this));
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Auto-Rebalancing Strategy&lt;/strong&gt;&lt;br&gt;
Move funds to highest yield:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function _tend(uint256 _totalIdle) internal override {
    uint256 aaveApy = getAaveApy();
    uint256 compoundApy = getCompoundApy();

    if (aaveApy &amp;gt; compoundApy * 101 / 100) { // 1% threshold
        // Move from Compound to Aave
        uint256 compoundBalance = compound.balanceOf(address(this));
        compound.withdraw(compoundBalance);
        aave.supply(address(asset), compoundBalance, address(this), 0);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  *&lt;em&gt;Part 8: Common Pitfalls *&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;❌ Mistake 1: Forgetting Idle Assets&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// WRONG
function _harvestAndReport() internal override returns (uint256) {
    return aave.balanceOf(address(this)); // Missing idle!
}

// CORRECT
function _harvestAndReport() internal override returns (uint256) {
    return aave.balanceOf(address(this)) 
         + asset.balanceOf(address(this)); // Include idle
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;❌ Mistake 2: Not Approving Tokens&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Add in constructor:
ERC20(_asset).approve(_yieldSource, type(uint256).max);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;❌ Mistake 3: Wrong Share Accounting&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Octant handles share minting automatically in report()
// DON'T manually mint shares for profit!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Part 9: Resources &amp;amp; Next Steps&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Documentation&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Octant Docs: &lt;a href="https://docs.v2.octant.build" rel="noopener noreferrer"&gt;https://docs.v2.octant.build&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Yearn v3 (similar architecture): &lt;a href="https://docs.yearn.fi/developers/v3/overview" rel="noopener noreferrer"&gt;https://docs.yearn.fi/developers/v3/overview&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example Strategies&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sky Compounder: See SkyCompounderStrategy.sol&lt;/li&gt;
&lt;li&gt;Lido Strategy: See LidoStrategy.sol&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Community&lt;/p&gt;

&lt;p&gt;Discord: [&lt;a href="https://discord.gg/octant" rel="noopener noreferrer"&gt;https://discord.gg/octant&lt;/a&gt;]&lt;br&gt;
GitHub: &lt;a href="https://github.com/golemfoundation/octant-v2-core" rel="noopener noreferrer"&gt;https://github.com/golemfoundation/octant-v2-core&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Challenge Yourself&lt;br&gt;
Try building strategies for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Morpho Blue&lt;/li&gt;
&lt;li&gt;Spark Protocol&lt;/li&gt;
&lt;li&gt;Compound v3&lt;/li&gt;
&lt;li&gt;Uniswap v4 hooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Summary Checklist&lt;/strong&gt;&lt;br&gt;
✅ Understand yield donation concept&lt;br&gt;
✅ Know the 3 required functions to implement&lt;br&gt;
✅ Can test strategies with Foundry&lt;br&gt;
✅ Can deploy strategies to mainnet&lt;br&gt;
✅ Understand profit minting mechanism&lt;br&gt;
✅ Know common pitfalls to avoid&lt;br&gt;
*&lt;em&gt;You're now ready to build production-grade yield-donating strategies for Octant v2!&lt;br&gt;
*&lt;/em&gt;&lt;/p&gt;

</description>
      <category>blockchain</category>
    </item>
  </channel>
</rss>
