Zum Inhalt

07 - AffiliateVaultV2

Affiliate-Provisionen via Merkle Proof


Übersicht

AffiliateVaultV2 verwaltet die Pull-basierten Affiliate-Provisionen. Affiliates werden während des Settlements berechtigt (Merkle Root) und können ihre Provisionen später selbst claimen - direkt oder über den ClaimRouter.

Datei: contracts/contracts/AffiliateVaultV2.sol


State Variables

mapping(uint256 => mapping(address => bool)) private _claimed;  // Claim-Status
ISettlementV5Affiliate public settlement;
address public trustedClaimer;   // Multicall3 Adresse

Merkle Roots im Settlement

Anders als PrizeVault speichert AffiliateVault keine eigenen Merkle Roots. Die Roots liegen in Settlement.affiliateMerkleRootByDay[dayId].


Merkle Leaf Struktur

bytes32 leaf = keccak256(abi.encode(
    dayId,      // uint256 - Tag der Provision
    affiliate,  // address - Affiliate-Wallet
    amount      // uint256 - Provisionsbetrag
));

Einfacher als der Prize-Leaf (keine Reassignment-Logik nötig).


claimAffiliate() - Hauptfunktion

function claimAffiliate(
    uint256 dayId,
    address affiliate,
    address to,
    uint256 amount,
    bytes32[] calldata proof
) external nonReentrant

Ablauf

1. Access Control

require(
    msg.sender == affiliate ||
    msg.sender == trustedClaimer,  // Multicall3
    "Unauthorized"
);

2. Validierung

require(to == affiliate, "No redirection");  // Kein Fund-Redirect erlaubt
require(amount > 0, "Zero amount");
require(dayId > 0, "Invalid day");

3. Double-Claim Check

require(!_claimed[dayId][affiliate], "Already claimed");

4. Merkle Root abrufen

bytes32 root = settlement.affiliateMerkleRootByDay(dayId);
require(root != bytes32(0), "No root");

5. Merkle Proof verifizieren

bytes32 leaf = keccak256(abi.encode(dayId, affiliate, amount));
require(MerkleProof.verifyCalldata(proof, root, leaf), "Invalid proof");

6. Payout via Settlement

settlement.payoutFromAffiliateVault(dayId, affiliate, to, amount);
// Settlement ruft ReserveVault.payPrize() auf


markClaimedFromSettlement()

function markClaimedFromSettlement(uint256 dayId, address affiliate)
    external onlySettlement
  • Nur Settlement kann Claims als ausgeführt markieren
  • Setzt _claimed[dayId][affiliate] = true
  • Wird nach erfolgreicher Auszahlung aufgerufen

Access Control

Funktion Owner (SAFE) Settlement TrustedClaimer Affiliate
claimAffiliate - - ✅ (eigene)
markClaimedFromSettlement - - -
setSettlement - - -
setTrustedClaimer - - -

Events

event AffiliateClaimed(uint256 dayId, address affiliate, uint256 amount);
event AffiliateClaimMarked(uint256 dayId, address affiliate);

Unterschiede zu PrizeVault

Feature PrizeVault AffiliateVault
Merkle Root Storage Lokal In Settlement
Wallet Reassignment Ja Nein
paidTo Redirect Ja (nach Reassignment) Nein (to == affiliate erzwungen)
Double-Claim Tracking In Settlement Lokal (_claimed)

Audit-Hinweise

Einfacherer Contract

AffiliateVault ist bewusst einfacher als PrizeVault gehalten. Affiliates sind bekannte Entities (keine Lost-Wallet-Problematik).

Pull-basiert

Affiliate-Payouts werden nicht während des Settlements ausgeführt. Der Betrag wird nur im Merkle Root committed. Der Affiliate muss aktiv claimen (Pull-Modell).