Skip to content

Instantly share code, notes, and snippets.

@jordaniza
Last active December 2, 2025 13:53
Show Gist options
  • Select an option

  • Save jordaniza/12c371c005f6bb28635853a117f3411c to your computer and use it in GitHub Desktop.

Select an option

Save jordaniza/12c371c005f6bb28635853a117f3411c to your computer and use it in GitHub Desktop.
xMaquina DAO Capital Distribution Implementation Plan

xMaquina DAO Capital Distribution Implementation Plan

High-Level Requirements

xMaquina needs a capital distribution system that rewards veToken stakers based on:

  • Their voting power (stake amount)
  • Their stake duration (time locked)
  • Continuous participation over a 30-day distribution period

The system should distribute rewards proportionally, with longer-term stakers receiving higher rewards through the veToken multiplier system.

Approach Considerations

1. Global Snapshot

The simplest approach - take a single snapshot at a specific timestamp. Calculate each user's reward share by calling votingPowerAt(tokenId, timestamp) for their token and dividing by totalVotingPowerAt(timestamp). This gives their proportional share of the reward pool.

  • Pros: Simple, gas-efficient (single votingPowerAt call per user)
  • Cons: Not gameable per se, but has a fairness issue - since veToken doesn't track historical ownership, users must own their tokens at snapshot time. If someone unstakes before the snapshot, they lose all accumulated rewards despite having staked for the period.
  • Implementation: One timestamp, one calculation

2. Custom Time-Weight Function

While technically feasible, implementing an additional time-weighting function on top of veToken's existing mechanics would be unnecessarily complex. The veToken system already incorporates time-based rewards through its voting power multiplier - users who lock for longer periods receive up to 12x their base amount after 1 year.

Adding another layer of time-based calculations would:

  • Require fetching individual token creation timestamps
  • Add computational complexity for marginal benefit
  • Create confusion since time-based incentives are already built into the voting power calculation
  • Make the reward distribution harder to verify and reason about

The existing veToken multiplier elegantly solves the "reward long-term stakers" requirement without additional complexity.

Recommendation: Single snapshot approach - simple, gas-efficient, and aligns with veToken's ownership model.

Alternative: Merkle Tree Distribution

Aragon's Capital Distributor includes a complete Merkle tree distribution implementation that allows complex reward calculations to be performed off-chain while maintaining verifiable on-chain claims.

How it works:

  1. Off-chain calculation: Calculate each user's veToken voting power share and rewards
  2. Tree generation: Create merkle tree with leaves as keccak256(abi.encodePacked(address, amount))
  3. Campaign creation: Deploy campaign with merkle root as initialization data
  4. User claims: Users provide merkle proof to claim their allocation

Implementation Steps:

  1. Generate Merkle Tree (using provided script):

    # Create recipients.json with veToken calculations
    forge script GenerateMerkleTree --sig "generateTreeFromFile(string)" recipients.json
  2. Create Campaign with merkle root:

    bytes memory strategyAuxData = abi.encode(merkleRoot);
    ICapitalDistributorPlugin.StrategyConfig({
        strategyId: "merkle-distributor-strategy",
        strategyParams: "",
        initData: strategyAuxData
    })
  3. Users Claim with proof:

    bytes memory claimData = abi.encode(merkleProof, claimAmount);
    plugin.claimCampaignPayout(campaignId, recipient, claimData, "");

Advantages for xMaquina:

  • Supports complex veToken calculations off-chain (multiple snapshots, custom weighting)
  • Gas-efficient for large user bases (O(log n) verification)
  • Can integrate with existing xMaquina infrastructure
  • Flexible updates via campaign pausing and root updates

Trade-offs:

  • Requires off-chain computation infrastructure
  • Users need merkle proofs (typically provided by frontend)
  • Trust in calculation process (mitigated by open-source scripts)

Capital Distributor Implementation

Core Components to Implement

  1. Custom Allocation Strategy

    contract VeTokenAllocationStrategy implements IAllocationStrategy {
        function getTotalClaimableAmount(
            uint256 _campaignId,
            address _recipient,
            bytes memory _strategyAuxData
        ) external view returns (uint256)
    }
  2. Key Functions:

    • getTotalClaimableAmount: Calculate user's reward based on VP snapshots
    • decodeAuxData: Extract snapshot timestamps and campaign parameters
    • calculateAverageShare: Sum VP across snapshots and divide by total
  3. Multi-Token Support:

    • To reward users with > 1 token (i.e. USDC,DEUS) just setup multiple campaigns
    • Each reward token = separate campaign
    • Reuse same strategy across campaigns
    • Different budgets per token type
  4. Optional Action Encoder:

    • By default, rewards are transferred to the user's wallet. Optionally, you can define advanced behaviours.
    • Enables alternative reward mechanisms, e.g., automatically stake DEUS rewards as xDEUS instead of direct transfer
    • Not required in the base case, for simple ERC20 transfers

Implementation Sketch

// not production code, just an idea
contract VeTokenAllocationStrategy is IAllocationStrategy {
    IVotingEscrow public veToken;

    struct SnapshotData {
        uint256 timestamp;
        uint256 totalBudget;
        uint256[] tokenIds; // User's veToken IDs
    }

    function getTotalClaimableAmount(
        uint256 _campaignId,
        address _recipient,
        bytes memory _strategyAuxData
    ) external view returns (uint256) {
        // Decode the auxiliary data to get snapshot timestamp and total budget
        SnapshotData memory data = abi.decode(_strategyAuxData, (SnapshotData));

        uint256 userVP = 0;

        // Sum voting power across all user's tokens at the snapshot
        for (uint i = 0; i < data.tokenIds.length; i++) {
            // Verify user still owns this token
            if (veToken.ownerOf(data.tokenIds[i]) == _recipient) {
                userVP += veToken.votingPowerAt(data.tokenIds[i], data.timestamp);
            }
        }

        uint256 totalVP = veToken.totalVotingPowerAt(data.timestamp);

        if (totalVP == 0) return 0;

        // Calculate user's share of the total voting power
        uint256 userShare = (userVP * 1e18) / totalVP;

        // Apply share to total budget to get user's reward
        return (data.totalBudget * userShare) / 1e18;
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment