PonstrategyFeeMachine
0x6a37f7191f5023fc780be59c688ddd495ea8545b
Verification
Verified
0.8.30+commit.73712a01
Type
Contract
11,495 bytes
ABI entries
73
32 read · 13 write
License
none
Contract information
- Address
- 0x6a37f7191f5023fc780be59c688ddd495ea8545b
- Chain
- Robinhood Chain (4663)
- Compiler
- 0.8.30+commit.73712a01
- Optimization
- Enabled
- Creator
- 0x99882Bdb84…9256775462
- Creation tx
- 0x6a3e1a130f…d7a0a278fc
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (32)
DEAD_ADDRESS() → address
MAX_CREATOR_SHARE_BPS() → uint16
buyIncrement() → uint256
creatorPayout() → address
creatorShareBps() → uint16
currentDropInterval() → uint32
dropBudget() → uint256
dropCount() → uint256
dropIntervalOverride() → uint32
dropShareBps() → uint16
keeper() → address
lastBuyBlock() → uint256
lastDropAt() → uint256
maxSlippageBps() → uint16
maxSpend() → uint256
owner() → address
pendingOwner() → address
pons() → address
ponsIsToken0() → bool
ponsLocker() → address
ponsPool() → address
ponsPoolFee() → uint24
ponstrategy() → address
quotePonsOut(uint256) → uint256
quoteWethValue(uint256) → uint256
router() → address
totalBurned() → uint256
totalDropValueWeth() → uint256
totalPonsBought() → uint256
totalPonsDropped() → uint256
twapWindow() → uint32
weth() → address
Events (11)
CreatorShareUpdatedDividendDroppedDropPolicyUpdatedDropStartedFeesBurnedFeesCollectedKeeperUpdatedOwnershipTransferStartedOwnershipTransferredParamsUpdatedPonsBought
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "initialOwner",
"type": "address"
},
{
"internalType": "address",
"name": "ponstrategy_",
"type": "address"
},
{
"internalType": "address",
"name": "pons_",
"type": "address"
},
{
"internalType": "address",
"name": "weth_",
"type": "address"
},
{
"internalType": "address",
"name": "router_",
"type": "address"
},
{
"internalType": "address",
"name": "v3Factory_",
"type": "address"
},
{
"internalType": "uint24",
"name": "ponsPoolFee_",
"type": "uint24"
},
{
"internalType": "address",
"name": "ponsLocker_",
"type": "address"
},
{
"internalType": "uint16",
"name": "creatorShareBps_",
"type": "uint16"
},
{
"internalType": "address",
"name": "creatorPayout_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "BadParams",
"type": "error"
},
{
"inputs": [],
"name": "DropAboveBudget",
"type": "error"
},
{
"inputs": [],
"name": "DropTooSoon",
"type": "error"
},
{
"inputs": [],
"name": "InvalidTick",
"type": "error"
},
{
"inputs": [],
"name": "LengthMismatch",
"type": "error"
},
{
"inputs": [],
"name": "NoLocker",
"type": "error"
},
{
"inputs": [],
"name": "NotKeeper",
"type": "error"
},
{
"inputs": [],
"name": "NothingToBurn",
"type": "error"
},
{
"inputs": [],
"name": "NothingToBuy",
"type": "error"
},
{
"inputs": [],
"name": "NothingToDrop",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "OwnableInvalidOwner",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "OwnableUnauthorizedAccount",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"inputs": [],
"name": "SpendAboveRatchet",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint16",
"name": "creatorShareBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "address",
"name": "creatorPayout",
"type": "address"
}
],
"name": "CreatorShareUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "dropId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "totalPons",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "valueWethAtDrop",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "holderCount",
"type": "uint256"
}
],
"name": "DividendDropped",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint16",
"name": "dropShareBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint32",
"name": "dropInterval",
"type": "uint32"
}
],
"name": "DropPolicyUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "dropId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "budget",
"type": "uint256"
}
],
"name": "DropStarted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "totalBurned",
"type": "uint256"
}
],
"name": "FeesBurned",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "wethAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
}
],
"name": "FeesCollected",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "keeper",
"type": "address"
}
],
"name": "KeeperUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferStarted",
"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"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint32",
"name": "twapWindow",
"type": "uint32"
},
{
"indexed": false,
"internalType": "uint16",
"name": "maxSlippageBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint256",
"name": "buyIncrement",
"type": "uint256"
}
],
"name": "ParamsUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "wethIn",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ponsOut",
"type": "uint256"
}
],
"name": "PonsBought",
"type": "event"
},
{
"inputs": [],
"name": "DEAD_ADDRESS",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_CREATOR_SHARE_BPS",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "acceptOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "burnFees",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "buyIncrement",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
}
],
"name": "buyPons",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "collectFromLocker",
"outputs": [
{
"internalType": "uint256",
"name": "wethAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "creatorPayout",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "creatorShareBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "currentDropInterval",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "dropBudget",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "dropCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "holders",
"type": "address[]"
},
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
}
],
"name": "dropDividend",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "dropIntervalOverride",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "dropShareBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "keeper",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "lastBuyBlock",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "lastDropAt",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxSlippageBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxSpend",
"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": "pendingOwner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pons",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "ponsIsToken0",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "ponsLocker",
"outputs": [
{
"internalType": "contract IPonsLockerLike",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "ponsPool",
"outputs": [
{
"internalType": "contract IUniswapV3PoolTwapLike",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "ponsPoolFee",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "ponstrategy",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "wethIn",
"type": "uint256"
}
],
"name": "quotePonsOut",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "ponsAmount",
"type": "uint256"
}
],
"name": "quoteWethValue",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "router",
"outputs": [
{
"internalType": "contract ISwapRouterLike",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "creatorShareBps_",
"type": "uint16"
},
{
"internalType": "address",
"name": "creatorPayout_",
"type": "address"
}
],
"name": "setCreatorShare",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "dropShareBps_",
"type": "uint16"
},
{
"internalType": "uint32",
"name": "dropInterval_",
"type": "uint32"
}
],
"name": "setDropPolicy",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "keeper_",
"type": "address"
}
],
"name": "setKeeper",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "twapWindow_",
"type": "uint32"
},
{
"internalType": "uint16",
"name": "maxSlippageBps_",
"type": "uint16"
},
{
"internalType": "uint256",
"name": "buyIncrement_",
"type": "uint256"
}
],
"name": "setParams",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "startDrop",
"outputs": [
{
"internalType": "uint256",
"name": "budget",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "totalBurned",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalDropValueWeth",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalPonsBought",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalPonsDropped",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "twapWindow",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "weth",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "wrapEth",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {Ownable2Step, Ownable} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {ISwapRouterLike, IWETHLike} from "../interfaces/IExternal.sol";
import {TickMathLib} from "../libraries/TickMathLib.sol";
interface IPonsLockerLike {
function collectFees(address token) external returns (uint256 amount0, uint256 amount1);
}
interface IUniswapV3FactoryLike {
function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool);
}
interface IUniswapV3PoolTwapLike {
function token0() external view returns (address);
function observe(uint32[] calldata secondsAgos)
external
view
returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);
}
/**
* @title PonstrategyFeeMachine
* @notice $PONSTRATEGY launches as a regular token on the Pons launchpad and
* this contract is set as its creator fee recipient (via the launcher's fee
* redirect). From then on the loop runs with almost no trust:
*
* - `collectFromLocker()` — PERMISSIONLESS. Pulls accrued creator fees from
* the Pons locker (the machine is the authorized recipient).
* - `buyPons()` — PERMISSIONLESS. Swaps accrued WETH into $PONS.
* Slippage is bounded on-chain by the PONS pool's TWAP; a per-block spend
* ratchet TWAPs entries and blocks single-shot drains into a manipulated
* pool.
* - `burnFees()` — PERMISSIONLESS. Sends every $PONSTRATEGY token
* accrued as fees to the dead address.
* - `dropDividend()` — KEEPER. Pushes $PONS to holders pro-rata; the
* holder snapshot comes from off-chain Transfer logs (the launcher token is
* a vanilla ERC-20). The drop's WETH value is computed on-chain from TWAP.
*
* There is NO withdraw function: funds can only ever move into $PONS buys,
* holder wallets, or the burn address. Owner (a multisig, eventually) only
* tunes parameters and rotates the keeper.
*/
contract PonstrategyFeeMachine is Ownable2Step, ReentrancyGuard {
using SafeERC20 for IERC20;
error NotKeeper();
error NoLocker();
error LengthMismatch();
error NothingToBurn();
error NothingToDrop();
error NothingToBuy();
error SpendAboveRatchet();
error BadParams();
error DropTooSoon();
error DropAboveBudget();
event FeesCollected(uint256 wethAmount, uint256 tokenAmount);
event PonsBought(uint256 wethIn, uint256 ponsOut);
event FeesBurned(uint256 amount, uint256 totalBurned);
event DividendDropped(
uint256 indexed dropId, uint256 totalPons, uint256 valueWethAtDrop, uint256 holderCount
);
event KeeperUpdated(address indexed keeper);
event ParamsUpdated(uint32 twapWindow, uint16 maxSlippageBps, uint256 buyIncrement);
event DropPolicyUpdated(uint16 dropShareBps, uint32 dropInterval);
event DropStarted(uint256 indexed dropId, uint256 budget);
event CreatorShareUpdated(uint16 creatorShareBps, address creatorPayout);
address public constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;
uint256 private constant BPS = 10_000;
uint256 private constant Q96 = 2 ** 96;
/// @notice Hard ceiling on the creator share — immutable, guarantees at
/// least half of every collection always buys $PONS, no matter what the
/// owner sets. This is the trust floor holders can verify once and forever.
uint16 public constant MAX_CREATOR_SHARE_BPS = 5_000; // 50%
IERC20 public immutable ponstrategy;
IERC20 public immutable pons;
IERC20 public immutable weth;
ISwapRouterLike public immutable router;
IPonsLockerLike public immutable ponsLocker;
IUniswapV3PoolTwapLike public immutable ponsPool;
uint24 public immutable ponsPoolFee;
bool public immutable ponsIsToken0;
/// @notice Creator's share of collected WETH fees (e.g. 1000 = 10% to the
/// creator wallet at every collection). Owner-tunable via setCreatorShare,
/// but hard-capped at MAX_CREATOR_SHARE_BPS. The remainder stays in the
/// machine and buys $PONS. Token-side fees are always burned.
uint16 public creatorShareBps;
/// @notice Where the creator share is forwarded. Owner-tunable.
address public creatorPayout;
/// @notice Low-privilege role that may execute dividend drops.
address public keeper;
/// @notice TWAP lookback for the buy guard (seconds).
uint32 public twapWindow = 30 minutes;
/// @notice Max shortfall vs TWAP quote a buy will accept.
uint16 public maxSlippageBps = 300; // 3%
/// @notice WETH-per-block spend ratchet for permissionless buys.
uint256 public buyIncrement = 0.05e18;
uint256 public lastBuyBlock;
/// @notice Drop policy — enforced ON-CHAIN and owner-modifiable: each drop
/// round may distribute at most `dropShareBps` of the machine's PONS
/// treasury, and rounds must be `currentDropInterval()` apart. Even a
/// compromised keeper cannot move more than one round's share.
uint16 public dropShareBps = 1_000; // 10% of the PONS treasury per round
/// @notice Manual interval override; 0 means the graduated schedule below
/// applies (8h for drops 1-10, 12h for drops 11-20, 24h thereafter).
uint32 public dropIntervalOverride;
uint256 public lastDropAt;
/// @notice Remaining PONS budget of the active drop round.
uint256 public dropBudget;
uint256 public totalPonsBought;
uint256 public totalPonsDropped;
uint256 public totalBurned;
uint256 public totalDropValueWeth;
uint256 public dropCount;
modifier onlyKeeperOrOwner() {
if (msg.sender != keeper && msg.sender != owner()) revert NotKeeper();
_;
}
constructor(
address initialOwner,
address ponstrategy_,
address pons_,
address weth_,
address router_,
address v3Factory_,
uint24 ponsPoolFee_,
address ponsLocker_,
uint16 creatorShareBps_,
address creatorPayout_
) Ownable(initialOwner) {
if (creatorShareBps_ > MAX_CREATOR_SHARE_BPS || (creatorShareBps_ > 0 && creatorPayout_ == address(0))) {
revert BadParams();
}
creatorShareBps = creatorShareBps_;
creatorPayout = creatorPayout_;
ponstrategy = IERC20(ponstrategy_);
pons = IERC20(pons_);
weth = IERC20(weth_);
router = ISwapRouterLike(router_);
ponsPoolFee = ponsPoolFee_;
ponsLocker = IPonsLockerLike(ponsLocker_);
keeper = initialOwner;
lastBuyBlock = block.number;
address pool = IUniswapV3FactoryLike(v3Factory_).getPool(pons_, weth_, ponsPoolFee_);
if (pool == address(0)) revert BadParams();
ponsPool = IUniswapV3PoolTwapLike(pool);
ponsIsToken0 = IUniswapV3PoolTwapLike(pool).token0() == pons_;
}
/* ------------------------------------------------------------------ */
/* Admin (parameters only) */
/* ------------------------------------------------------------------ */
function setKeeper(address keeper_) external onlyOwner {
keeper = keeper_;
emit KeeperUpdated(keeper_);
}
function setParams(uint32 twapWindow_, uint16 maxSlippageBps_, uint256 buyIncrement_)
external
onlyOwner
{
if (twapWindow_ < 5 minutes || maxSlippageBps_ > 2_000 || buyIncrement_ == 0) {
revert BadParams();
}
twapWindow = twapWindow_;
maxSlippageBps = maxSlippageBps_;
buyIncrement = buyIncrement_;
emit ParamsUpdated(twapWindow_, maxSlippageBps_, buyIncrement_);
}
/// @notice Updates the drop policy (share of treasury per round, minimum
/// spacing between rounds). This is the intended lever for evolving the
/// dividend TIMING and size over time. Pass `dropInterval_ = 0` to follow
/// the graduated schedule (8h -> 12h -> 24h by drop count); any non-zero
/// value (min 1h) overrides it.
function setDropPolicy(uint16 dropShareBps_, uint32 dropInterval_) external onlyOwner {
if (dropShareBps_ == 0 || dropShareBps_ > BPS || (dropInterval_ != 0 && dropInterval_ < 1 hours)) {
revert BadParams();
}
dropShareBps = dropShareBps_;
dropIntervalOverride = dropInterval_;
emit DropPolicyUpdated(dropShareBps_, dropInterval_);
}
/// @notice Updates the ETH-side FEE SPLIT: the creator's share of each
/// collection and where it goes. Capped at MAX_CREATOR_SHARE_BPS so the
/// buy-side can never be starved. Set share to 0 to route 100% into buys.
function setCreatorShare(uint16 creatorShareBps_, address creatorPayout_) external onlyOwner {
if (creatorShareBps_ > MAX_CREATOR_SHARE_BPS) revert BadParams();
if (creatorShareBps_ > 0 && creatorPayout_ == address(0)) revert BadParams();
creatorShareBps = creatorShareBps_;
creatorPayout = creatorPayout_;
emit CreatorShareUpdated(creatorShareBps_, creatorPayout_);
}
/* ------------------------------------------------------------------ */
/* Permissionless mechanism */
/* ------------------------------------------------------------------ */
/// @notice Accept native ETH and wrap on demand.
receive() external payable {}
function wrapEth() external {
uint256 balance = address(this).balance;
if (balance > 0) IWETHLike(address(weth)).deposit{value: balance}();
}
/// @notice Pulls accrued creator fees (both assets) from the Pons locker.
/// If a creator share was frozen in at deploy, that slice of the received
/// WETH is forwarded to the creator payout address; the rest stays in the
/// machine for the loop.
function collectFromLocker() external nonReentrant returns (uint256 wethAmount, uint256 tokenAmount) {
if (address(ponsLocker) == address(0)) revert NoLocker();
uint256 wethBefore = weth.balanceOf(address(this));
(uint256 amount0, uint256 amount1) = ponsLocker.collectFees(address(ponstrategy));
if (creatorShareBps > 0) {
uint256 received = weth.balanceOf(address(this)) - wethBefore;
uint256 creatorCut = (received * creatorShareBps) / BPS;
if (creatorCut > 0) weth.safeTransfer(creatorPayout, creatorCut);
}
// Orientation of the locker's return values is pool-specific; report
// both raw amounts and let indexers attribute them.
emit FeesCollected(amount0, amount1);
return (amount0, amount1);
}
/// @notice Maximum WETH a single buy may spend, growing per block.
function maxSpend() public view returns (uint256) {
return (block.number - lastBuyBlock + 1) * buyIncrement;
}
/// @notice TWAP quote: expected PONS out for `wethIn`.
function quotePonsOut(uint256 wethIn) public view returns (uint256) {
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = twapWindow;
secondsAgos[1] = 0;
(int56[] memory tickCumulatives,) = ponsPool.observe(secondsAgos);
int56 delta = tickCumulatives[1] - tickCumulatives[0];
int24 avgTick = int24(delta / int56(uint56(twapWindow)));
// Round toward negative infinity like the canonical OracleLibrary.
if (delta < 0 && (delta % int56(uint56(twapWindow)) != 0)) avgTick--;
uint160 sqrtP = TickMathLib.getSqrtRatioAtTick(avgTick);
if (ponsIsToken0) {
// price token1/token0 = (sqrtP/Q96)^2 -> WETH per PONS; invert.
uint256 intermediate = Math.mulDiv(wethIn, Q96, sqrtP);
return Math.mulDiv(intermediate, Q96, sqrtP);
} else {
uint256 intermediate = Math.mulDiv(wethIn, sqrtP, Q96);
return Math.mulDiv(intermediate, sqrtP, Q96);
}
}
/// @notice TWAP value in WETH of a PONS amount (for drop bookkeeping).
function quoteWethValue(uint256 ponsAmount) public view returns (uint256) {
uint256 ponsPerWeth = quotePonsOut(1e18);
if (ponsPerWeth == 0) return 0;
return Math.mulDiv(ponsAmount, 1e18, ponsPerWeth);
}
/// @notice Swaps accrued WETH into $PONS. Permissionless: slippage is
/// bounded by TWAP and spend is bounded by the per-block ratchet.
function buyPons(uint256 amountIn) external nonReentrant {
uint256 balance = weth.balanceOf(address(this));
if (amountIn == 0 || amountIn > balance) revert NothingToBuy();
if (amountIn > maxSpend()) revert SpendAboveRatchet();
lastBuyBlock = block.number;
uint256 minOut = (quotePonsOut(amountIn) * (BPS - maxSlippageBps)) / BPS;
weth.forceApprove(address(router), amountIn);
uint256 out = router.exactInputSingle(
ISwapRouterLike.ExactInputSingleParams({
tokenIn: address(weth),
tokenOut: address(pons),
fee: ponsPoolFee,
recipient: address(this),
amountIn: amountIn,
amountOutMinimum: minOut,
sqrtPriceLimitX96: 0
})
);
totalPonsBought += out;
emit PonsBought(amountIn, out);
}
/// @notice Burns every $PONSTRATEGY token accrued as fees.
function burnFees() external nonReentrant {
uint256 balance = ponstrategy.balanceOf(address(this));
if (balance == 0) revert NothingToBurn();
totalBurned += balance;
ponstrategy.safeTransfer(DEAD_ADDRESS, balance);
emit FeesBurned(balance, totalBurned);
}
/* ------------------------------------------------------------------ */
/* Keeper mechanism */
/* ------------------------------------------------------------------ */
/// @notice Minimum spacing before the next drop round may open. Follows a
/// graduated on-chain schedule — 8h between the first 10 drops, 12h
/// through drop 20, 24h thereafter — unless the owner has set a manual
/// override via `setDropPolicy`. The very first drop has no wait
/// (`lastDropAt` starts at 0).
function currentDropInterval() public view returns (uint32) {
if (dropIntervalOverride != 0) return dropIntervalOverride;
if (dropCount < 10) return 8 hours;
if (dropCount < 20) return 12 hours;
return 24 hours;
}
/// @notice Opens a drop round: budgets `dropShareBps` of the current PONS
/// treasury and starts the interval clock. Reverts if called before
/// `currentDropInterval()` has elapsed since the previous round.
function startDrop() external onlyKeeperOrOwner returns (uint256 budget) {
if (block.timestamp < lastDropAt + currentDropInterval()) revert DropTooSoon();
lastDropAt = block.timestamp;
budget = (pons.balanceOf(address(this)) * dropShareBps) / BPS;
dropBudget = budget;
dropCount += 1;
emit DropStarted(dropCount, budget);
}
/// @notice Pushes a batch of the active drop round to holders, pro-rata
/// amounts computed off-chain from Transfer-log snapshots. Batches may not
/// exceed the round's on-chain budget; the WETH value is computed on-chain
/// from TWAP — nothing about the drop's reported value is keeper-supplied.
function dropDividend(address[] calldata holders, uint256[] calldata amounts)
external
onlyKeeperOrOwner
nonReentrant
{
if (holders.length == 0) revert NothingToDrop();
if (holders.length != amounts.length) revert LengthMismatch();
uint256 total;
for (uint256 i; i < holders.length; i++) {
total += amounts[i];
}
if (total > dropBudget) revert DropAboveBudget();
dropBudget -= total;
for (uint256 i; i < holders.length; i++) {
pons.safeTransfer(holders[i], amounts[i]);
}
uint256 valueWeth = quoteWethValue(total);
totalPonsDropped += total;
totalDropValueWeth += valueWeth;
emit DividendDropped(dropCount, total, valueWeth, holders.length);
}
}
Chain explorer2493msChain node81ms