| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
Latest 25 internal transactions (View All)
| Parent Transaction Hash | Block | From | To | |||
|---|---|---|---|---|---|---|
| 92597974 | 7 mins ago | 0.20110622 BNB | ||||
| 92597974 | 7 mins ago | 0.02793142 BNB | ||||
| 92597974 | 7 mins ago | 0.22903764 BNB | ||||
| 92593311 | 42 mins ago | 0.20908264 BNB | ||||
| 92593311 | 42 mins ago | 0.02903925 BNB | ||||
| 92593311 | 42 mins ago | 0.2381219 BNB | ||||
| 92586885 | 1 hr ago | 0.21358281 BNB | ||||
| 92586885 | 1 hr ago | 0.02966428 BNB | ||||
| 92586885 | 1 hr ago | 0.24324709 BNB | ||||
| 92574354 | 3 hrs ago | 0.21685479 BNB | ||||
| 92574354 | 3 hrs ago | 0.03011872 BNB | ||||
| 92574354 | 3 hrs ago | 0.24697351 BNB | ||||
| 92531912 | 8 hrs ago | 0.21834218 BNB | ||||
| 92531912 | 8 hrs ago | 0.0303253 BNB | ||||
| 92531912 | 8 hrs ago | 0.24866748 BNB | ||||
| 92515631 | 10 hrs ago | 0.2184278 BNB | ||||
| 92515631 | 10 hrs ago | 0.03033719 BNB | ||||
| 92515631 | 10 hrs ago | 0.24876499 BNB | ||||
| 92507580 | 11 hrs ago | 0.23533988 BNB | ||||
| 92507580 | 11 hrs ago | 0.03268609 BNB | ||||
| 92507580 | 11 hrs ago | 0.26802597 BNB | ||||
| 92495149 | 12 hrs ago | 0.23379473 BNB | ||||
| 92495149 | 12 hrs ago | 0.03247149 BNB | ||||
| 92495149 | 12 hrs ago | 0.26626623 BNB | ||||
| 92478638 | 15 hrs ago | 0.23830873 BNB |
Cross-Chain Transactions
Loading...
Loading
Minimal Proxy Contract for 0xdaca3b6071c5494b9bc8960bb5e80e20a69794ff
Contract Name:
TaxProcessor
Compiler Version
v0.8.24+commit.e11b9ed9
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity =0.8.24;
import "@openzeppelin-contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin-contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {IERC20} from "@openzeppelin/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol";
import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import {IDividend} from "src/interfaces/Tax/IDividend.sol";
import {IPortal, IPortalTradeV2, IPortalTypes} from "src/interfaces/IPortal.sol";
import {BuyBackGuardUpgradeable} from "./BuyBackGuardUpgradeable.sol";
interface IWETH {
function withdraw(uint256) external;
function deposit() external payable;
}
interface IUniswapV2Factory {
function getPair(address tokenA, address tokenB) external view returns (address pair);
}
interface IUniswapRouter02 {
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
function addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) external returns (uint256 amountA, uint256 amountB, uint256 liquidity);
function getAmountsOut(uint256 amountIn, address[] calldata path)
external
view
returns (uint256[] memory amounts);
function factory() external pure returns (address);
}
/// @notice Initialization parameters for TaxProcessor
struct TaxProcessorInitParams {
address quoteToken;
address router;
address feeReceiver;
address marketAddress;
address dividendAddress;
address taxToken;
uint16 feeRate;
uint16 marketBps;
uint16 deflationBps;
uint16 lpBps;
uint16 dividendBps;
}
contract TaxProcessor is OwnableUpgradeable, ReentrancyGuardUpgradeable, BuyBackGuardUpgradeable {
using SafeERC20 for IERC20;
// --- Constants ---
/// @notice Dead address for burning deflation tokens
address public constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;
// --- Immutable Storage ---
/// @notice WETH address for ETH conversion (immutable)
address public immutable weth;
/// @notice FlapBlackHole address (immutable)
address public immutable flapBlackHole;
/// @notice Portal address for swapping (immutable)
address public immutable portal;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(address weth_, address flapBlackHole_, address portal_) {
require(weth_ != address(0), "TaxProcessor: zero WETH address");
require(portal_ != address(0), "TaxProcessor: zero portal address");
weth = weth_;
flapBlackHole = flapBlackHole_;
portal = portal_;
_disableInitializers();
}
// --- Storage ---
/// @notice Quote token (WETH or other ERC20), only set if not WETH
address public quoteToken;
/// @notice Tax token (the token that calls processing)
address public taxToken;
/// @notice Uniswap V2 router address
address public router;
/// @notice Fee receiver address
address public feeReceiver;
/// @notice Market receiver address (only set if marketBps > 0)
address public marketAddress;
/// @notice Dividend contract address (only set if dividendBps > 0)
address public dividendAddress;
/// @notice Accumulated quote token balance for fee
uint256 public feeQuoteBalance;
/// @notice Accumulated quote token balance for lp
uint256 public lpQuoteBalance;
/// @notice Accumulated quote token balance for market
uint256 public marketQuoteBalance;
/// @notice Accumulated quote token balance for dividend
uint256 public dividendQuoteBalance;
/// @notice Gas-optimized struct containing all fee configuration
/// @dev Packed into a single 256-bit storage slot for gas efficiency
/// Field breakdown:
/// - marketBps: 16 bits (basis points, max 65535)
/// - deflationBps: 16 bits (basis points, max 65535)
/// - lpBps: 16 bits (basis points, max 65535)
/// - dividendBps: 16 bits (basis points, max 65535)
/// - feeRate: 16 bits (basis points, max 65535)
/// - isWeth: 8 bits (boolean, padded to 8 bits for alignment)
/// Total: 16 + 16 + 16 + 16 + 16 + 8 = 88 bits (fits in one storage slot)
struct PackedFeeConfig {
uint16 marketBps;
uint16 deflationBps;
uint16 lpBps;
uint16 dividendBps;
uint16 feeRate;
bool isWeth;
}
/// @notice All fee-related configuration packed into a single storage slot
PackedFeeConfig public feeConfig;
/// @notice Total quote token sent to dividend contract
uint256 public totalQuoteSentToDividend;
/// @notice Total quote token added to liquidity
uint256 public totalQuoteAddedToLiquidity;
/// @notice Total tax token added to liquidity
uint256 public totalTokenAddedToLiquidity;
/// @notice Total quote token sent to marketing wallet
uint256 public totalQuoteSentToMarketing;
/// @notice Pre-bonding burn funds (deflation portion from bonding curve tax)
uint256 public preBondBurnFunds;
/// @notice Minimum buy back quote amount threshold
uint256 public minBuyBackQuote;
/// @notice Maximum gas limit for buy back operations
uint256 public maxBuyBackGasLimit;
// --- Events ---
event FlapTaxProcessorDispatchExecuted(
address indexed taxToken, uint256 feeAmount, uint256 marketAmount, uint256 dividendAmount
);
event FlapTaxProcessorBurnExecuted(address indexed taxToken, uint256 quoteAmount, uint256 tokensBurned);
event FlapTaxProcessorBuyBackSkipped(address indexed taxToken, uint256 quoteAmount, uint256 gasLeft, string reason);
event FlapTaxProcessorPortalRefund(address indexed taxToken, uint256 refundAmount, bool isWeth);
event FlapTaxProcessorDividendDepositSkipped(address indexed taxToken, uint256 amount, string reason);
event FlapTaxProcessorTokensBurned(address indexed taxToken, uint256 amount);
event FlapTaxProcessorBondingCurveTax(address indexed taxToken, uint256 quoteAmount);
event FlapTaxProcessorProcessTaxTokens(address indexed taxToken, uint256 taxAmount);
event FlapTaxProcessorMaxBuyBackGasLimitUpdated(uint256 oldLimit, uint256 newLimit);
event FlapTaxProcessorMinBuyBackQuoteUpdated(uint256 oldAmount, uint256 newAmount);
// --- Modifiers ---
modifier onlyTaxToken() {
require(msg.sender == taxToken, "TaxProcessor: caller is not the tax token");
_;
}
// --- Initialization ---
function initialize(TaxProcessorInitParams memory params) external initializer {
require(
params.router != address(0) && params.feeReceiver != address(0) && params.taxToken != address(0),
"TaxProcessor: zero required init arg"
);
// Validate total distribution percentages sum to 10000 (100%)
uint256 totalBps = uint256(params.marketBps) + uint256(params.deflationBps) + uint256(params.lpBps)
+ uint256(params.dividendBps);
require(totalBps == 10000, "TaxProcessor: distribution bps must sum to 10000");
// Validate fee rate
require(params.feeRate <= 10000, "TaxProcessor: feeRate must be <= 10000");
__Ownable_init();
__ReentrancyGuard_init();
__BuyBackGuard_init();
// Set basic addresses
router = params.router;
feeReceiver = params.feeReceiver;
taxToken = params.taxToken;
// Determine if quote token is WETH
bool isWeth = params.quoteToken == weth;
// Set quote token only if not WETH
if (!isWeth) {
require(params.quoteToken != address(0), "TaxProcessor: zero quote token");
quoteToken = params.quoteToken;
}
// Set addresses only if their respective bps > 0 and validate non-zero
if (params.marketBps > 0) {
require(params.marketAddress != address(0), "TaxProcessor: zero market address");
marketAddress = params.marketAddress;
}
if (params.dividendBps > 0) {
require(params.dividendAddress != address(0), "TaxProcessor: zero dividend address");
dividendAddress = params.dividendAddress;
}
// Pack fee configuration into a single storage slot
feeConfig = PackedFeeConfig({
marketBps: params.marketBps,
deflationBps: params.deflationBps,
lpBps: params.lpBps,
dividendBps: params.dividendBps,
feeRate: params.feeRate,
isWeth: isWeth
});
// Calculate and set minBuyBackQuote based on chain and quote token
minBuyBackQuote = _calculateMinBuyBackQuote(isWeth, params.quoteToken);
// Set maximum gas limit for buy back operations (default: 500k gas)
maxBuyBackGasLimit = 500_000;
}
// --- External Functions ---
/// @notice Get the quote token address
/// @return The quote token address (WETH if isWeth is true, otherwise stored quoteToken)
function getQuoteToken() external view returns (address) {
return _getQuoteToken();
}
/// @notice Process bonding curve tax in quote tokens
/// @param quoteAmount The amount of quote tokens to process
/// @dev Splits quote tokens into fee, market, dividend, lp, and deflation based on configured ratios
/// LP portion is added to lpQuoteBalance for later processing
/// Deflation portion is added to preBondBurnFunds for buyback and burn
/// During buyback (_isBuyingBack=true), all tax goes to preBondBurnFunds to avoid balance pollution
function processBondingCurveTax(uint256 quoteAmount) external {
require(quoteAmount > 0, "TaxProcessor: zero amount");
// Transfer quote token from sender (always as ERC20, even if WETH)
address quoteTokenAddr = _getQuoteToken();
IERC20(quoteTokenAddr).safeTransferFrom(msg.sender, address(this), quoteAmount);
// During buyback, redirect all tax to preBondBurnFunds
// This prevents balance pollution and simplifies refund detection
// Business logic: tax generated during buyback should also be used for burning
if (_isBuyingBack()) {
preBondBurnFunds += quoteAmount;
emit FlapTaxProcessorBondingCurveTax(taxToken, quoteAmount);
return;
}
// Normal distribution
PackedFeeConfig memory config = feeConfig;
// Calculate distribution
uint256 fee = (quoteAmount * uint256(config.feeRate)) / 10000;
uint256 remaining = quoteAmount - fee;
uint256 market = (remaining * uint256(config.marketBps)) / 10000;
uint256 deflation = (remaining * uint256(config.deflationBps)) / 10000;
uint256 lp = (remaining * uint256(config.lpBps)) / 10000;
uint256 dividend = remaining - market - deflation - lp;
// Update balances
feeQuoteBalance += fee;
marketQuoteBalance += market;
dividendQuoteBalance += dividend;
lpQuoteBalance += lp;
preBondBurnFunds += deflation;
emit FlapTaxProcessorBondingCurveTax(taxToken, quoteAmount);
}
/// @notice Process tax tokens: compute fees, split amounts, burn deflation, swap and distribute
/// @param taxAmount The total amount of tax tokens to process
/// @dev Uses TaxProcessor's stored configuration for fee and distribution calculations
function processTaxTokens(uint256 taxAmount) external onlyTaxToken {
require(taxAmount > 0, "TaxProcessor: zero amount");
// Transfer tax tokens from sender first
IERC20(taxToken).safeTransferFrom(msg.sender, address(this), taxAmount);
// Get packed config
PackedFeeConfig memory config = feeConfig;
// Step 1: Compute fee and remaining using stored configuration
uint256 fee = (taxAmount * uint256(config.feeRate)) / 10000;
uint256 remaining = taxAmount - fee;
// Step 2: Split remaining into components using stored percentages
uint256 market = (remaining * uint256(config.marketBps)) / 10000;
uint256 deflation = (remaining * uint256(config.deflationBps)) / 10000;
uint256 lp = (remaining * uint256(config.lpBps)) / 10000;
uint256 dividend = remaining - (market + deflation + lp);
// Step 3: Burn deflation tokens directly
// Note: preBondBurnFunds is now handled in dispatch() function
if (deflation > 0) {
IERC20(taxToken).safeTransfer(flapBlackHole, deflation);
emit FlapTaxProcessorTokensBurned(taxToken, deflation);
}
// Step 4: Process remaining token distribution (swap, add liquidity, record balances)
_processTokenDistribution(fee, market, lp, dividend);
// No unwrapping here - keep everything as WETH internally for efficiency
emit FlapTaxProcessorProcessTaxTokens(taxToken, taxAmount);
}
/// @notice Process token distribution: add liquidity first, then swap remaining tokens
/// @param fee Amount of tax tokens for fee
/// @param market Amount of tax tokens for market
/// @param lp Amount of tax tokens for liquidity provision
/// @param dividend Amount of tax tokens for dividend
function _processTokenDistribution(uint256 fee, uint256 market, uint256 lp, uint256 dividend) internal {
uint256 lpTaxToSwap = lp; // Default: all lp tokens need to be swapped
// Step 1: Add liquidity using lp tax tokens and accumulated lpQuoteBalance
if (lp > 0 && lpQuoteBalance > 0) {
(uint256 actualTokenUsed, uint256 actualQuoteUsed) = _addLiquidity(lp, lpQuoteBalance);
// Calculate remaining tokens after liquidity provision
lpTaxToSwap = lp >= actualTokenUsed ? lp - actualTokenUsed : 0;
lpQuoteBalance = lpQuoteBalance >= actualQuoteUsed ? lpQuoteBalance - actualQuoteUsed : 0;
}
// Step 2: Swap all remaining tax tokens (lpTaxToSwap + fee + market + dividend)
uint256 totalToSwap = lpTaxToSwap + fee + market + dividend;
if (totalToSwap > 0) {
uint256 quoteReceived = _swapTokensForQuote(totalToSwap);
if (quoteReceived > 0) {
// Use totalToSwap as the base for proportional distribution
// Record balances for fee, market, dividend, lpTaxLeft proportionally
if (totalToSwap > 0) {
if (fee > 0) {
uint256 quoteForFee = (quoteReceived * fee) / totalToSwap;
feeQuoteBalance += quoteForFee;
}
if (market > 0) {
uint256 quoteForMarket = (quoteReceived * market) / totalToSwap;
marketQuoteBalance += quoteForMarket;
}
if (dividend > 0) {
uint256 quoteForDividend = (quoteReceived * dividend) / totalToSwap;
dividendQuoteBalance += quoteForDividend;
}
if (lpTaxToSwap > 0) {
uint256 quoteForLp = (quoteReceived * lpTaxToSwap) / totalToSwap;
lpQuoteBalance += quoteForLp;
}
}
}
}
}
/// @notice Dispatch accumulated quote tokens to receivers
function dispatch() external nonReentrant {
uint256 feeAmount = feeQuoteBalance;
uint256 marketAmount = marketQuoteBalance;
uint256 dividendAmount = dividendQuoteBalance;
uint256 burnAmount = preBondBurnFunds;
// Clear balances first (checks-effects-interactions pattern)
if (feeAmount > 0) feeQuoteBalance = 0;
if (marketAmount > 0) marketQuoteBalance = 0;
if (dividendAmount > 0) dividendQuoteBalance = 0;
if (burnAmount > 0) preBondBurnFunds = 0;
PackedFeeConfig memory config = feeConfig;
// If quote token is WETH, optimize by unwrapping once and sending ETH directly
if (config.isWeth) {
_dispatchETH(feeAmount, marketAmount, dividendAmount);
} else {
// Regular ERC20 token dispatch
_dispatchERC20(feeAmount, marketAmount, dividendAmount);
}
// Process burn funds based on token state
if (burnAmount > 0) {
// Check if token has migrated to DEX via Portal's getTokenV7
IPortalTradeV2.TokenStateV7 memory state = IPortal(portal).getTokenV7(taxToken);
// TokenState: Tradable=BondingCurve, DEX=Graduated
if (state.status == IPortalTypes.TokenStatus.DEX) {
// Token is on DEX, liquidate all remaining funds regardless of threshold
// Use DEX router to swap quote tokens for tax tokens and burn
uint256 taxTokensReceived = _swapQuoteForTokens(burnAmount);
if (taxTokensReceived > 0) {
IERC20(taxToken).safeTransfer(flapBlackHole, taxTokensReceived);
emit FlapTaxProcessorBurnExecuted(taxToken, burnAmount, taxTokensReceived);
}
} else {
// Token still on bonding curve, only process if exceeds threshold
if (burnAmount >= minBuyBackQuote) {
_buyBackAndBurn(burnAmount);
} else {
// Re-add to preBondBurnFunds if below threshold
preBondBurnFunds = burnAmount;
}
}
}
// Reconcile any untracked balance (refunds from _buyBackAndBurn, donations, etc.)
_reconcileBalance();
emit FlapTaxProcessorDispatchExecuted(taxToken, feeAmount, marketAmount, dividendAmount);
}
/// @notice Dispatch ETH (when quote token is WETH) with optimized unwrapping
/// @param feeAmount Amount to send to fee receiver
/// @param marketAmount Amount to send to market receiver
/// @param dividendAmount Amount to send to dividend contract
function _dispatchETH(uint256 feeAmount, uint256 marketAmount, uint256 dividendAmount) internal {
uint256 totalETHNeeded = 0;
// Calculate total ETH needed for external addresses (fee + market)
if (feeAmount > 0) totalETHNeeded += feeAmount;
if (marketAmount > 0 && marketAddress != address(0)) totalETHNeeded += marketAmount;
// Unwrap all needed ETH at once
if (totalETHNeeded > 0) {
IWETH(weth).withdraw(totalETHNeeded);
}
uint256 ethUsed = 0;
// Send fee
if (feeAmount > 0) {
(bool success,) = payable(feeReceiver).call{value: feeAmount}("");
if (success) {
ethUsed += feeAmount;
}
}
// Send market
if (marketAmount > 0 && marketAddress != address(0)) {
(bool success,) = payable(marketAddress).call{value: marketAmount}("");
if (success) {
ethUsed += marketAmount;
totalQuoteSentToMarketing += marketAmount;
}
}
// If any ETH sends failed, wrap the remaining ETH back to WETH and add to fee balance
uint256 remainingETH = totalETHNeeded - ethUsed;
if (remainingETH > 0) {
IWETH(weth).deposit{value: remainingETH}();
feeQuoteBalance += remainingETH; // Add failed amounts to fee balance
}
// Send dividend to Dividend contract (keep as WETH)
if (dividendAmount > 0 && dividendAddress != address(0)) {
_sendToDividendContract(dividendAmount);
}
}
/// @notice Dispatch ERC20 tokens (when quote token is not WETH)
/// @param feeAmount Amount to send to fee receiver
/// @param marketAmount Amount to send to market receiver
/// @param dividendAmount Amount to send to dividend contract
function _dispatchERC20(uint256 feeAmount, uint256 marketAmount, uint256 dividendAmount) internal {
address quote = _getQuoteToken();
// Send fee
if (feeAmount > 0) {
IERC20(quote).safeTransfer(feeReceiver, feeAmount);
}
// Send market
if (marketAmount > 0 && marketAddress != address(0)) {
totalQuoteSentToMarketing += marketAmount;
IERC20(quote).safeTransfer(marketAddress, marketAmount);
}
// Send dividend to Dividend contract
if (dividendAmount > 0 && dividendAddress != address(0)) {
_sendToDividendContract(dividendAmount);
}
}
/// @notice Owner can update receivers
function setReceivers(address feeReceiver_, address marketAddress_, address dividendAddress_) external onlyOwner {
require(feeReceiver_ != address(0), "TaxProcessor: zero fee receiver");
feeReceiver = feeReceiver_;
// Only set market and dividend addresses if they will be used (bps > 0)
PackedFeeConfig memory config = feeConfig;
if (config.marketBps > 0) {
require(marketAddress_ != address(0), "TaxProcessor: zero market address");
marketAddress = marketAddress_;
}
if (config.dividendBps > 0) {
require(dividendAddress_ != address(0), "TaxProcessor: zero dividend address");
dividendAddress = dividendAddress_;
}
}
/// @notice Owner can update tax configuration
function setTaxConfig(uint16 feeRate_, uint16 marketBps_, uint16 deflationBps_, uint16 lpBps_, uint16 dividendBps_)
external
onlyOwner
{
// Validate fee rate and percentages
require(feeRate_ <= 10000, "TaxProcessor: feeRate must be <= 10000");
uint256 totalBps = uint256(marketBps_) + uint256(deflationBps_) + uint256(lpBps_) + uint256(dividendBps_);
require(totalBps == 10000, "TaxProcessor: distribution bps must sum to 10000");
// Validate that addresses are set for non-zero BPS allocations
if (marketBps_ > 0) {
require(marketAddress != address(0), "TaxProcessor: market address not set");
}
if (dividendBps_ > 0) {
require(dividendAddress != address(0), "TaxProcessor: dividend address not set");
}
// Update packed config
PackedFeeConfig memory config = feeConfig;
config.feeRate = feeRate_;
config.marketBps = marketBps_;
config.deflationBps = deflationBps_;
config.lpBps = lpBps_;
config.dividendBps = dividendBps_;
feeConfig = config;
}
/// @notice Owner can update maximum buy back gas limit
/// @param newLimit The new maximum gas limit for buy back operations
function setMaxBuyBackGasLimit(uint256 newLimit) external onlyOwner {
require(newLimit > 0, "TaxProcessor: zero maxBuyBackGasLimit");
uint256 oldLimit = maxBuyBackGasLimit;
maxBuyBackGasLimit = newLimit;
emit FlapTaxProcessorMaxBuyBackGasLimitUpdated(oldLimit, newLimit);
}
/// @notice Owner can update minimum buy back quote amount
/// @param newAmount The new minimum buy back quote amount
function setMinBuyBackQuote(uint256 newAmount) external onlyOwner {
require(newAmount > 0, "TaxProcessor: zero minBuyBackQuote");
uint256 oldAmount = minBuyBackQuote;
minBuyBackQuote = newAmount;
emit FlapTaxProcessorMinBuyBackQuoteUpdated(oldAmount, newAmount);
}
// --- Internal Functions ---
/// @notice Calculate minimum buy back quote amount based on chain and quote token
/// @param isWeth Whether the quote token is WETH
/// @param quoteTokenAddr The quote token address
/// @return The minimum buy back quote amount
function _calculateMinBuyBackQuote(bool isWeth, address quoteTokenAddr) internal view returns (uint256) {
uint256 chainId = block.chainid;
uint256 decimals = IERC20Metadata(quoteTokenAddr).decimals();
// BNB chain (BSC mainnet or testnet)
if (chainId == 56 || chainId == 97) {
if (isWeth) {
return 0.05 ether;
} else if (quoteTokenAddr == 0x000Ae314E2A2172a039B26378814C252734f556A) {
return 100 ether;
} else if (quoteTokenAddr == 0x924fa68a0FC644485b8df8AbfA0A41C2e7744444) {
return 300 ether;
} else {
// treat all other tokens a USD*
return 50 * (10 ** decimals);
}
}
// xlayer
else if (chainId == 196) {
if (isWeth) {
// OKB
return 0.3 ether;
} else {
// USD* $50
return 50 * (10 ** decimals);
}
} else {
return 0.1 ether;
}
}
/// @notice Internal function to get the quote token address
/// @return The quote token address (WETH if isWeth is true, otherwise stored quoteToken)
function _getQuoteToken() internal view returns (address) {
return feeConfig.isWeth ? weth : quoteToken;
}
/// @notice Swap tax tokens for quote tokens
/// @param amount Amount of tax tokens to swap
/// @return quoteReceived Amount of quote tokens received
function _swapTokensForQuote(uint256 amount) internal returns (uint256 quoteReceived) {
// Approve router
IERC20(taxToken).safeApprove(router, 0);
IERC20(taxToken).safeApprove(router, amount);
address quoteTokenAddr = _getQuoteToken();
address[] memory path = new address[](2);
path[0] = taxToken;
path[1] = quoteTokenAddr;
uint256 quoteBefore = IERC20(quoteTokenAddr).balanceOf(address(this));
IUniswapRouter02(router).swapExactTokensForTokensSupportingFeeOnTransferTokens(
amount,
0, // Accept any amount
path,
address(this),
block.timestamp
);
quoteReceived = IERC20(quoteTokenAddr).balanceOf(address(this)) - quoteBefore;
return quoteReceived;
}
/// @notice Swap quote tokens for tax tokens (reverse swap)
/// @param quoteAmount Amount of quote tokens to swap
/// @return taxTokensReceived Amount of tax tokens received
function _swapQuoteForTokens(uint256 quoteAmount) internal returns (uint256 taxTokensReceived) {
address quoteTokenAddr = _getQuoteToken();
// Approve router
IERC20(quoteTokenAddr).safeApprove(router, 0);
IERC20(quoteTokenAddr).safeApprove(router, quoteAmount);
address[] memory path = new address[](2);
path[0] = quoteTokenAddr;
path[1] = taxToken;
uint256 taxTokenBefore = IERC20(taxToken).balanceOf(address(this));
IUniswapRouter02(router).swapExactTokensForTokensSupportingFeeOnTransferTokens(
quoteAmount,
0, // Accept any amount
path,
address(this),
block.timestamp
);
taxTokensReceived = IERC20(taxToken).balanceOf(address(this)) - taxTokenBefore;
return taxTokensReceived;
}
/// @notice Add liquidity to DEX
/// @param tokenAmount Amount of tax tokens
/// @param quoteAmount Amount of quote tokens
/// @return actualTokenUsed Amount of tax tokens actually used
/// @return actualQuoteUsed Amount of quote tokens actually used
function _addLiquidity(uint256 tokenAmount, uint256 quoteAmount)
internal
returns (uint256 actualTokenUsed, uint256 actualQuoteUsed)
{
address quoteTokenAddr = _getQuoteToken();
// Approve router
IERC20(taxToken).safeApprove(router, 0);
IERC20(taxToken).safeApprove(router, tokenAmount);
IERC20(quoteTokenAddr).safeApprove(router, 0);
IERC20(quoteTokenAddr).safeApprove(router, quoteAmount);
// Add liquidity and receive LP tokens to this contract
(uint256 amountA, uint256 amountB,) = IUniswapRouter02(router).addLiquidity(
taxToken, quoteTokenAddr, tokenAmount, quoteAmount, 0, 0, address(DEAD_ADDRESS), block.timestamp
);
// Determine which amount corresponds to tax token and quote token
// Router returns amounts in the same order as input tokens
actualTokenUsed = amountA;
actualQuoteUsed = amountB;
// Update totals
totalTokenAddedToLiquidity += actualTokenUsed;
totalQuoteAddedToLiquidity += actualQuoteUsed;
}
/// @notice Send quote tokens to Dividend contract
/// @param amount The amount to send
function _sendToDividendContract(uint256 amount) internal {
address quoteTokenAddr = _getQuoteToken();
// Approve Dividend contract
IERC20(quoteTokenAddr).safeApprove(dividendAddress, 0);
IERC20(quoteTokenAddr).safeApprove(dividendAddress, amount);
// Call deposit on Dividend contract and check return value
bool success = IDividend(dividendAddress).deposit(amount);
if (!success) {
// No shareholders or deposit failed, add back to dividendQuoteBalance
dividendQuoteBalance += amount;
emit FlapTaxProcessorDividendDepositSkipped(taxToken, amount, "No shareholders or deposit failed");
} else {
totalQuoteSentToDividend += amount;
}
}
/// @notice Buy back tax tokens using quote tokens and burn them
/// @param quoteAmount The amount of quote tokens to use for buyback
/// @dev Uses duringBuyBack modifier - any tax received during swap goes directly to preBondBurnFunds
/// Refunds are NOT handled here - _reconcileBalance() in dispatch() catches all untracked balances
function _buyBackAndBurn(uint256 quoteAmount) internal duringBuyBack {
uint256 tokensBought;
if (feeConfig.isWeth) {
// Quote token is WETH - need to unwrap to native token for Portal swap
IWETH(weth).withdraw(quoteAmount);
IPortalTradeV2.ExactInputParams memory params = IPortalTradeV2.ExactInputParams({
inputToken: address(0), // Native token (BNB/ETH)
outputToken: taxToken,
inputAmount: quoteAmount,
minOutputAmount: 0, // No slippage protection
permitData: ""
});
try IPortal(portal).swapExactInput{value: quoteAmount, gas: maxBuyBackGasLimit}(params) returns (
uint256 received
) {
tokensBought = received;
// Wrap any remaining ETH back to WETH (refund will be picked up by _reconcileBalance)
uint256 ethBalance = address(this).balance;
if (ethBalance > 0) {
IWETH(weth).deposit{value: ethBalance}();
}
} catch {
// Swap failed, wrap ETH back
IWETH(weth).deposit{value: quoteAmount}();
preBondBurnFunds += quoteAmount;
emit FlapTaxProcessorBuyBackSkipped(taxToken, quoteAmount, gasleft(), "Swap failed");
return;
}
} else {
// Quote token is ERC20
address quoteTokenAddr = _getQuoteToken();
IERC20(quoteTokenAddr).safeApprove(portal, 0);
IERC20(quoteTokenAddr).safeApprove(portal, quoteAmount);
IPortalTradeV2.ExactInputParams memory params = IPortalTradeV2.ExactInputParams({
inputToken: quoteTokenAddr,
outputToken: taxToken,
inputAmount: quoteAmount,
minOutputAmount: 0, // No slippage protection
permitData: ""
});
try IPortal(portal).swapExactInput{gas: maxBuyBackGasLimit}(params) returns (uint256 received) {
tokensBought = received;
// Refund (if any) stays in balance - will be picked up by _reconcileBalance on next dispatch
} catch {
preBondBurnFunds += quoteAmount;
emit FlapTaxProcessorBuyBackSkipped(taxToken, quoteAmount, gasleft(), "Swap failed");
return;
}
}
// Burn the bought tokens by sending to FlapBlackHole
if (tokensBought > 0) {
IERC20(taxToken).safeTransfer(flapBlackHole, tokensBought);
emit FlapTaxProcessorBurnExecuted(taxToken, quoteAmount, tokensBought);
}
}
/// @notice Reconcile actual token balance with tracked balances
/// @dev Any untracked balance (from refunds, donations, etc.) goes to preBondBurnFunds
/// This is a "catch-all" that ensures no quote tokens are ever lost
function _reconcileBalance() internal {
address quoteTokenAddr = _getQuoteToken();
uint256 actualBalance = IERC20(quoteTokenAddr).balanceOf(address(this));
uint256 trackedBalance =
feeQuoteBalance + marketQuoteBalance + dividendQuoteBalance + lpQuoteBalance + preBondBurnFunds;
if (actualBalance > trackedBalance) {
uint256 untracked = actualBalance - trackedBalance;
preBondBurnFunds += untracked;
emit FlapTaxProcessorPortalRefund(taxToken, untracked, feeConfig.isWeth);
}
}
// --- Emergency Functions ---
/// @notice Withdraw all remaining tokens to a specified address
/// @param token Token address to withdraw (use address(0) for quote token or native ETH)
/// @param to Recipient address
function withdrawAll(address token, address to) external onlyOwner {
require(to != address(0), "TaxProcessor: zero address");
if (token == address(0)) {
// Default to withdraw quote token or native ETH
PackedFeeConfig memory config = feeConfig;
if (config.isWeth) {
// If quote token is WETH, withdraw native ETH
uint256 balance = address(this).balance;
if (balance > 0) {
(bool success,) = payable(to).call{value: balance}("");
require(success, "TaxProcessor: ETH transfer failed");
}
} else {
// Withdraw quote token
address quoteTokenAddr = _getQuoteToken();
uint256 balance = IERC20(quoteTokenAddr).balanceOf(address(this));
if (balance > 0) {
IERC20(quoteTokenAddr).safeTransfer(to, balance);
}
}
} else {
// Withdraw specific ERC20 token
uint256 balance = IERC20(token).balanceOf(address(this));
if (balance > 0) {
IERC20(token).safeTransfer(to, balance);
}
}
}
// --- Fallback ---
/// @notice Allow contract to receive native ETH
receive() external payable {}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
function __Ownable_init() internal onlyInitializing {
__Ownable_init_unchained();
}
function __Ownable_init_unchained() internal onlyInitializing {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuardUpgradeable is Initializable {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
function __ReentrancyGuard_init() internal onlyInitializing {
__ReentrancyGuard_init_unchained();
}
function __ReentrancyGuard_init_unchained() internal onlyInitializing {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return
success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
/// @notice Interface for the Dividend distribution contract
interface IDividend {
// --- Events ---
/// @notice Emitted when a user's share is updated
/// @param taxToken The tax token contract address
/// @param user The user whose share was changed
/// @param newShare The new share amount for the user
/// @param totalShares The total shares after the change
event FlapDividendShareChanged(
address indexed taxToken, address indexed user, uint256 newShare, uint256 totalShares
);
/// @notice Emitted when dividends are deposited into the contract
/// @param taxToken The tax token contract address
/// @param amount The amount of dividend tokens deposited
/// @param magnifiedDividendPerShare The updated magnified dividend per share
event FlapDividendDeposited(address indexed taxToken, uint256 amount, uint256 magnifiedDividendPerShare);
/// @notice Emitted when dividends are distributed to a user
/// @param taxToken The tax token contract address
/// @param user The user who received the dividends
/// @param amount The amount of dividends distributed
event FlapDividendDistributed(address indexed taxToken, address user, uint256 amount);
/// @notice Emitted when an address is excluded from receiving dividends
/// @param taxToken The tax token contract address
/// @param addr The address that was excluded
event FlapDividendAddressExcluded(address indexed taxToken, address addr);
/// @notice Emitted when a dividend withdrawal fails
/// @param taxToken The tax token contract address
/// @param user The user for whom the withdrawal failed
/// @param amount The amount that failed to be withdrawn
event FlapDividendWithdrawalFailed(address indexed taxToken, address user, uint256 amount);
/// @notice Emitted when a user's pending balance changes
/// @param taxToken The tax token contract address
/// @param user The user whose pending balance changed
/// @param pendingBalance The new pending balance
event FlapDividendPendingBalanceChanged(address indexed taxToken, address indexed user, uint256 pendingBalance);
/// @notice Emitted when a user's reward debt changes
/// @param taxToken The tax token contract address
/// @param user The user whose reward debt changed
/// @param rewardDebt The new reward debt
event FlapDividendRewardDebtChanged(address indexed taxToken, address indexed user, uint256 rewardDebt);
/// @notice Initialize the dividend contract
/// @param dividendToken_ The token used for dividend payments
/// @param taxToken_ The tax token contract address
/// @param minimumShareBalance_ The minimum balance required for dividend eligibility
function initialize(address dividendToken_, address taxToken_, uint256 minimumShareBalance_) external;
/// @notice Set user's share (only callable by FlapTaxToken)
/// @param user The user address
/// @param share The new share amount for the user
function setShare(address user, uint256 share) external;
/// @notice Deposit dividends to be distributed
/// @param amount The amount of dividend tokens to deposit
/// @return success Whether the deposit was successful
function deposit(uint256 amount) external returns (bool success);
/// @notice Batch distribute dividends to specified users
/// @param users Array of user addresses to distribute dividends to
/// @return successCount Number of successful distributions
function distributeDividend(address[] calldata users) external returns (uint256 successCount);
/// @notice User can call this to withdraw their own dividends (unwraps WETH to ETH if applicable)
/// @return success Whether the withdrawal was successful
function withdrawDividends() external returns (bool success);
/// @notice Withdraw dividends for a specific user
/// @param user The user address to withdraw for
/// @return success Whether the withdrawal was successful
function withdrawDividendsFor(address user) external returns (bool success);
/// @notice Withdraw dividends for a specific user with option to unwrap WETH
/// @param user The user address to withdraw for
/// @param unwrapWETH Whether to unwrap WETH to ETH
/// @return success Whether the withdrawal was successful
function withdrawDividendsFor(address user, bool unwrapWETH) external returns (bool success);
/// @notice Get the withdrawable dividend amount for a user
/// @param user The user address
/// @return The amount of dividends the user can claim
function withdrawableDividends(address user) external view returns (uint256);
/// @notice Exclude an address from receiving dividends
/// @param addr The address to exclude
function excludeAddress(address addr) external;
/// @notice Get total shares across all users
/// @return The total amount of shares
function totalShares() external view returns (uint256);
/// @notice Get the minimum share balance required for dividend eligibility
/// @return The minimum share balance
function minimumShareBalance() external view returns (uint256);
/// @notice Get total dividends withdrawn by a user
/// @param user The user address
/// @return The total amount of dividends withdrawn
function withdrawnDividends(address user) external view returns (uint256);
/// @notice Emergency withdraw function to recover tokens in case of emergency
/// @param token The token address to withdraw (use address(0) for native ETH)
/// @param amount The amount to withdraw (0 means withdraw all)
/// @param to The address to send the tokens to
function emergencyWithdraw(address token, uint256 amount, address to) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {IAccessControlUpgradeable} from "@openzeppelin-contracts-upgradeable/access/IAccessControlUpgradeable.sol";
import {IUniswapV3MintCallback} from "uni-v3-core/interfaces/callback/IUniswapV3MintCallback.sol";
import {IPancakeV3MintCallback} from "pancake-v3-core/interfaces/callback/IPancakeV3MintCallback.sol";
/// @title Common Types
/// @notice This interface defines common types shared across the portal
interface IPortalCommonTypes {
/// @dev curve Types
enum CurveType {
CURVE_LEGACY_15, // r = 15
CURVE_4, // r = 4
CURVE_0_974, // r = 0.974
CURVE_0_5, // r = 0.5
CURVE_1000, // r = 1000
CURVE_20000, // r = 20000
CURVE_2500, // r = 2500
CURVE_500, // r = 500
CURVE_2, // r = 2
CURVE_6, // r = 6
CURVE_75, // r = 75
CURVE_4M, // r= 4 M
CURVE_28, // r = 28
CURVE_21_25, // r = 21.25
CURVE_RH_UNUSED, // r = 27.6, h = 352755468
CURVE_RH_28D25_108002126, // r = 28.25, h = 108002126, k = 31301060059.5
CURVE_RH_14981_108002125, // r = 14981, h = 108002125, k = 16598979834625
CURVE_RH_TOSHI_MORPH_2ETH, // r = 0.7672, h = 107036751, k = 849318595.3672 - TOSHI/MORPH 2ETH curve
CURVE_RH_TOSHI, // r = 6140351, h = 107036752, k = 6797594227179952 - TOSHI Curve
CURVE_RH_BGB, // r = 767.5, h = 107036752, k = 849650707160 - BGB curve
CURVE_RH_BNB, // r = 6.14, h = 107036752, k = 6797205657.28 - BNB Curve
CURVE_RH_USD, // r = 3837, h = 107036752, k = 4247700017424 - USD curve
CURVE_RH_MONAD, // r = 50000, h = 107036752, k = 55351837600000 - MONAD curve
CURVE_RH_MONAD_V2, // r = 107400, h = 107036752, k = 118895747164800 - MONAD V2 curve
CURVE_RH_KGST // r = 380000, h = 107036752, k = 420673965760000 - KGST curve
}
/// @dev dex threshold types
enum DexThreshType {
TWO_THIRDS, // 66.67% supply
FOUR_FIFTHS, // 80% supply
HALF, // 50% supply
_95_PERCENT, // 95% supply
_81_PERCENT, // 81% supply
_1_PERCENT // 1% supply => mainly for testing
}
/// @notice Fee profile for tokens
/// @dev Determines the fee structure applied to a token's trades and liquidity operations
/// Fees are represented in basis points (bps), where 1% = 100 bps
enum FlapFeeProfile {
FEE_GLOBAL_DEFAULT, // Default fee profile used when no specific profile is set for a token
FEE_FLAPSALE_V0 // Fee profile for FlapSale V0
}
//
// Custom Errors for Common Types
//
/// @notice error if the curve type is invalid
/// @param curveType The invalid curve type
error InvalidCurveType(CurveType curveType);
/// @notice error if the dex threshold type is invalid
error InvalidDexThresholdType(DexThreshType threshold);
}
/// @title Types and Structs
/// @notice This interface defines the types and structs used in the portal
interface IPortalTypes is IPortalCommonTypes {
//
// public constants
//
//
// Types and Structs
//
/// @dev Profile types for deployment-specific parameters or behaviors
enum Profile {
DEFAULT,
TOSHI_MART,
X_LAYER,
MORPH,
MONAD
}
/// @dev Token version
/// Which token implementation is used
enum TokenVersion {
TOKEN_LEGACY_MINT_NO_PERMIT,
TOKEN_LEGACY_MINT_NO_PERMIT_DUPLICATE, // for historical reasons, both 0 and 1 are the same: TOKEN_LEGACY_MINT_NO_PERMIT
TOKEN_V2_PERMIT, // 2
TOKEN_GOPLUS, // 3
TOKEN_TAXED, // 4: The original tax token (FlapTaxToken)
TOKEN_TAXED_V2 // 5: The new advanced tax token (FlapTaxTokenV2)
}
/// @dev the quote token, i.e, the token as the reserve
enum QuoteTokenType {
NATIVE_GAS_TOKEN, // The native gas token
ERC20_TOKEN_WITH_PERMIT, // The ERC20 token with permit
ERC20_TOKEN_WITHOUT_PERMIT // The ERC20 token without permit
}
/// @notice the status of a token
/// The token has 5 statuses:
// - Tradable: The token can be traded(buy/sell)
// - InDuel: (obsolete) The token is in a battle, it can only be bought but not sold.
// - Killed: (obsolete) The token is killed, it can not be traded anymore. Can only be redeemed for another token.
// - DEX: The token has been added to the DEX
// - Staged: The token is staged but not yet created (address is predetermined)
enum TokenStatus {
Invalid, // The token does not exist
Tradable,
InDuel, // obsolete
Killed, // obsolete
DEX,
Staged // The token is staged (address determined, but not yet created)
}
/// @notice the migrator type
/// @dev the migrator type determines how the liquidity is added to the DEX.
/// Note: To mitigate the risk of DOS, if a V3 migrator is used but the liquidity cannot
/// be added to v3 pools, the migrator will fallback to a V2 migrator.
/// A TAX token must use a V2 migrator.
enum MigratorType {
V3_MIGRATOR, // Migrate the liquidity to a Uniswap V3 like pool
V2_MIGRATOR // Migrate the liquidity to a Uniswap V2 like pool
}
/// @notice the V3 LP fee profile
/// @dev determines the LP fee tier to use when migrating tokens to Uniswap V3 or Pancake V3
enum V3LPFeeProfile {
LP_FEE_PROFILE_STANDARD, // Standard fee tier: 0.25% on PancakeSwap, 0.3% on Uniswap
LP_FEE_PROFILE_LOW, // Low fee tier: typically, 0.01% on PancakeSwap, 0.05% on Uniswap
LP_FEE_PROFILE_HIGH // High fee tier (1% for exotic pairs)
}
/// @notice the DEX ID
/// @dev determines the DEX we want to migrate to
/// On BSC:
/// - only DEX0 will be enabled, which is PancakeSwap
/// On xLayer:
/// - only DEX0 will be enabled, which is PotatoSwap
/// On Monad:
/// - DEX0 is Uniswap
/// - DEX1 is PancakeSwap
/// - DEX2 is Monday
/// Note that, currently, we only support at most 3 DEXes
/// We may add more DEXes in the future if needed
enum DEXId {
DEX0,
DEX1,
DEX2
}
/// @notice the state of a token (with dex related fields)
struct TokenStateV2 {
TokenStatus status; // the status of the token
uint256 reserve; // the reserve of the token
uint256 circulatingSupply; // the circulatingSupply of the token
uint256 price; // the price of the token
TokenVersion tokenVersion; // the version of the token implementation this token is using
uint256 r; // the r of the curve of the token
uint256 dexSupplyThresh; // the cirtulating supply threshold for adding the token to the DEX
}
/// @notice the state of a token (with all V2 fields plus quoteTokenAddress and nativeToQuoteSwapEnabled)
struct TokenStateV3 {
/// The status of the token (see TokenStatus enum)
TokenStatus status;
/// The reserve amount of the quote token held by the bonding curve
uint256 reserve;
/// The circulating supply of the token
uint256 circulatingSupply;
/// The current price of the token (in quote token units, 18 decimals)
uint256 price;
/// The version of the token implementation (see TokenVersion enum)
TokenVersion tokenVersion;
/// The curve parameter 'r' used for the bonding curve
uint256 r;
/// The circulating supply threshold for adding the token to the DEX
uint256 dexSupplyThresh;
/// The address of the quote token (address(0) if native gas token)
address quoteTokenAddress;
/// Whether native-to-quote swap is enabled for this token
bool nativeToQuoteSwapEnabled;
}
/// @notice the state of a token (with all V3 fields plus extensionID and 'r' curve parameter only)
struct TokenStateV4 {
/// The status of the token (see TokenStatus enum)
TokenStatus status;
/// The reserve amount of the quote token held by the bonding curve
uint256 reserve;
/// The circulating supply of the token
uint256 circulatingSupply;
/// The current price of the token (in quote token units, 18 decimals)
uint256 price;
/// The version of the token implementation (see TokenVersion enum)
TokenVersion tokenVersion;
/// The curve parameter 'r' used for the bonding curve
uint256 r;
/// The circulating supply threshold for adding the token to the DEX
uint256 dexSupplyThresh;
/// The address of the quote token (address(0) if native gas token)
address quoteTokenAddress;
/// Whether native-to-quote swap is enabled for this token
bool nativeToQuoteSwapEnabled;
/// The extension ID used by the token (bytes32(0) if no extension)
bytes32 extensionID;
}
/// @notice the state of a token (with all V4 fields plus all curve parameters)
struct TokenStateV5 {
/// The status of the token (see TokenStatus enum)
TokenStatus status;
/// The reserve amount of the quote token held by the bonding curve
uint256 reserve;
/// The circulating supply of the token
uint256 circulatingSupply;
/// The current price of the token (in quote token units, 18 decimals)
uint256 price;
/// The version of the token implementation (see TokenVersion enum)
TokenVersion tokenVersion;
/// The curve parameter 'r' used for the bonding curve
uint256 r;
/// The curve parameter 'h' - virtual token reserve
uint256 h;
/// The curve parameter 'k' - square of virtual liquidity
uint256 k;
/// The circulating supply threshold for adding the token to the DEX
uint256 dexSupplyThresh;
/// The address of the quote token (address(0) if native gas token)
address quoteTokenAddress;
/// Whether native-to-quote swap is enabled for this token
bool nativeToQuoteSwapEnabled;
/// The extension ID used by the token (bytes32(0) if no extension)
bytes32 extensionID;
}
/// @notice the state of a token (with all V5 fields plus taxRate, pool, and progress)
struct TokenStateV6 {
/// The status of the token (see TokenStatus enum)
TokenStatus status;
/// The reserve amount of the quote token held by the bonding curve
uint256 reserve;
/// The circulating supply of the token
uint256 circulatingSupply;
/// The current price of the token (in quote token units, 18 decimals)
uint256 price;
/// The version of the token implementation (see TokenVersion enum)
TokenVersion tokenVersion;
/// The curve parameter 'r' used for the bonding curve
uint256 r;
/// The curve parameter 'h' - virtual token reserve
uint256 h;
/// The curve parameter 'k' - square of virtual liquidity
uint256 k;
/// The circulating supply threshold for adding the token to the DEX
uint256 dexSupplyThresh;
/// The address of the quote token (address(0) if native gas token)
address quoteTokenAddress;
/// Whether native-to-quote swap is enabled for this token
bool nativeToQuoteSwapEnabled;
/// The extension ID used by the token (bytes32(0) if no extension)
bytes32 extensionID;
/// The tax rate in basis points (0 if not a tax token)
uint256 taxRate;
/// The DEX pool address (address(0) if not listed on DEX)
address pool;
/// The progress towards DEX listing (0 to 1e18, where 1e18 = 100%)
uint256 progress;
}
/// @notice the state of a token (with all V6 fields plus lpFeeProfile)
struct TokenStateV7 {
/// The status of the token (see TokenStatus enum)
TokenStatus status;
/// The reserve amount of the quote token held by the bonding curve
uint256 reserve;
/// The circulating supply of the token
uint256 circulatingSupply;
/// The current price of the token (in quote token units, 18 decimals)
uint256 price;
/// The version of the token implementation (see TokenVersion enum)
TokenVersion tokenVersion;
/// The curve parameter 'r' used for the bonding curve
uint256 r;
/// The curve parameter 'h' - virtual token reserve
uint256 h;
/// The curve parameter 'k' - square of virtual liquidity
uint256 k;
/// The circulating supply threshold for adding the token to the DEX
uint256 dexSupplyThresh;
/// The address of the quote token (address(0) if native gas token)
address quoteTokenAddress;
/// Whether native-to-quote swap is enabled for this token
bool nativeToQuoteSwapEnabled;
/// The extension ID used by the token (bytes32(0) if no extension)
bytes32 extensionID;
/// The tax rate in basis points (0 if not a tax token)
uint256 taxRate;
/// The DEX pool address (address(0) if not listed on DEX)
address pool;
/// The progress towards DEX listing (0 to 1e18, where 1e18 = 100%)
uint256 progress;
/// The V3 LP fee profile for the token
V3LPFeeProfile lpFeeProfile;
/// The Dex Id
DEXId dexId;
}
/// @notice Parameters for creating a new token (V2)
struct NewTokenV2Params {
/// The name of the token
string name;
/// The symbol of the token
string symbol;
/// The metadata URI of the token
string meta;
/// The DEX supply threshold type
DexThreshType dexThresh;
/// The salt for deterministic deployment
bytes32 salt;
/// The tax rate in basis points (if non-zero, this is a tax token)
uint16 taxRate;
/// The migrator type (see MigratorType enum)
MigratorType migratorType;
/// The quote token address (native gas token if zero address)
address quoteToken;
/// The initial quote token amount to spend for buying
uint256 quoteAmt;
/// The beneficiary address for the token
/// For rev share tokens, this is the address that can claim the LP fees
/// For tax tokens, this is the address that receives the tax fees
address beneficiary;
/// The optional permit data for the quote token
bytes permitData;
}
/// @notice Parameters for creating a new token (V3) with extension support
struct NewTokenV3Params {
/// The name of the token
string name;
/// The symbol of the token
string symbol;
/// The metadata URI of the token
string meta;
/// The DEX supply threshold type
DexThreshType dexThresh;
/// The salt for deterministic deployment
bytes32 salt;
/// The tax rate in basis points (if non-zero, this is a tax token)
uint16 taxRate;
/// The migrator type (see MigratorType enum)
MigratorType migratorType;
/// The quote token address (native gas token if zero address)
address quoteToken;
/// The initial quote token amount to spend for buying
uint256 quoteAmt;
/// The beneficiary address for the token
/// For rev share tokens, this is the address that can claim the LP fees
/// For tax tokens, this is the address that receives the tax fees
address beneficiary;
/// The optional permit data for the quote token
bytes permitData;
/// @notice The ID of the extension to be used for the new token if not zero
bytes32 extensionID;
/// @notice Additional extension specific data to be passed to the extension's `onTokenCreation` method, check the extension's documentation for details on the expected format and content.
bytes extensionData;
}
/// @notice Parameters for creating a new token (V4) with DEX ID and LP fee profile support
struct NewTokenV4Params {
/// The name of the token
string name;
/// The symbol of the token
string symbol;
/// The metadata URI of the token
string meta;
/// The DEX supply threshold type
DexThreshType dexThresh;
/// The salt for deterministic deployment
bytes32 salt;
/// The tax rate in basis points (if non-zero, this is a tax token)
uint16 taxRate;
/// The migrator type (see MigratorType enum)
MigratorType migratorType;
/// The quote token address (native gas token if zero address)
address quoteToken;
/// The initial quote token amount to spend for buying
uint256 quoteAmt;
/// The beneficiary address for the token
/// For rev share tokens, this is the address that can claim the LP fees
/// For tax tokens, this is the address that receives the tax fees
address beneficiary;
/// The optional permit data for the quote token
bytes permitData;
/// @notice The ID of the extension to be used for the new token if not zero
bytes32 extensionID;
/// @notice Additional extension specific data to be passed to the extension's `onTokenCreation` method, check the extension's documentation for details on the expected format and content.
bytes extensionData;
/// @notice The preferred DEX ID for the token
DEXId dexId;
/// @notice The preferred V3 LP fee profile for the token
V3LPFeeProfile lpFeeProfile;
}
/// @notice Parameters for creating a new token (V5) with tax V2 support
struct NewTokenV5Params {
/// The name of the token
string name;
/// The symbol of the token
string symbol;
/// The metadata URI of the token
string meta;
/// The DEX supply threshold type
DexThreshType dexThresh;
/// The salt for deterministic deployment
bytes32 salt;
/// The tax rate in basis points (if non-zero, this is a tax token)
uint16 taxRate;
/// The migrator type (see MigratorType enum)
MigratorType migratorType;
/// The quote token address (native gas token if zero address)
address quoteToken;
/// The initial quote token amount to spend for buying
uint256 quoteAmt;
/// The beneficiary address for the token
/// For rev share tokens, this is the address that can claim the LP fees
/// For tax tokens, this is the address that receives the tax fees
address beneficiary;
/// The optional permit data for the quote token
bytes permitData;
/// @notice The ID of the extension to be used for the new token if not zero
bytes32 extensionID;
/// @notice Additional extension specific data to be passed to the extension's `onTokenCreation` method, check the extension's documentation for details on the expected format and content.
bytes extensionData;
/// @notice The preferred DEX ID for the token
DEXId dexId;
/// @notice The preferred V3 LP fee profile for the token
V3LPFeeProfile lpFeeProfile;
// New V5 tax-specific fields (only used when taxRate > 0)
/// Tax duration in seconds (max: 100 years)
uint64 taxDuration;
/// Anti-farmer duration in seconds (max: 1 year)
uint64 antiFarmerDuration;
/// Market allocation basis points (to beneficiary)
uint16 mktBps;
/// Deflation basis points (burned)
uint16 deflationBps;
/// Dividend basis points (to dividend contract)
uint16 dividendBps;
/// Liquidity provision basis points (LP to dead address)
uint16 lpBps;
/// Minimum balance for dividend eligibility (min: 10K ether, required when dividendBps > 0)
uint256 minimumShareBalance;
}
/// @notice Parameters for staging a new token (V5) - immutable parameters only
struct StageNewTokenV5Params {
/// The DEX supply threshold type
DexThreshType dexThresh;
/// The salt for deterministic deployment
bytes32 salt;
/// Whether this is a tax token
bool isTaxToken;
/// The migrator type (see MigratorType enum)
MigratorType migratorType;
/// The quote token address (native gas token if zero address)
address quoteToken;
/// @notice The preferred DEX ID for the token
DEXId dexId;
}
/// @notice Parameters for committing a staged token (V5) - mutable/deployment-time parameters
struct CommitNewTokenV5Params {
/// The salt for deterministic deployment (must match staged salt)
bytes32 salt;
/// The tax rate in basis points (0 for non-tax tokens)
uint16 taxRate;
/// The name of the token
string name;
/// The symbol of the token
string symbol;
/// The metadata URI of the token
string meta;
/// The initial quote token amount to spend for buying
uint256 quoteAmt;
/// The beneficiary address for the token
/// For rev share tokens, this is the address that can claim the LP fees
/// For tax tokens, this is the address that receives the tax fees
address beneficiary;
/// The optional permit data for the quote token
bytes permitData;
// New V5 tax-specific fields (only used when taxRate > 0)
/// Tax duration in seconds (max: 100 years)
uint64 taxDuration;
/// Anti-farmer duration in seconds (max: 1 year)
uint64 antiFarmerDuration;
/// Market allocation basis points (to beneficiary)
uint16 mktBps;
/// Deflation basis points (burned)
uint16 deflationBps;
/// Dividend basis points (to dividend contract)
uint16 dividendBps;
/// Liquidity provision basis points (LP to dead address)
uint16 lpBps;
/// Minimum balance for dividend eligibility (required when dividendBps > 0)
uint256 minimumShareBalance;
}
/// @dev The configuration of the "native to quote" swap
/// i.e How to swap ETH for the quote token when the quote token is not ETH
enum NativeToQuoteSwapType {
SWAP_DISABLED, // 0: disabled
SWAP_VIA_V2_POOL, // 1: swap through v2 pool
SWAP_VIA_V3_2500_POOL, // 2: swap through v3 2500 pool
SWAP_VIA_V3_500_POOL, // 3: swap through v3 500 pool
SWAP_VIA_V3_3000_POOL, // 4: swap through v3 3000 pool
SWAP_VIA_V3_10000_POOL // 5: swap through v3 10000 pool
}
/// @dev the quote token configurations
struct QuoteTokenConfiguration {
uint8 enabled; // 8bit: 1 if allowed, 0 if not allowed
CurveType defaultCurve; // 8bit: the default token curve type of the quote token
CurveType alternativeCurve; // 8bit: the alternative token curve type of the quote token
NativeToQuoteSwapType nativeToQuoteSwapType; // 8bit: the native to quote swap feature configuration of the quote token
uint8 dexId; // 8bit: DEX ID for multiple DEXes support
}
/// @dev Enum for DEX pool types
enum PoolType {
V2, // Uniswap V2 style pools
V3 // Uniswap V3 style pools
}
/// @dev Packed DEX pool information
struct PackedDexPool {
address pool; // 160 bits: pool address
uint24 fee; // 24 bits: fee tier (for V3), 0 for V2
PoolType poolType; // 8 bits: enum for pool type
uint64 unused; // 64 bits: reserved for future use
}
//
// Events
//
/// @notice emitted when a token is staged (but not yet created)
///
/// @param ts The timestamp of the event
/// @param creator The address of the creator
/// @param token The predetermined address of the token
event FlapTokenStaged(uint256 ts, address creator, address token);
/// @notice emitted when a new token is created
///
/// @param ts The timestamp of the event
/// @param creator The address of the creator
/// @param nonce The nonce of the token
/// @param token The address of the token
/// @param name The name of the token
/// @param symbol The symbol of the token
/// @param meta The meta URI of the token
event TokenCreated(
uint256 ts, address creator, uint256 nonce, address token, string name, string symbol, string meta
);
/// @notice emitted when a token is bought
///
/// @param ts The timestamp of the event
/// @param token The address of the token
/// @param buyer The address of the buyer
/// @param amount The amount of tokens bought
/// @param eth The amount of ETH spent
/// @param fee The amount of ETH spent on fee
/// @param postPrice The price of the token after this trade
event TokenBought(
uint256 ts, address token, address buyer, uint256 amount, uint256 eth, uint256 fee, uint256 postPrice
);
/// @notice emitted when a token is sold
///
/// @param ts The timestamp of the event
/// @param token The address of the token
/// @param seller The address of the seller
/// @param amount The amount of tokens sold
/// @param eth The amount of ETH received
/// @param fee The amount of ETH deducted as a fee
/// @param postPrice The price of the token after this trade
event TokenSold(
uint256 ts, address token, address seller, uint256 amount, uint256 eth, uint256 fee, uint256 postPrice
);
/// emitted when a token's curve is set
/// @param token The address of the token
/// @param curve The address of the curve
/// @param curveParameter The parameter of the curve
event TokenCurveSet(address token, address curve, uint256 curveParameter);
/// @notice emitted when a token's curve parameters are set (V2)
/// @param token The address of the token
/// @param r The virtual ETH reserve parameter
/// @param h The virtual token reserve parameter
/// @param k The square of the virtual Liquidity parameter
event TokenCurveSetV2(address token, uint256 r, uint256 h, uint256 k);
/// emitted when a token's dexSupplyThresh is set
/// @param token The address of the token
/// @param dexSupplyThresh The new dexSupplyThresh of the token
event TokenDexSupplyThreshSet(address token, uint256 dexSupplyThresh);
/// emitted when a token's implementation is set
/// @param token The address of the token
/// @param version The version of the token
event TokenVersionSet(address token, TokenVersion version);
/// @notice emitted when a new vanity token is created
/// @param token The address of the created token
/// @param creator The address of the creator
/// @param beneficiary The address of the beneficiary
event VanityTokenCreated(address token, address creator, address beneficiary);
/// @notice emitted when a token's quote token is set
/// @param token The address of the token
/// @param quoteToken The address of the quote token
event TokenQuoteSet(address token, address quoteToken);
/// @notice emitted when a token's migrator is set
/// @param token The address of the token
/// @param migratorType The migrator type
event TokenMigratorSet(address token, MigratorType migratorType);
/// @notice emitted when a token's extension is enabled
/// @param token The address of the token
/// @param extensionID The extension ID
/// @param extensionAddress The address of the extension contract
/// @param version The version of the extension
event TokenExtensionEnabled(address token, bytes32 extensionID, address extensionAddress, uint8 version);
/// @notice emitted when a token's pool info is updated
/// @param token The address of the token
/// @param poolInfo The new pool information
event TokenPoolInfoUpdated(address token, PackedDexPool poolInfo);
/// @notice emitted when a trader's fee exemption status is updated
/// @param trader The address of the trader
/// @param isExempted Whether the trader is exempted from fees
event FeeExemptionUpdated(address indexed trader, bool isExempted);
//
// events
//
/// @notice emitted when token is redeemed
/// @param ts The timestamp of the event
/// @param srcToken The address of the token to redeem
/// @param dstToken The address of the token to receive
/// @param srcAmount The amount of srcToken to redeem
/// @param dstAmount The amount of dstToken to receive
/// @param who The address of the redeemer
event TokenRedeemed(
uint256 ts, address srcToken, address dstToken, uint256 srcAmount, uint256 dstAmount, address who
);
/// @notice emitted when the bit flags are changed
/// @param oldFlags The old flags
/// @param newFlags The new flags
event BitFlagsChanged(uint256 oldFlags, uint256 newFlags);
/// @notice emitted when adding liquidity to DEX
/// @param token The address of the token
/// @param pool The address of the pool
/// @param amount The amount of token added
/// @param eth The amount of quote Token added
event LaunchedToDEX(address token, address pool, uint256 amount, uint256 eth);
/// @notice emitted when the progress of a token changes
/// @param token The address of the token
/// @param newProgress The new progress value in Wad
event FlapTokenProgressChanged(address token, uint256 newProgress);
//
// Token V2 supply change
//
/// @notice emitted when the circulating supply of a token changes
/// @param token The address of the token
/// @param newSupply The new circulating supply
event FlapTokenCirculatingSupplyChanged(address token, uint256 newSupply);
/// @notice emitted when a new tax is set for a token
/// @param token The address of the token
/// @param tax The tax value set for the token
event FlapTokenTaxSet(address token, uint256 tax);
// operation related
// should remove later
/// @notice emitted when a users successfully checked in
/// @param user The address of the user
event CheckedIn(address user);
/// @notice emitted when a beneficiary claims fees
/// @param token The address of the token
/// @param beneficiary The address of the beneficiary
/// @param tokenAmount The amount of the token claimed
/// @param ethAmount The amount of ETH claimed
event BeneficiaryClaimed(address token, address beneficiary, uint256 tokenAmount, uint256 ethAmount);
/// @notice emitted when a token beneficiary is changed
/// @param token The address of the token
/// @param oldBeneficiary The previous beneficiary address
/// @param newBeneficiary The new beneficiary address
event BeneficiaryChanged(address token, address oldBeneficiary, address newBeneficiary);
/// @notice emitted when an extension is registered
/// @param extensionId The unique identifier for the extension
/// @param extensionAddress The address of the extension contract
/// @param version The version of the extension
event ExtensionRegistered(bytes32 extensionId, address extensionAddress, uint8 version);
/// @notice emitted when a spammer's blocked status is changed
/// @param spammer The address of the spammer
/// @param blocked True if blocked, false if unblocked
event SpammerBlockedStatusChanged(address spammer, bool blocked);
/// @notice emitted when a user is rate limited from creating tokens
/// @param user The address of the rate-limited user
/// @param lastCreationTime The timestamp of their last successful token creation
event RateLimited(address user, uint256 lastCreationTime);
/// @notice emitted when a quote token configuration is set
/// @param quoteToken The address of the quote token
/// @param config The configuration set for the quote token
event QuoteTokenConfigurationSet(address quoteToken, QuoteTokenConfiguration config);
/// @notice emitted when a V3 favored fee is set for a quote token
/// @param quoteToken The address of the quote token
/// @param favoredFee The favored fee set for the quote token
event V3FavoredFeeSet(address quoteToken, uint24 favoredFee);
/// @notice emitted when a token's DEX preference is set
/// @param token The address of the token
/// @param dexId The preferred DEX ID for the token
/// @param lpFeeProfile The preferred V3 LP fee profile for the token
event TokenDexPreferenceSet(address token, DEXId dexId, V3LPFeeProfile lpFeeProfile);
/// @notice emitted when a message is sent
/// @param sender The address of the sender
/// @param token The address of the token
/// @param message The message sent
event MsgSent(address sender, address token, string message);
//
// Custom Errors
//
/// @notice error if the dex is both pancake and algebra1.9
/// which is impossible
error DEXCannotBeBothPancakeAndAlgebra1_9();
/// @notice error if the portal lens address is zero
error PortalLensCannotBeZero();
/// @notice error if the multi dex router address is zero
error MultiDexRouterCannotBeZero();
/// @notice error if the token does not exist
error TokenNotFound(address token);
/// @notice error if the amount is too small
error AmountTooSmall(uint256 amount);
/// @notice error if slippage is too high
/// i.e: actualAmount < minAmount
error SlippageTooHigh(uint256 actualAmount, uint256 minAmount);
/// @notice error if the input token & output token of a swap is the same
error SameToken(address tokenA);
/// @notice error if trying to trade a killed token
error TokenKilled(address token);
/// @notice error if token is not tradable
error TokenNotTradable(address token);
/// @notice error if trying to sell a token that is in a battle
error TokenInDuel(address token);
/// @notice error if trying to redeem a token that is not killed
error TokenNotKilled(address token);
/// @notice error if the token has already been added to the DEX
error TokenAlreadyDEXed(address token);
/// @notice error if the token has already been staged or created
error TokenAlreadyStaged(address token);
/// @notice error if the token is not in staged status
error TokenNotStaged(address token);
/// @notice error if the token is not listed on DEX yet
error TokenNotDEXed(address token);
/// @notice error if there is no conversion path from srcToken to dstToken
error NoConversionPath(address srcToken, address dstToken);
/// @notice error if the round is not found
error RoundNotFound(uint256 id);
/// @notice error if the round id is invalid
error InvalidRoundID(uint256 id);
/// @notice error if try to start a new round but the last round is not resolved
error LastRoundNotResolved();
/// @notice cannot use a token for the next round of the game
error InvalidTokenForBattle(address token);
/// @notice error if the signature is invalid
error InvalidSigner(address signer);
/// @notice error if the seq is not found in Game queue
error SeqNotFound(uint256 seq);
/// @notice error if not implemented yet
error NotImplemented();
/// @notice error a token is already in the game
error TokenAlreadyInGame(address token);
/// @notice error if a call reverted but without any data
error CallReverted();
/// @notice error if creating token is disabled
error PermissionlessCreateDisabled();
/// @notice error if trading is disabled
error TradeDisabled();
/// @notice error if the circuit breakers are off
error ProtocolDisabled();
/// @notice error if the game supply threshold is not valid
error InvalidGameSupplyThreshold();
/// @notice error if the dex supply threshold is not valid
error InvalidDEXSupplyThreshold();
/// @notice error if the proof does not match the msg.sender
error MismatchedAddressInProof(address expected, address actual);
/// @notice error if the whitlist creator cannot create more tokens
error NoQuotaForCreator(uint256 created, uint256 max);
/// @notice error if the piggyback lenght is not valid
error InvalidPiggybackLength(uint256 expected, uint256 actual);
/// @notice error if the feature is disabled
error FeatureDisabled();
/// @notice error if caller is not authorized (e.g., only SaleForge can call)
error OnlySaleForge();
/// @notice error if the quote token is not allowed
error QuoteTokenNotAllowed(address quoteToken);
/// @notice error if a native to quote swap is required but not supported
/// native to quote swap: i.e, swap the input token to the desired quote token
error NativeToQuoteSwapNotSupported();
/// @notice error if the native to quote swap v3 fee type is not supported
/// @param NativeToQuoteSwapType The unsupported native to quote swap type
error NativeToQuoteSwapFeeTierNotSupported(uint8 NativeToQuoteSwapType);
/// @notice error if met any dirty bits
error DirtyBits();
//
// Dex Related
//
/// @notice error if sqrPriceA is gte than sqrtPriceB
error PriceAMustLTPriceB(uint160 sqrtPriceA, uint160 sqrtPriceB);
/// @notice error if the actual amount is more than the expected amount
error ActualAmountMustLTEAmount(uint256 actualAmount, uint256 amount1);
/// @notice error if the msg.sender is not a Uniswap V3 pool
error NotUniswapV3Pool(address sender);
/// @notice error if the uniswap v2 pool's liquidity is not zero
error UniswapV2PoolNotZero(address pool, uint256 liquidity);
/// @notice error if the required token amount for adding Uniswap v2 liquidity is more than the remaining token
error RequiredTokenMustLTE(uint256 requiredToken, uint256 reserveToken);
/// @notice revert when calling slot0 of a Uniswap V3 pool failed
error UniswapV3Slot0Failed();
/// @notice error if a non-position NFT is received
error NonPositionNFTReceived(address collection);
/// @notice error if the provided dex threshold is invalid
error InvalidDexThreshold(uint256 threshold);
/// @notice error if the provided address is not a valid pool
error InvalidPoolAddress(address pool, address expected);
//
// staking related
//
/// @notice error if the locks are invalid
error InvalidLocks();
/// @notice error if staking feature is not enabled
error StakingDisabled();
/// @notice error if the operator does not have the roller role
error NotRoller();
// operation related
/// @notice error if the user cannot check in yet
/// @param next The timestamp when the user can check in again
error cannotCheckInUntil(uint256 next);
// misc
/// @notice error if another token has the same meta
error MetaAlreadyUsedByOtherToken(string meta);
/// @notice error if the creation fee is insufficient
error InsufficientCreationFee(uint256 required, uint256 provided);
/// @notice error if the provided ETH is insufficient to cover the required fee
/// @param required The required fee amount
/// @param provided The provided ETH amount
error InsufficientFee(uint256 required, uint256 provided);
/// @notice error if the vanity address requirement is not met
/// @param token The generated token address
error VanityAddressRequirementNotMet(address token);
/// @notice error if the token is not in DEX status
error TokenNotInDEXStatus(address token);
/// @notice error if the caller is not the token's beneficiary
error CallerNotBeneficiary(address caller, address expected);
/// @notice error if no locks are available for the token
error NoLocksAvailable(address token);
/// @notice error if the provided ETH is insufficient to cover the required input amount
/// @param provided The provided ETH amount
/// @param required The required ETH amount
error InsufficientEth(uint256 provided, uint256 required);
/// @notice error if the provided tax bps is invalid
/// @param tax The provided tax bps
error InvalidTaxBps(uint256 tax);
/// @notice error if the transferFrom call failed
/// @param token The address of the token
/// @param from The address from which the tokens were to be transferred
/// @param amount The amount of tokens that were to be transferred
error TransferFromFailed(address token, address from, uint256 amount);
/// @notice error if the migrator type is invalid
error InvalidMigratorType();
/// @notice error if the quote token is not native but not using PortalTradeV2
error QuoteTokenNotNativeButNotUsingTradeV2();
/// @notice error if the msg.value is less than expected value when creating
/// a tax token using an ERC20 (e.g: USDC) token as the quote token.
/// @dev For the tax token's tax splitter to work properly, we need approximately 1gwei due to our
/// implemenation of the tax splitter.
error InsufficientValueForTaxTokenCreation(uint256 expected, uint256 provided);
/// @notice error if the caller is not a guardian or admin
/// @param caller The address of the caller
error NotGuardian(address caller);
/// @notice error if the extension version is not supported
/// @param version The unsupported extension version
error UnsupportedExtensionVersion(uint8 version);
/// @notice error if the token uses an extension but is traded through the legacy PortalTrade contract
/// @param token The address of the token with an extension
error TokenWithExtensionNotSupported(address token);
/// @notice error if the parameters for ToshiMart are invalid
error InvalidParamsForToshiMart();
/// @notice error if the parameters for X_LAYER are invalid
error InvalidParamsForXLayer();
/// @notice error if the quote token configuration is invalid
error InvalidQuoteTokenConfiguration();
/// @notice error when trying to use PortalTrade with tokens that have non-zero h parameter
error ErrShouldUsePortalTradeV2();
/// @notice error when no supported DEX is found for the token
error NoSupportedDEX();
/// @notice invalid fee tier for DEX
error InvalidFeeTierForDEX();
/// @notice error if the tax distribution percentages don't add up to 100%
error InvalidTaxDistribution();
/// @notice error if tax duration exceeds maximum allowed
error TaxDurationTooLong();
/// @notice error if tax duration is less than minimum allowed
error TaxDurationTooShort();
/// @notice error if anti-farmer duration exceeds maximum allowed
error AntiFarmerDurationTooLong();
/// @notice error if anti-farmer duration is less than minimum allowed
error AntiFarmerDurationTooShort();
/// @notice error if minimum share balance is too low for dividend eligibility
error MinimumShareBalanceTooLow();
/// @notice error when dividend parameters are missing but required
error DividendParametersRequired();
/// @notice error when user is rate limited from creating tokens
/// @param user The address that is rate limited
/// @param lastCreationTime The timestamp of the user's last successful token creation
error RateLimitExceeded(address user, uint256 lastCreationTime);
}
/// @title IPortalLauncherTwoStep
/// @notice Interface for the two-step token launch process
interface IPortalLauncherTwoStep is IPortalTypes {
/// @notice Stage a new token (V5) without creating it yet
/// @param params The immutable parameters for the new token
/// @return token The predetermined address of the token
/// @dev This stages a token by validating parameters and reserving the token address.
/// The token contract is not deployed yet. Call commitNewTokenV5 to actually deploy it.
/// Extensions are not supported in two-step launch. LP fee profile is always STANDARD.
/// FIXME: this is not enabled in this version. Will be available once the audit for this part is ready.
function stageNewTokenV5(StageNewTokenV5Params calldata params) external returns (address token);
/// @notice Commit a staged token (V5) and create it
/// @param params The parameters for token deployment (includes salt to identify staged token)
/// @dev This deploys the token contract and initializes it. The token must have been staged first via stageNewTokenV5.
/// The salt and isTaxToken in params must match what was used during staging.
/// If taxRate > 0, creates FlapTaxTokenV2, otherwise creates regular token.
/// FIXME: this is not enabled in this version. Will be available once the audit for this part is ready.
function commitNewTokenV5(CommitNewTokenV5Params calldata params) external payable;
}
/// @notice Handles token creation and related operations
interface IPortalLauncher is IPortalTypes {
/// @notice Create a new token (V2) with flexible parameters
/// @param params The parameters for the new token
/// @return token The address of the created
/// @dev due to the implementation limit, when creating a tax token and using an ERC20 token as the quote token,
/// You need to pay an extra 1gwei native gas token (i.e msg.value = 1 gwei), or you will encounter an InsufficientValueForTaxTokenCreation error
function newTokenV2(NewTokenV2Params calldata params) external payable returns (address token);
/// @notice Create a new token (V3) with extension support
/// @param params The parameters for the new token including extension configuration
/// @return token The address of the created token
/// @dev Similar to newTokenV2 but with extension support. Extension hooks will be called if extensionID is non-zero
function newTokenV3(NewTokenV3Params calldata params) external payable returns (address token);
/// @notice Create a new token (V4) with DEX ID and LP fee profile support
/// @param params The parameters for the new token including DEX ID and LP fee profile
/// @return token The address of the created token
/// @dev Similar to newTokenV3 but with DEX ID and LP fee profile support. Allows specifying preferred DEX and fee tier
function newTokenV4(NewTokenV4Params calldata params) external payable returns (address token);
/// @notice Create a new token (V5) with tax V2 support
/// @param params The parameters for the new token including advanced tax features
/// @return token The address of the created token
/// @dev Similar to newTokenV4 but with support for FlapTaxTokenV2 when taxRate > 0.
/// When taxRate is 0, behaves like newTokenV4 (uses regular token or FlapTaxToken).
/// When taxRate > 0, creates a FlapTaxTokenV2 with advanced tax distribution features.
/// FIXME: this is not enabled in this version. Will be available once the audit for this part is ready.
function newTokenV5(NewTokenV5Params calldata params) external payable returns (address token);
}
/// @title Portal Lens Interface
/// @notice Handles read-only token state queries
interface IPortalLens is IPortalTypes {
/// @notice Get token state
/// @param token The address of the token
/// @return state The state of the token
function getTokenV2(address token) external view returns (TokenStateV2 memory state);
/// @notice Get token state (V3)
/// @param token The address of the token
/// @return state The state of the token (V3)
function getTokenV3(address token) external view returns (TokenStateV3 memory state);
/// @notice Get token state (V4)
/// @param token The address of the token
/// @return state The state of the token (V4) with only 'r' curve parameter
function getTokenV4(address token) external view returns (TokenStateV4 memory state);
/// @notice Get token state (V5)
/// @param token The address of the token
/// @return state The state of the token (V5) with all curve parameters (r, h, k)
function getTokenV5(address token) external view returns (TokenStateV5 memory state);
/// @notice Get token state (V6)
/// @param token The address of the token
/// @return state The state of the token (V6) with all V5 fields plus taxRate, pool, and progress
function getTokenV6(address token) external view returns (TokenStateV6 memory state);
/// @notice Get token state (V7)
/// @param token The address of the token
/// @return state The state of the token (V7) with all V6 fields plus lpFeeProfile
function getTokenV7(address token) external view returns (TokenStateV7 memory state);
/// @notice Get the quote token configuration for a given quote token address
/// @param quoteToken The address of the quote token
/// @return config The configuration of the quote token
function getQuoteTokenConfiguration(address quoteToken)
external
view
returns (QuoteTokenConfiguration memory config);
}
/// @title IPortalTrade Interface
/// @notice Handles token trading and redemption
interface IPortalTrade is IPortalTypes {
/// @notice Buy token with ETH on creation
/// @param token The address of the token to buy
/// @param recipient The address to send the token to
/// @param inputAmount The amount of ETH to spend
///
/// @dev This function is mainly for internal use (be delegated called from the portal contract)
/// The msg.value can be greater than inputAmount, the excess ETH will not be
/// refunded to the caller. They will be charged as a fee.
///
/// Note: the slippage is not checked in this function.
///
function buyOnCreation(address token, address recipient, uint256 inputAmount)
external
payable
returns (uint256 amount);
/// @notice Buy token with ETH
/// @param token The address of the token to buy
/// @param recipient The address to send the token to
/// @param minAmount The minimum amount of tokens to buy
function buy(address token, address recipient, uint256 minAmount) external payable returns (uint256 amount);
/// @notice Sell token for ETH
/// @param token The address of the token to sell
/// @param amount The amount of tokens to sell
/// @param minEth The minimum amount of ETH to receive
function sell(address token, uint256 amount, uint256 minEth) external returns (uint256 eth);
/// @notice Redeem a killed token for another token
/// @param srcToken The address of the token to redeem
/// @param dstToken The address of the token to receive
/// @param srcAmount The amount of srcToken to redeem
/// @return dstAmount The amount of dstToken to receive
function redeem(address srcToken, address dstToken, uint256 srcAmount) external returns (uint256 dstAmount);
/// @notice Preview the amount of tokens to buy with ETH
/// @param token The address of the token to buy
/// @param eth The amount of ETH to spend
/// @return amount The amount of tokens to buy
function previewBuy(address token, uint256 eth) external view returns (uint256 amount);
/// @notice Preview the amount of ETH to receive for selling tokens
/// @param token The address of the token to sell
/// @param amount The amount of tokens to sell
/// @return eth The amount of ETH to receive
function previewSell(address token, uint256 amount) external view returns (uint256 eth);
/// @notice Preview redeem
/// @param srcToken The address of the token to redeem
/// @param dstToken The address of the token to receive
/// @param srcAmount The amount of srcToken to redeem
/// @return dstAmount The amount of dstToken to receive
function previewRedeem(address srcToken, address dstToken, uint256 srcAmount)
external
view
returns (uint256 dstAmount);
}
/// @title IPortalTradeV2 Interface
/// @notice Handles unified token swaps and quoting
interface IPortalTradeV2 is IPortalTypes {
/// @notice Emitted when tax is paid on bonding curve for TAX_TOKEN
/// @param token The address of the token
/// @param amount The amount of tax paid
event TaxOnBondingCurvePaid(address indexed token, uint256 amount);
/// @notice Emitted when tax is paid on bonding curve for TAX_TOKEN_V2
/// @param token The address of the token
/// @param amount The amount of tax paid
event TaxV2OnBondingCurvePaid(address indexed token, uint256 amount);
/// @notice Parameters for swapping exact input amount for output token
struct ExactInputParams {
/// @notice The address of the input token (use address(0) for native asset)
address inputToken;
/// @notice The address of the output token (use address(0) for native asset)
address outputToken;
/// @notice The amount of input token to swap (in input token decimals)
uint256 inputAmount;
/// @notice The minimum amount of output token to receive
uint256 minOutputAmount;
/// @notice Optional permit data for the input token (can be empty)
bytes permitData;
}
/// @notice Parameters for swapping exact input amount for output token (V3) with extension support
struct ExactInputV3Params {
/// @notice The address of the input token (use address(0) for native asset)
address inputToken;
/// @notice The address of the output token (use address(0) for native asset)
address outputToken;
/// @notice The amount of input token to swap (in input token decimals)
uint256 inputAmount;
/// @notice The minimum amount of output token to receive
uint256 minOutputAmount;
/// @notice Optional permit data for the input token (can be empty)
bytes permitData;
/// @notice Additional extension specific data to be passed to the extension's `onTrade` method, check the extension's documentation for details on the expected format and content
bytes extensionData;
}
/// @notice Parameters for quoting the output amount for a given input
struct QuoteExactInputParams {
/// @notice The address of the input token (use address(0) for native asset)
address inputToken;
/// @notice The address of the output token (use address(0) for native asset)
address outputToken;
/// @notice The amount of input token to swap (in input token decimals)
uint256 inputAmount;
}
/// @notice Swap exact input amount for output token
/// @param params The swap parameters
/// @return outputAmount The amount of output token received
/// @dev Here are some possible scenarios:
/// If the token's reserve is BNB or ETH (i.e: the quote token is the native gas token):
/// - BUY: input token is address(0), output token is the token address
/// - SELL: input token is the token address, output token is address(0)
/// If the token's reserve is another ERC20 token (eg. USD*, i.e, the quote token is an ERC20 token):
/// - BUY with USD*: input token is the USD* address, output token is the token address
/// - SELL for USD*: input token is the token address, output token is the USD* address
/// - BUY with BNB or ETH: input token is address(0), output token is the token address.
/// (Note: this requires an internal swap to convert BNB/ETH to USD*, nativeToQuoteSwap must be anabled for this quote token)
/// Note: Currently, this method only supports trading tokens that are still in the bonding curve state.
/// However, in the future, we may also support trading tokens that are already in DEX state.
function swapExactInput(ExactInputParams calldata params) external payable returns (uint256 outputAmount);
/// @notice Swap exact input amount for output token (V3) with extension support
/// @param params The swap parameters including extension data
/// @return outputAmount The amount of output token received
/// @dev Similar to swapExactInput but with extension support. Extension hooks will be called if the token uses an extension
function swapExactInputV3(ExactInputV3Params calldata params) external payable returns (uint256 outputAmount);
/// @notice Quote the output amount for a given input
/// @param params The quote parameters
/// @return outputAmount The quoted output amount
/// @dev refer to the swapExactInput method for the scenarios
function quoteExactInput(QuoteExactInputParams calldata params) external returns (uint256 outputAmount);
}
/// @title IPortalCore Interface
/// @notice Combines IPortalLauncher and IPortalTrade
interface IPortalCore is IPortalLauncher, IPortalTrade, IPortalTradeV2 {}
/// @title IPortalMigrator Interface
/// @notice Add liquidity from the bonding curve to DEX
/// @dev this is not a public interface of the portal.
/// All the functions of this interface are either called from the portal
/// or from the UniswapV3Pool contract.
interface IPortalMigrator {
/// @notice Add liquidity to DEX
/// @param token The address of the token
/// @dev This is an internal function
/// Any dispatch to this function should be checked in portal contract
/// This function may be dellegated called from a payable function.
function luanchToDEX(address token) external payable;
}
/// @title IRoller Interface
/// @notice This acts as the glue between the portal and the flap staking contract
interface IRoller {
/// @notice The lock the token is using
enum LockType {
INVALID_LOCK, // Invalid lock
UNCX_LOCK, // The UNCX lock
GOPLUS_UNIV3_LOCK, // The Goplus UNIv3 lock
TOSHI_LP_LOCK, // The Toshi LP lock
IZI_LP_LOCK // The IziSwap LP locker
}
/// @notice get the locks by token address
/// @param token The address of the token
/// @return locks The lock ids of the token
function getLocks(address token) external view returns (uint256[] memory locks);
/// @dev deprecated
function rollv2(bytes calldata packedParams) external;
/// @notice Revenue Share: Claim LP fees for a vanity token
/// @param token The address of the token
/// @return tokenAmount The amount of the token claimed
/// @return ethAmount The amount of ETH claimed
/// @dev Only the beneficiary of the token can call this function.
function claim(address token) external returns (uint256 tokenAmount, uint256 ethAmount);
/// @notice Allows the default admin to change the beneficiary of a token
/// @param token The address of the token
/// @param newBeneficiary The new beneficiary address
function setTokenBeneficiary(address token, address newBeneficiary) external;
/// @notice Allows a roller or default admin to claim LP fees on behalf of the beneficiary
/// @param token The address of the token
/// @return tokenAmount The amount of the token claimed
/// @return quoteAmount The amount of quote token (or ETH) claimed
/// @dev Only the roller or default admin can call this function.
/// The claimed fee will be sent to the beneficiary of the token.
function delegateClaim(address token) external returns (uint256 tokenAmount, uint256 quoteAmount);
}
interface IPortalDexRouter {
// @notice Update the DEX pool information for a token
// @dev can only be called by DEX_ROUTER_MANAGER_ROLE roles
function updateTokenPoolInfo(address token, IPortalTypes.PackedDexPool calldata poolInfo) external;
}
/// @title IPortalTweak Interface
/// @notice Handles admin-only configuration operations for the Portal
interface IPortalTweak is IPortalTypes {
/// @notice Parameters for updating tax token addresses
struct TaxTokenAddressUpdate {
/// @notice The address of the tax token to update
address token;
/// @notice The new beneficiary address for the tax splitter
address beneficiary;
/// @notice The new fee receiver address for the tax splitter
address feeReceiver;
}
/// @notice Emitted when tax token addresses are updated
/// @param token The address of the tax token
/// @param beneficiary The new beneficiary address
/// @param feeReceiver The new fee receiver address
/// @dev Only Default ADMIN can change this
event TaxTokenAddressesUpdated(address indexed token, address beneficiary, address feeReceiver);
/// @notice Set the configuration for a quote token
/// @dev Only callable by the default admin
/// @param quoteToken The address of the quote token
/// @param config The configuration struct for the quote token
function setQuoteTokenConfiguration(address quoteToken, QuoteTokenConfiguration calldata config) external;
/// @notice Set the fee exemption status for a list of traders
/// @dev Only callable by the default admin
/// @param traders The addresses of the traders to set exemption for
/// @param isExempted Whether the traders should be exempted from fees
function setFeeExemption(address[] memory traders, bool isExempted) external;
/// @notice Get the current buy and sell fee rates
/// @return buyFeeRate The current buy fee rate in basis points (e.g. 200 = 2%)
/// @return sellFeeRate The current sell fee rate in basis points (e.g. 200 = 2%)
function getFeeRate() external view returns (uint256 buyFeeRate, uint256 sellFeeRate);
/// @notice Set the fee profile for a token
/// @dev Only callable by DEFAULT_ADMIN_ROLE or TOKEN_FLAP_FEE_SETTER_ROLE
/// @param token The address of the token
/// @param feeProfile The fee profile to set
function setFlapFeeProfile(address token, FlapFeeProfile feeProfile) external;
/// @notice Update beneficiary and feeReceiver addresses for one or more tax tokens
/// @dev Only callable by the default admin or TAX_MANAGER_ROLE
/// @param updates Array of TaxTokenAddressUpdate structs containing token addresses and new addresses
function updateTaxTokenAddresses(TaxTokenAddressUpdate[] calldata updates) external;
/// @notice Register an extension for use with the portal
/// @param extensionId The unique identifier for this extension
/// @param extensionAddress The address of the extension contract
/// @param version The version of the extension interface it implements (starting from 1)
/// @dev Only callable by the default admin
function registerExtension(bytes32 extensionId, address extensionAddress, uint8 version) external;
/// @notice Block or unblock multiple addresses from creating tokens
/// @param spammers The addresses to block or unblock
/// @param blocked True to block, false to unblock
/// @dev Only callable by the default admin or MODERATOR_ROLE
function setSpammerBlockedBatch(address[] calldata spammers, bool blocked) external;
}
/// @title Portal Interface
/// @notice This interface combines the core and game interfaces
interface IPortal is
IPortalCore,
IAccessControlUpgradeable,
IRoller,
IPortalDexRouter,
IPortalTweak,
IPortalLens,
IPortalLauncherTwoStep
{
/// @notice Get the version of the portal
/// @return The version string
function version() external view returns (string memory);
/// @notice Check if tax on bonding curve is enabled
/// @return enabled True if tax on bonding curve is enabled
function enableTaxOnBondingCurve() external view returns (bool enabled);
/// @notice Change the protocol bit flags
/// @dev Can only be called with DEFAULT_ADMIN_ROLE
/// @param flags The new flags
function setBitFlags(uint256 flags) external;
/// @notice Can only be called by the guardian role or the default admin role
/// @dev This function is used to pause the protocol
function halt() external;
/// @notice Check if an address is blocked from creating tokens
/// @param spammer The address to check
/// @return True if the address is blocked
function isSpammerBlocked(address spammer) external view returns (bool);
/// @notice Send a message for a token
/// @param token The address of the token
/// @param message The message to send
function sendMsg(address token, string memory message) external;
/// @notice Get the current nonce of the portal
function nonce() external view returns (uint256);
}// SPDX-License-Identifier: MIT
pragma solidity =0.8.24;
import {Initializable} from "@openzeppelin-contracts-upgradeable/proxy/utils/Initializable.sol";
/// @title BuyBackGuardUpgradeable
/// @notice Abstract contract providing buyback status tracking with gas-optimized storage
/// @dev Uses uint256 with constants instead of bool for gas efficiency:
/// - Non-zero to non-zero transitions (1 → 2 → 1) are cheaper than zero to non-zero (0 → 1 → 0)
/// - uint256 is native word size, no need for type conversion
/// Pattern follows OpenZeppelin's ReentrancyGuardUpgradeable
abstract contract BuyBackGuardUpgradeable is Initializable {
// --- Constants ---
uint256 private constant _NOT_BUYING_BACK = 1;
uint256 private constant _BUYING_BACK = 2;
// --- Storage ---
uint256 private _buyBackStatus;
// --- Internal Functions ---
/// @notice Initialize the buyback guard
/// @dev Must be called in the initializer of the inheriting contract
function __BuyBackGuard_init() internal onlyInitializing {
__BuyBackGuard_init_unchained();
}
function __BuyBackGuard_init_unchained() internal onlyInitializing {
_buyBackStatus = _NOT_BUYING_BACK;
}
/// @notice Check if buyback is currently in progress
/// @return True if buyback is in progress
function _isBuyingBack() internal view returns (bool) {
return _buyBackStatus == _BUYING_BACK;
}
// --- Modifiers ---
/// @notice Modifier to mark buyback operations
/// @dev Sets status to BUYING_BACK during execution, reverts to NOT_BUYING_BACK after
modifier duringBuyBack() {
_buyBackStatus = _BUYING_BACK;
_;
_buyBackStatus = _NOT_BUYING_BACK;
}
/// @dev This empty reserved space is put in place to allow future versions to add new
/// variables without shifting down storage in the inheritance chain.
/// See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
uint256[49] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
pragma solidity ^0.8.0;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/AddressUpgradeable.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
* constructor.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: setting the version to 255 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized != type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint8) {
return _initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _initializing;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControlUpgradeable {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Callback for IUniswapV3PoolActions#mint
/// @notice Any contract that calls IUniswapV3PoolActions#mint must implement this interface
interface IUniswapV3MintCallback {
/// @notice Called to `msg.sender` after minting liquidity to a position from IUniswapV3Pool#mint.
/// @dev In the implementation you must pay the pool tokens owed for the minted liquidity.
/// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
/// @param amount0Owed The amount of token0 due to the pool for the minted liquidity
/// @param amount1Owed The amount of token1 due to the pool for the minted liquidity
/// @param data Any data passed through by the caller via the IUniswapV3PoolActions#mint call
function uniswapV3MintCallback(
uint256 amount0Owed,
uint256 amount1Owed,
bytes calldata data
) external;
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Callback for IPancakeV3PoolActions#mint
/// @notice Any contract that calls IPancakeV3PoolActions#mint must implement this interface
interface IPancakeV3MintCallback {
/// @notice Called to `msg.sender` after minting liquidity to a position from IPancakeV3Pool#mint.
/// @dev In the implementation you must pay the pool tokens owed for the minted liquidity.
/// The caller of this method must be checked to be a PancakeV3Pool deployed by the canonical PancakeV3Factory.
/// @param amount0Owed The amount of token0 due to the pool for the minted liquidity
/// @param amount1Owed The amount of token1 due to the pool for the minted liquidity
/// @param data Any data passed through by the caller via the IPancakeV3PoolActions#mint call
function pancakeV3MintCallback(
uint256 amount0Owed,
uint256 amount1Owed,
bytes calldata data
) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library AddressUpgradeable {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}{
"remappings": [
"ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/",
"erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
"forge-std/=lib/forge-std/src/",
"@openzeppelin/=lib/openzeppelin-contracts/contracts/",
"@openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
"@ozv5/=lib/ozv5/contracts/",
"solady/=lib/solady/src/",
"uni-v3-core/=lib/v3-core/contracts/",
"uni-v4-core/=lib/v4-core/src/",
"uni-v4-periphery/=lib/v4-periphery/contracts/",
"uni-v2-core/=lib/v2-core/contracts/",
"uni-v2-periphery/=lib/v2-periphery/contracts/",
"pancake-v3-core/=lib/pancake-v3-contracts/projects/v3-core/contracts/",
"izi-periphery/=lib/iZiSwap-periphery/contracts/",
"@ensdomains/=lib/v4-core/node_modules/@ensdomains/",
"@uniswap/v4-core/=lib/v4-periphery/lib/v4-core/",
"forge-gas-snapshot/=lib/v4-periphery/lib/forge-gas-snapshot/src/",
"halmos-cheatcodes/=lib/ozv5/lib/halmos-cheatcodes/src/",
"hardhat/=lib/v4-core/node_modules/hardhat/",
"iZiSwap-periphery/=lib/iZiSwap-periphery/contracts/",
"openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/",
"openzeppelin/=lib/openzeppelin-contracts-upgradeable/contracts/",
"ozv5/=lib/ozv5/",
"pancake-v3-contracts/=lib/pancake-v3-contracts/",
"solmate/=lib/v4-periphery/lib/solmate/src/",
"v2-core/=lib/v2-core/contracts/",
"v2-periphery/=lib/v2-periphery/contracts/",
"v3-core/=lib/v3-core/",
"v4-core/=lib/v4-core/src/",
"v4-periphery/=lib/v4-periphery/contracts/"
],
"optimizer": {
"enabled": true,
"runs": 99999
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"abi"
]
}
},
"evmVersion": "london",
"viaIR": false
}Contract ABI
API[{"inputs":[{"internalType":"address","name":"weth_","type":"address"},{"internalType":"address","name":"flapBlackHole_","type":"address"},{"internalType":"address","name":"portal_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taxToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"quoteAmount","type":"uint256"}],"name":"FlapTaxProcessorBondingCurveTax","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taxToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"quoteAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokensBurned","type":"uint256"}],"name":"FlapTaxProcessorBurnExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taxToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"quoteAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"gasLeft","type":"uint256"},{"indexed":false,"internalType":"string","name":"reason","type":"string"}],"name":"FlapTaxProcessorBuyBackSkipped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taxToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"feeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"marketAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dividendAmount","type":"uint256"}],"name":"FlapTaxProcessorDispatchExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taxToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"string","name":"reason","type":"string"}],"name":"FlapTaxProcessorDividendDepositSkipped","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldLimit","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newLimit","type":"uint256"}],"name":"FlapTaxProcessorMaxBuyBackGasLimitUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newAmount","type":"uint256"}],"name":"FlapTaxProcessorMinBuyBackQuoteUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taxToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"refundAmount","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isWeth","type":"bool"}],"name":"FlapTaxProcessorPortalRefund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taxToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"taxAmount","type":"uint256"}],"name":"FlapTaxProcessorProcessTaxTokens","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taxToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FlapTaxProcessorTokensBurned","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","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":"DEAD_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dispatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"dividendAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dividendQuoteBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeConfig","outputs":[{"internalType":"uint16","name":"marketBps","type":"uint16"},{"internalType":"uint16","name":"deflationBps","type":"uint16"},{"internalType":"uint16","name":"lpBps","type":"uint16"},{"internalType":"uint16","name":"dividendBps","type":"uint16"},{"internalType":"uint16","name":"feeRate","type":"uint16"},{"internalType":"bool","name":"isWeth","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeQuoteBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"flapBlackHole","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getQuoteToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"quoteToken","type":"address"},{"internalType":"address","name":"router","type":"address"},{"internalType":"address","name":"feeReceiver","type":"address"},{"internalType":"address","name":"marketAddress","type":"address"},{"internalType":"address","name":"dividendAddress","type":"address"},{"internalType":"address","name":"taxToken","type":"address"},{"internalType":"uint16","name":"feeRate","type":"uint16"},{"internalType":"uint16","name":"marketBps","type":"uint16"},{"internalType":"uint16","name":"deflationBps","type":"uint16"},{"internalType":"uint16","name":"lpBps","type":"uint16"},{"internalType":"uint16","name":"dividendBps","type":"uint16"}],"internalType":"struct TaxProcessorInitParams","name":"params","type":"tuple"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lpQuoteBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"marketAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"marketQuoteBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxBuyBackGasLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minBuyBackQuote","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":"portal","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"preBondBurnFunds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"quoteAmount","type":"uint256"}],"name":"processBondingCurveTax","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"taxAmount","type":"uint256"}],"name":"processTaxTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"quoteToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"router","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"newLimit","type":"uint256"}],"name":"setMaxBuyBackGasLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newAmount","type":"uint256"}],"name":"setMinBuyBackQuote","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"feeReceiver_","type":"address"},{"internalType":"address","name":"marketAddress_","type":"address"},{"internalType":"address","name":"dividendAddress_","type":"address"}],"name":"setReceivers","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"feeRate_","type":"uint16"},{"internalType":"uint16","name":"marketBps_","type":"uint16"},{"internalType":"uint16","name":"deflationBps_","type":"uint16"},{"internalType":"uint16","name":"lpBps_","type":"uint16"},{"internalType":"uint16","name":"dividendBps_","type":"uint16"}],"name":"setTaxConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"taxToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalQuoteAddedToLiquidity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalQuoteSentToDividend","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalQuoteSentToMarketing","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalTokenAddedToLiquidity","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":"weth","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"}],"name":"withdrawAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in BNB
Multichain Portfolio | 32 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.