StockDistributor

0x33b0095333e64bf375952ef197b6fdc3437dc014

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
2,080 bytes
ABI entries
16
5 read · 4 write
License
none

Contract information

Address
0x33b0095333e64bf375952ef197b6fdc3437dc014
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x1547b73fec…1a8cb5a282

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (5)

indexToken()address
interval()uint256
nextDistribution()uint256
owner()address
treasury()address

Events (2)

DistributedOwnershipTransferred

ABI

[
  {
    "inputs": [
      {
        "internalType": "contract ReflectionToken",
        "name": "_indexToken",
        "type": "address"
      },
      {
        "internalType": "contract StockTreasury",
        "name": "_treasury",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "owner_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TooEarly",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "stock",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "holders",
        "type": "uint256"
      }
    ],
    "name": "Distributed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "distribute",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "indexToken",
    "outputs": [
      {
        "internalType": "contract ReflectionToken",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "interval",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "nextDistribution",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "v",
        "type": "uint256"
      }
    ],
    "name": "setInterval",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "treasury",
    "outputs": [
      {
        "internalType": "contract StockTreasury",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReflectionToken} from "./ReflectionToken.sol";
import {StockTreasury} from "./StockTreasury.sol";

/// Every `interval` (15 min) pushes this contract's balance of each registered stock token
/// pro-rata to all Index holders in the token's registry (>= minShareBalance; LP/treasury/
/// distributor excluded there). Permissionless poke — anyone/a keeper can call distribute().
/// ponytail: single-tx O(stocks x holders) push, ~thousands of holders on this cheap L2;
/// switch to batched cycles or a dividend-per-share accumulator if the registry outgrows gas.
contract StockDistributor is Ownable {
    using SafeERC20 for IERC20;

    ReflectionToken public immutable indexToken;
    StockTreasury public immutable treasury;

    uint256 public interval = 15 minutes;
    uint256 public nextDistribution;

    event Distributed(address indexed stock, uint256 amount, uint256 holders);

    error TooEarly();

    constructor(ReflectionToken _indexToken, StockTreasury _treasury, address owner_) Ownable(owner_) {
        indexToken = _indexToken;
        treasury = _treasury;
    }

    function setInterval(uint256 v) external onlyOwner { interval = v; }

    function distribute() external {
        if (block.timestamp < nextDistribution) revert TooEarly();
        nextDistribution = block.timestamp + interval;

        uint256 n = indexToken.holderCount();
        if (n == 0) return;

        address[] memory holders = new address[](n);
        uint256[] memory bals = new uint256[](n);
        uint256 eligible;
        for (uint256 i; i < n; ++i) {
            address h = indexToken.holderAt(i);
            uint256 b = indexToken.balanceOf(h);
            holders[i] = h;
            bals[i] = b;
            eligible += b;
        }
        if (eligible == 0) return;

        uint256 m = treasury.stocksLength();
        for (uint256 k; k < m; ++k) {
            IERC20 stock = IERC20(treasury.stockTokenAt(k));
            uint256 pot = stock.balanceOf(address(this));
            if (pot == 0) continue;
            for (uint256 i; i < n; ++i) {
                uint256 amt = (pot * bals[i]) / eligible; // dust stays for the next cycle
                if (amt != 0) stock.safeTransfer(holders[i], amt);
            }
            emit Distributed(address(stock), pot, n);
        }
    }
}
Chain explorer3751msChain node79ms