Skip to content

BribeDistributorMerkle

Scalable bribe distribution via Merkle proofs. Off-chain compute produces a root over (claimer, amount, gauge, epoch, token) tuples; owner publishes root after epoch closes.

Source: khomdev-veforge/src/BribeDistributorMerkle.vy (Vyper 0.4.x)

Alternative to: BribeDistributor (on-chain pro-rata claims)

Implementation overview

  • Fundingdeposit_bribe per (gauge, epoch, token) before root set (C5)
  • Roots — write-once per bucket, only for closed epochs (H2)
  • Leaves — double-hashed, domain-bound to (gauge, epoch, token) (H1)
  • Delay — optional ROOT_DELAY between root publish and claimability (A8)
  • Sweep — owner reclaims leftovers after 90 days
flowchart LR
    Depositor -->|deposit_bribe| BDM[BribeDistributorMerkle]
    Owner -->|set_merkle_root| BDM
    User -->|claim + proof| BDM

Immutables

Name Role
ROOT_DELAY Seconds after root set before claims live (0 = immediate)

Constants

Name Value Role
WEEK 604800 Epoch alignment
SWEEP_DELAY 90 days Min age before sweep
MAX_PROOF_LEN 32 Max Merkle proof depth
MAX_ROOT_DELAY 14 days Constructor cap on delay

Roles

Role Powers
owner (2-step) set_merkle_root, sweep_unclaimed

Events

Event When
BribeDeposited Pool funded
RootSet Merkle root published
MerkleClaim User claim
Sweep Owner sweep

Views

current_epoch

BribeDistributorMerkle.current_epoch() -> uint256

Current week-aligned epoch.

Returns: uint256(block.timestamp // WEEK) * WEEK.

Access: view, any caller.

Source (khomdev-veforge/src/BribeDistributorMerkle.vy:122-125):

@external
@view
def current_epoch() -> uint256:
    return (block.timestamp // WEEK) * WEEK

Deposit

deposit_bribe

BribeDistributorMerkle.deposit_bribe(_gauge, _epoch, _token, _amount)

Fund a bribe pool for (gauge, epoch, token). Must fund before owner sets root.

Param Type Description
_gauge address Gauge
_epoch uint256 Week-aligned epoch (current or future)
_token address ERC-20 token
_amount uint256 Amount to add

Access: Caller. nonreentrant. Pulls via transferFrom.

Events: BribeDeposited.

Reverts if: zero amount, epoch not aligned, or funding past epoch.

Note: Fund before set_merkle_root — post-root deposits are stranded until sweep.

Source (khomdev-veforge/src/BribeDistributorMerkle.vy:177-202):

@external
@nonreentrant
def deposit_bribe(_gauge: address, _epoch: uint256, _token: address, _amount: uint256):
    assert _epoch >= aligned_now, "BDM: cannot fund past epochs"
    self.bribes[_gauge][_epoch][_token] += _amount

Cross-links: set_merkle_root

Root publication

set_merkle_root

BribeDistributorMerkle.set_merkle_root(_gauge, _epoch, _token, _root)

Publish Merkle root for a closed epoch. Write-once per bucket. Owner-only (H2).

Param Type Description
_gauge address Gauge
_epoch uint256 Closed week-aligned epoch
_token address Token
_root bytes32 Merkle root

Access: Owner only.

Events: RootSet.

Reverts if: epoch still open, root already set, or zero root.

Note: Sets root_live_at = block.timestamp + ROOT_DELAY (A8).

Source (khomdev-veforge/src/BribeDistributorMerkle.vy:207-225):

@external
def set_merkle_root(_gauge: address, _epoch: uint256, _token: address, _root: bytes32):
    ownable._check_owner()
    assert _epoch < aligned_now, "BDM: epoch still open"
    assert self.merkle_roots[_gauge][_epoch][_token] == empty(bytes32), "BDM: root already set"
    self.root_live_at[_gauge][_epoch][_token] = block.timestamp + ROOT_DELAY

Cross-links: claim · deposit_bribe

Claim

claim

BribeDistributorMerkle.claim(_gauge, _epoch, _token, _amount, _proof)

Claim against published Merkle root with proof.

Param Type Description
_gauge address Gauge
_epoch uint256 Epoch
_token address Token
_amount uint256 Claim amount (must match leaf)
_proof DynArray[bytes32, 32] Merkle proof

Access: Caller. nonreentrant.

Events: MerkleClaim.

Reverts if: root not set, root not yet live, already claimed, invalid proof, or amount exceeds pool (C5).

Note: Leaf = keccak256(keccak256(abi.encode(addr, amount, gauge, epoch, token))) (H1).

Source (khomdev-veforge/src/BribeDistributorMerkle.vy:230-265):

@external
@nonreentrant
def claim(_gauge: address, _epoch: uint256, _token: address, _amount: uint256, _proof: DynArray[bytes32, MAX_PROOF_LEN]):
    leaf: bytes32 = self._leaf(msg.sender, _amount, _gauge, _epoch, _token)
    assert self._verify(_proof, root, leaf), "BDM: invalid proof"
    assert _amount <= pool, "BDM: amount exceeds pool"

Example:

bdm.claim(gauge, epoch, token, amount, proof)

Admin

sweep_unclaimed

BribeDistributorMerkle.sweep_unclaimed(_gauge, _epoch, _token, _to)

Reclaim leftover pool balance after 90 days past epoch. Owner-only.

Param Type Description
_gauge address Gauge
_epoch uint256 Week-aligned epoch
_token address Token
_to address Recipient

Access: Owner only. nonreentrant.

Events: Sweep.

Reverts if: zero recipient, epoch not aligned, or sweep too early.

Source (khomdev-veforge/src/BribeDistributorMerkle.vy:270-290):

@external
@nonreentrant
def sweep_unclaimed(_gauge: address, _epoch: uint256, _token: address, _to: address):
    ownable._check_owner()
    assert block.timestamp >= _epoch + SWEEP_DELAY, "BDM: sweep too early"
    self.bribes[_gauge][_epoch][_token] = 0
    self._safe_transfer(_token, _to, amount)