Automated Rebalancing

How AutoRebalancer proves all three pillars: ZK Privacy, Verifiable AI, and DAO Constraints working together in production.

The Complete Flow

The AutoRebalancer is where everything comes together. It demonstrates how AI-driven decisions can be:

  • Verifiable — Every recommendation is committed on-chain before execution
  • DAO-Constrained — All allocations must pass governance rules
  • Privacy-Preserving — Pool-level rebalancing doesn't expose individual positions

The 4-Step Verification Flow

1

Trigger Detection (On-Chain)

AutoRebalancer monitors on-chain conditions every block:

function checkUpkeep(bytes calldata) 
    external view returns (bool upkeepNeeded, bytes memory performData) 
{
    // 1. Check cooldown (5 blocks for testnet, 100+ for production)
    if (block.number < lastRebalanceBlock + MIN_BLOCKS_BETWEEN_REBALANCE) {
        return (false, "Cooldown active");
    }
    
    // 2. Check allocation drift (>5% from target)
    for (protocol in protocols) {
        uint256 drift = calculateDrift(lastAllocation, currentAllocation);
        if (drift > 500) return (true, "Allocation drift detected");
    }
    
    // 3. Check risk score changes (>10 points)
    for (protocol in protocols) {
        uint256 riskChange = abs(currentRisk - lastRisk);
        if (riskChange > 10) return (true, "Risk score changed");
    }
    
    // 4. Check APY differentials (>2% spread)
    if (maxAPY - minAPY > 200) return (true, "APY differential detected");
    
    return (false, "No rebalance needed");
}
What This Proves:

Rebalancing is triggered by objective on-chain conditions, not arbitrary decisions. Anyone can verify the trigger was legitimate by checking the same on-chain data.

2

AI Analysis & Commitment (Verifiable AI)

When conditions are met, AutoRebalancer generates an AI recommendation and commits it on-chain before execution:

function performUpkeep(bytes calldata performData) external {
    // Re-verify conditions (security critical)
    (bool upkeepNeeded, ) = this.checkUpkeep("");
    require(upkeepNeeded, "Upkeep not needed");
    
    // Generate AI recommendation based on risk scores
    (uint256 aaveAlloc, uint256 lidoAlloc, uint256 compAlloc) = 
        _generateRecommendation(protocols, percentages);
    
    // COMMIT FIRST (before execution)
    bytes32 commitmentHash = keccak256(abi.encode(
        aaveAlloc, lidoAlloc, compAlloc, reason, block.timestamp
    ));
    uint256 recId = aiCommitment.commitRecommendation(commitmentHash);
    
    // Then execute...
}
🔐 What This Proves:

The AI commits to its recommendation before execution. The commitment hash is stored on-chain and can't be changed later. This creates an immutable audit trail of what the AI decided and when.

3

DAO Governance Check (DAO Constraints)

Before execution, the allocation must pass DAO-defined constraints:

function updateAllocationWeightsWithAI(...) external onlyOwner {
    require(_aaveWeight + _lidoWeight + _compoundWeight == 10000, "Must total 100%");
    
    // Verify AI recommendation matches commitment
    bool verified = aiCommitment.verifyRecommendation(
        recommendationId, _aaveWeight, _lidoWeight, _compoundWeight, reason
    );
    require(verified, "Allocation does not match AI recommendation");
    
    // Check DAO constraints (happens in verifyRecommendation)
    // - Max single protocol: 50%
    // - Min diversification: 3 protocols
    // - Risk ceiling: Score < 70
    
    // Only if all checks pass, update weights
    aaveWeight = _aaveWeight;
    lidoWeight = _lidoWeight;
    compoundWeight = _compoundWeight;
    
    emit AIVerifiedAllocation(recommendationId, ...);
}
⚖️ What This Proves:

The AI cannot bypass DAO governance rules. Even if the AI generates a recommendation, it will be rejected on-chain if it violates constraints like "no single protocol > 50%". The DAO stays in control.

4

Execution & Privacy (ZK Privacy Pools)

Once verified, the rebalance executes at the pool level, preserving individual privacy:

// Pool-level rebalancing (aggregate capital)
strategyRouter.rebalance(
    ["aave"],           // Withdraw from Aave
    ["lido"],           // Deposit to Lido  
    [amountToMove]      // Move X wETH
);

// What's visible on-chain:
✅ Pool moved 10 wETH from Aave to Lido
✅ New allocation: 35% Aave, 40% Lido, 25% Compound
✅ AI Recommendation #17 was verified and executed

// What's NOT visible:
❌ Individual user positions (hidden via ZK commitments)
❌ Which users deposited how much
❌ Who will withdraw next (unlinkable addresses)
🔒 What This Proves:

The rebalance operates on aggregate pool capital, not individual positions. Users maintain privacy through ZK commitments while benefiting from AI-driven optimization. The AI sees the forest, not the trees.

Why This Flow Matters

🚫
Without This System
  • • AI is a black box
  • • No audit trail
  • • DAO has no control
  • • Everyone sees your moves
⚠️
Partial Solutions
  • • Manual multisig (too slow)
  • • Trusted AI service (opaque)
  • • Privacy pools (no AI)
  • • Public AI agents (no privacy)
Obsqra.fi Solution
  • • AI commits before execution
  • • DAO rules enforced on-chain
  • • Full audit trail
  • • Individual privacy preserved

Technical Implementation

Commit-Reveal Pattern

The AI uses a cryptographic commit-reveal pattern to ensure recommendations can't be changed after the fact:

// Step 1: Commit (before execution)
bytes32 commitmentHash = keccak256(abi.encode(
    aaveAllocation,      // e.g., 4500 (45%)
    lidoAllocation,      // e.g., 3000 (30%)
    compoundAllocation,  // e.g., 2500 (25%)
    reason,              // "Increased Aave for higher yields"
    timestamp            // Block timestamp
));
uint256 recId = aiCommitment.commitRecommendation(commitmentHash);

// Step 2: Reveal (during execution)
bool verified = aiCommitment.verifyRecommendation(
    recId,
    aaveAllocation,
    lidoAllocation,
    compoundAllocation,
    reason
);
// If hash doesn't match, transaction reverts

DAO Constraint Enforcement

Every allocation (AI or manual) must pass DAO-defined rules:

// In PoolController.updateAllocationWeights()
if (address(daoConstraintManager) != address(0)) {
    require(
        daoConstraintManager.checkAllocationConstraints(
            _aaveWeight, 
            _lidoWeight, 
            _compoundWeight
        ),
        "Violates DAO constraints"
    );
}

// DAOConstraintManager checks:
// 1. No single protocol > maxSingleProtocolAllocation (default: 50%)
// 2. At least minDiversification protocols with >10% (default: 3)
// 3. Total allocation = 100% (10000 basis points)

Privacy Preservation

Rebalancing operates on aggregate pool capital, not individual commitments:

// What the AI sees:
Pool State {
    totalTVL: 100 wETH,
    allocations: { aave: 40%, lido: 35%, compound: 25% },
    riskScores: { aave: 45, lido: 38, compound: 52 }
}

// What the AI DOESN'T see:
❌ Individual commitments (ZK-hidden)
❌ User deposit amounts
❌ Withdrawal addresses
❌ Transaction history per user

// Result: AI optimizes the pool, users keep privacy

Live Example: Testnet Rebalance

Here's what happens when you trigger AutoRebalancer on the demo:

Block 305 → Trigger Detected
AutoRebalancer.checkUpkeep() returns true because Aave risk score changed from 45 → 58 (exceeds 10-point threshold)
Block 306 → AI Commits Recommendation
AI generates: Aave 35% → 30%, Lido 35% → 42%, Compound 30% → 28%
Commitment: 0x7a3f9c... (stored on-chain)
Block 306 → DAO Check Passes
✅ Max single protocol: 42% < 50% limit
✅ Diversification: 3 protocols with >10%
✅ Total: 30% + 42% + 28% = 100%
Block 306 → Execution & Verification
PoolController.updateAllocationWeightsWithAI() executes
✅ Event: AIVerifiedAllocation(recId: 17, ...)
✅ Event: AllocationWeightsUpdated(3000, 4200, 2800)
🎯 Result: Full Audit Trail

Anyone can verify this rebalance by:

  1. Checking the trigger condition was met (on-chain data)
  2. Verifying the commitment hash matches the executed allocation
  3. Confirming DAO constraints were satisfied
  4. Seeing the full transaction history in the block explorer

Demo vs Production

🧪 Testnet (Current)

  • Trigger: Manual via cast send
  • AI Logic: Simple risk-based allocation (on-chain)
  • Cooldown: 5 blocks (~5 transactions)
  • Purpose: Demonstrate the verification flow

🚀 Production (Future)

  • Trigger: Chainlink Automation (automatic)
  • AI Logic: Off-chain ML models via Chainlink Functions
  • Cooldown: 100+ blocks (~20 minutes)
  • Purpose: Real treasury management at scale

Key Point: The verification logic is identical in both environments. Only the trigger mechanism changes (manual script vs Chainlink Keepers). This ensures the testnet accurately demonstrates production behavior.

Try It Yourself

Trigger an Automated Rebalance

  1. Deposit funds to the pool (at least 10 wETH)
  2. Wait 5 blocks (make a few deposits/withdrawals to advance blocks)
  3. Check if upkeep is needed:
    cast call $AUTO_REBALANCER "checkUpkeep(bytes)(bool,bytes)" 0x \
      --rpc-url http://localhost:8545
  4. If true, trigger rebalance:
    cast send $AUTO_REBALANCER "performUpkeep(bytes)" 0x \
      --rpc-url http://localhost:8545 --private-key $PRIVATE_KEY
  5. Check the History tab to see the new AI-verified rebalance with full audit trail