SlvrToken

0x791229e3ebd6cfdc3d8157f48722684173c29ad9

Verification
Verified
v0.8.33+commit.64118f21
Type
Contract
16,027 bytes
ABI entries
116
42 read · 32 write
License
none

Contract information

Address
0x791229e3ebd6cfdc3d8157f48722684173c29ad9
Chain
Robinhood Chain (4663)
Compiler
v0.8.33+commit.64118f21
Optimization
Enabled
Creation tx
0x0bfcc7ef0b…d8aab33f54

Token

Name
Slvr
Symbol
SLVR
Decimals
18
Holders
1,644

Read contract (42)

DEAD()address
DEFAULT_ADMIN_ROLE()bytes32
INITIAL_SUPPLY()uint256
LAUNCH_TAX_BPS()uint16
MAX_SUPPLY()uint256
MAX_SWAPBACK_THRESHOLD()uint256
MINTER_ROLE()bytes32
MIN_SWAPBACK_INTERVAL()uint256
accumulatedEthRewards()uint256
accumulatedTax()uint256
allowance(address, address)uint256
balanceOf(address)uint256
buyTaxBps()uint16
decimals()uint8
failedAccrualsByBlock(address, uint256)uint256
getCirculatingSupply()uint256
getEffectiveTaxRate(uint16)uint16
getRoleAdmin(bytes32)bytes32
growthFund()address
hasRole(bytes32, address)bool
initialMintDone()bool
isSellTaxExempt(address)bool
isTaxExempt(address)bool
isTaxedPool(address)bool
jackpotRecipient()address
lastSwapbackTime()uint256
name()string
owner()address
pairToken()address
pendingAccruals(address)uint256
pendingOwner()address
sellTaxBps()uint16
supportsInterface(bytes4)bool
swapRouter()address
swapbackInterval()uint256
swapbackMinOut()uint256
swapbackThreshold()uint256
symbol()string
taxHandler()address
teamVesting()address
totalSupply()uint256
uniswapV2Pair()address

Events (29)

AccrueFailedAccrueRetriedApprovalBuyTaxUpdatedEthDepositedToJackpotGrowthFundUpdatedJackpotRecipientUpdatedOwnershipTransferStartedOwnershipTransferredRoleAdminChangedRoleGrantedRoleRevokedSellTaxExemptUpdatedSellTaxUpdatedSwapbackAttemptedSwapbackConfigUpdatedSwapbackExecutedSwapbackFailedSwapbackMinOutUpdatedTaxCollectedTaxExemptUpdatedTaxHandlerFailedTaxHandlerUpdatedTaxRoutedToHandlerTaxedPoolUpdatedTeamVestingUpdatedTokensBurnedTransferUniswapV2PairUpdated

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "admin",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AccessControlBadConfirmation",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "neededRole",
        "type": "bytes32"
      }
    ],
    "name": "AccessControlUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "allowance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "needed",
        "type": "uint256"
      }
    ],
    "name": "ERC20InsufficientAllowance",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "needed",
        "type": "uint256"
      }
    ],
    "name": "ERC20InsufficientBalance",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "approver",
        "type": "address"
      }
    ],
    "name": "ERC20InvalidApprover",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      }
    ],
    "name": "ERC20InvalidReceiver",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "ERC20InvalidSender",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      }
    ],
    "name": "ERC20InvalidSpender",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "contractAddress",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "blockNumber",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "timestamp",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "reason",
        "type": "string"
      }
    ],
    "name": "AccrueFailed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "contractAddress",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "success",
        "type": "bool"
      }
    ],
    "name": "AccrueRetried",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "Approval",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "oldTaxBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "newTaxBps",
        "type": "uint16"
      }
    ],
    "name": "BuyTaxUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "EthDepositedToJackpot",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "oldFund",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newFund",
        "type": "address"
      }
    ],
    "name": "GrowthFundUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "oldRecipient",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newRecipient",
        "type": "address"
      }
    ],
    "name": "JackpotRecipientUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferStarted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "previousAdminRole",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "newAdminRole",
        "type": "bytes32"
      }
    ],
    "name": "RoleAdminChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "RoleGranted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "RoleRevoked",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "exempt",
        "type": "bool"
      }
    ],
    "name": "SellTaxExemptUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "oldTaxBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "newTaxBps",
        "type": "uint16"
      }
    ],
    "name": "SellTaxUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "accumulatedTax",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "threshold",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "timeSinceFirstExceeded",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "intervalMet",
        "type": "bool"
      }
    ],
    "name": "SwapbackAttempted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "router",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "pairToken",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "threshold",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "interval",
        "type": "uint256"
      }
    ],
    "name": "SwapbackConfigUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "slvrSwapped",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethReceived",
        "type": "uint256"
      }
    ],
    "name": "SwapbackExecuted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "reason",
        "type": "string"
      }
    ],
    "name": "SwapbackFailed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "oldMinOut",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newMinOut",
        "type": "uint256"
      }
    ],
    "name": "SwapbackMinOutUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "taxRateBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "isBuy",
        "type": "bool"
      }
    ],
    "name": "TaxCollected",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "exempt",
        "type": "bool"
      }
    ],
    "name": "TaxExemptUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "handler",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "bytes",
        "name": "reason",
        "type": "bytes"
      }
    ],
    "name": "TaxHandlerFailed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "oldHandler",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newHandler",
        "type": "address"
      }
    ],
    "name": "TaxHandlerUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "handler",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "TaxRoutedToHandler",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "taxed",
        "type": "bool"
      }
    ],
    "name": "TaxedPoolUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "oldVesting",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newVesting",
        "type": "address"
      }
    ],
    "name": "TeamVestingUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newTotalSupply",
        "type": "uint256"
      }
    ],
    "name": "TokensBurned",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "Transfer",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "oldPair",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newPair",
        "type": "address"
      }
    ],
    "name": "UniswapV2PairUpdated",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "DEAD",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "DEFAULT_ADMIN_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "INITIAL_SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "LAUNCH_TAX_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_SWAPBACK_THRESHOLD",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MINTER_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MIN_SWAPBACK_INTERVAL",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "accumulatedEthRewards",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "accumulatedTax",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      }
    ],
    "name": "allowance",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "approve",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "balanceOf",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "burn",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "burnFrom",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "buyTaxBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "decimals",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "depositEthToJackpot",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "amountOutMin",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "deadline",
        "type": "uint256"
      }
    ],
    "name": "executeSwapback",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "failedAccrualsByBlock",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "getCirculatingSupply",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "circulating",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "baseTaxBps",
        "type": "uint16"
      }
    ],
    "name": "getEffectiveTaxRate",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      }
    ],
    "name": "getRoleAdmin",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "grantRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "growthFund",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "hasRole",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "initialSupply",
        "type": "uint256"
      }
    ],
    "name": "initialMint",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "initialMint",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "initialMintDone",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "isSellTaxExempt",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "isTaxExempt",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "isTaxedPool",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "jackpotRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "lastSwapbackTime",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "mint",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "name",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "amountOutMin",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "deadline",
        "type": "uint256"
      }
    ],
    "name": "ownerExecuteSwapback",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pairToken",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "pendingAccruals",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "processTax",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "callerConfirmation",
        "type": "address"
      }
    ],
    "name": "renounceRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "contractAddress",
        "type": "address"
      }
    ],
    "name": "retryAccrue",
    "outputs": [
      {
        "internalType": "bool",
        "name": "success",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "revokeRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "sellTaxBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "taxBps",
        "type": "uint16"
      }
    ],
    "name": "setBuyTax",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "fund",
        "type": "address"
      }
    ],
    "name": "setGrowthFund",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "setJackpotRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "taxBps",
        "type": "uint16"
      }
    ],
    "name": "setSellTax",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "exempt",
        "type": "bool"
      }
    ],
    "name": "setSellTaxExempt",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "router",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pairToken_",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "threshold",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "interval",
        "type": "uint256"
      }
    ],
    "name": "setSwapbackConfig",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "minOut",
        "type": "uint256"
      }
    ],
    "name": "setSwapbackMinOut",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "threshold",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "interval",
        "type": "uint256"
      }
    ],
    "name": "setSwapbackTiming",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "exempt",
        "type": "bool"
      }
    ],
    "name": "setTaxExempt",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "handler",
        "type": "address"
      }
    ],
    "name": "setTaxHandler",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "taxed",
        "type": "bool"
      }
    ],
    "name": "setTaxedPool",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "vesting",
        "type": "address"
      }
    ],
    "name": "setTeamVesting",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "pair",
        "type": "address"
      }
    ],
    "name": "setUniswapV2Pair",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes4",
        "name": "interfaceId",
        "type": "bytes4"
      }
    ],
    "name": "supportsInterface",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "swapRouter",
    "outputs": [
      {
        "internalType": "contract IUniV2RouterLike",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "swapbackInterval",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "swapbackMinOut",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "swapbackThreshold",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "symbol",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "taxHandler",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "teamVesting",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalSupply",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "transfer",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "transferFrom",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "uniswapV2Pair",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

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

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IUniV2RouterLike} from "./interfaces/IUniV2RouterLike.sol";
import {ISlvrTaxHandler} from "./interfaces/ISlvrTaxHandler.sol";

/// @title SlvrToken
/// @notice ERC20 token with configurable buy/sell tax for the Slvr ecosystem.
/// @notice Maximum supply of 500,000 SLVR tokens (MAX_SUPPLY cap).
/// @notice Implements separate buy and sell taxes (2%/2% at launch, DECREASE-ONLY) that go to jackpot.
/// @notice Automatically swaps collected tax to ETH and deposits into jackpot during transfers.
/// @notice Total supply tracking:
/// @notice - Initial mint: 10,000 SLVR (one-time, via initialMint)
/// @notice - Lottery mints: Base amount + 10% to team vesting + 4% to growth fund (via mint)
/// @notice - All mints increase total supply and are counted toward MAX_SUPPLY
/// @notice - Burns decrease total supply (via ERC20Burnable), freeing up space for more mints
/// @notice - MAX_SUPPLY is a cap: minting allowed as long as totalSupply() + newMint <= MAX_SUPPLY
/// @notice - Because burns reduce totalSupply(), we can continue minting up to MAX_SUPPLY even after burns
/// @notice - Tokens in vesting contracts are part of total supply but not circulating until claimed
contract SlvrToken is ERC20, ERC20Burnable, AccessControl, Ownable2Step, ReentrancyGuard {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    uint256 public constant MAX_SUPPLY = 500_000e18; // 500,000 SLVR

    /// @notice The conventional "dead" address. Transfers here (or to the zero address) are
    ///         redirected to a real burn so they reduce totalSupply and reopen mint headroom for
    ///         the circular emission model — rather than parking supply at 0xdead forever.
    address public constant DEAD = 0x000000000000000000000000000000000000dEaD;

    // Initial mint tracking
    bool public initialMintDone; // Tracks if initial mint has been completed
    uint256 public constant INITIAL_SUPPLY = 10_000e18; // Default genesis seed (used by initialMint(to)); initialMint(to, amount) can start smaller

    // Tax parameters. The token launches at LAUNCH_TAX_BPS (2%/2%) and the tax is
    // DECREASE-ONLY: setBuyTax/setSellTax revert on any increase, so no owner action
    // can ever raise the tax above its launch value. (Deliberate: keeps token
    // scanners like GoPlus from flagging high/raisable tax, and removes the old
    // 25%->2% first-hour decay, which read as a 25% tax to anyone scanning at launch.)
    uint16 public constant LAUNCH_TAX_BPS = 200; // 2% buy / 2% sell at deploy — the all-time maximum
    uint16 public buyTaxBps; // Buy tax in basis points; starts at LAUNCH_TAX_BPS, only decreasable
    uint16 public sellTaxBps; // Sell tax in basis points; starts at LAUNCH_TAX_BPS, only decreasable
    address public jackpotRecipient; // Address that receives tax (lottery contract)
    address public teamVesting; // Team vesting contract address (for 8% of mint allocations)
    address public growthFund; // Growth fund contract address (for 4% of supply inflation)

    // Swapback parameters
    IUniV2RouterLike public swapRouter; // Uniswap V2 router for swaps
    address public pairToken; // ETH (WETH) address for the swap pair
    address public uniswapV2Pair; // Legacy single V2 pair (kept working; also treated as a taxed pool)

    // DEX-agnostic tax detection: any address flagged here counts as an AMM pool, so transfers TO it
    // are taxed as sells and transfers FROM it as buys. Lets the token tax trades across multiple
    // venues (additional V2 pairs, V3 pools, a V4 PoolManager, etc.) without a code change — the
    // owner just registers each pool via setTaxedPool. The legacy `uniswapV2Pair` is always treated
    // as taxed too, for backwards compatibility.
    mapping(address => bool) public isTaxedPool;
    uint256 public swapbackThreshold; // Minimum SLVR amount before triggering swapback
    uint256 public swapbackInterval; // Minimum time between swapbacks (configurable, but >= MIN_SWAPBACK_INTERVAL)
    uint256 public lastSwapbackTime; // Last time swapback was executed (or threshold first exceeded, if no swapback yet)
    uint256 public constant MIN_SWAPBACK_INTERVAL = 30 seconds; // Hard limit: minimum time between swapbacks (cannot be bypassed)
    uint256 public constant MAX_SWAPBACK_THRESHOLD = 10e18; // Maximum swapback threshold: 10 SLVR (common sense limit)

    // Minimum ETH out required by the AUTOMATIC swapback (the one fired inside a user's sell in
    // _update). Defaults to 0 (accept any) to preserve existing behavior. Owners who want a floor on
    // the sandwich-exposed auto path can set an absolute minimum here; the manual executeSwapback /
    // ownerExecuteSwapback entrypoints already take a caller-supplied amountOutMin. The per-swap size
    // is hard-capped at MAX_SWAPBACK_THRESHOLD, which bounds any residual MEV leak.
    uint256 public swapbackMinOut;

    // Pluggable tax handler (ISlvrTaxHandler). When set, collected SLVR tax is routed here for
    // processing instead of the built-in V2 swapback — so the swap venue and destinations are fully
    // swappable (V2/V3/V4/splitters/…) with no token redeploy. 0 = use the built-in V2 swapback.
    address public taxHandler;

    // Accumulated tax and rewards
    uint256 public accumulatedTax; // Accumulated SLVR tax waiting to be swapped
    uint256 public accumulatedEthRewards; // Accumulated ETH rewards waiting to be deposited

    // Reentrancy guard for swapback
    bool private inSwapback;

    // Exempt addresses from tax (lottery, staking contracts, etc.)
    mapping(address => bool) public isTaxExempt;
    // Exempt addresses from sell tax only (for liquidity operations)
    // These addresses can still pay buy tax on swaps, but won't pay sell tax when adding liquidity
    mapping(address => bool) public isSellTaxExempt;
    
    // Pending accruals tracking (for failed accrue() calls)
    // Maps contract address to pending accrual amount
    mapping(address => uint256) public pendingAccruals;
    
    // Failed accruals tracking
    // Maps contract address => block number => amount
    mapping(address => mapping(uint256 => uint256)) public failedAccrualsByBlock;

    event BuyTaxUpdated(uint16 oldTaxBps, uint16 newTaxBps);
    event SellTaxUpdated(uint16 oldTaxBps, uint16 newTaxBps);
    event JackpotRecipientUpdated(address indexed oldRecipient, address indexed newRecipient);
    event TaxExemptUpdated(address indexed account, bool exempt);
    event SellTaxExemptUpdated(address indexed account, bool exempt);
    event TaxCollected(address indexed from, uint256 amount, uint16 taxRateBps, bool isBuy);
    event SwapbackExecuted(uint256 slvrSwapped, uint256 ethReceived);
    event EthDepositedToJackpot(uint256 amount);
    event SwapbackConfigUpdated(address router, address pairToken, uint256 threshold, uint256 interval);
    event SwapbackMinOutUpdated(uint256 oldMinOut, uint256 newMinOut);
    event TaxHandlerUpdated(address indexed oldHandler, address indexed newHandler);
    event TaxRoutedToHandler(address indexed handler, uint256 amount);
    // reason is the handler's RAW revert data (Error(string) or a custom-error selector), not a
    // string cast — casting mangles it (selector bytes become garbage). Decode off-chain.
    event TaxHandlerFailed(address indexed handler, uint256 amount, bytes reason);
    event UniswapV2PairUpdated(address indexed oldPair, address indexed newPair);
    event TaxedPoolUpdated(address indexed pool, bool taxed);
    event TeamVestingUpdated(address indexed oldVesting, address indexed newVesting);
    event GrowthFundUpdated(address indexed oldFund, address indexed newFund);
    event TokensBurned(address indexed account, uint256 amount, uint256 newTotalSupply);
    event AccrueFailed(address indexed contractAddress, uint256 amount, uint256 blockNumber, uint256 timestamp, string reason);
    event AccrueRetried(address indexed contractAddress, uint256 amount, bool success);
    event SwapbackFailed(uint256 amount, string reason);
    event SwapbackAttempted(uint256 accumulatedTax, uint256 threshold, uint256 timeSinceFirstExceeded, bool intervalMet);

    constructor(address admin) ERC20("Slvr", "SLVR") Ownable(admin) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        // Exempt admin and this contract from tax
        isTaxExempt[admin] = true;
        isTaxExempt[address(this)] = true;
        // Launch taxes: 2%/2%, decrease-only from here (see LAUNCH_TAX_BPS).
        buyTaxBps = LAUNCH_TAX_BPS;
        sellTaxBps = LAUNCH_TAX_BPS;
        swapbackThreshold = 1e18;
        swapbackInterval = 55 seconds;
    }

    /// @notice Initial mint of the default seed supply (INITIAL_SUPPLY) - once, owner-only.
    /// @dev Convenience wrapper kept for backwards compatibility. Prefer initialMint(to, amount) to
    ///      launch with a smaller circulating seed and let the rest emit naturally via the hub.
    /// @param to Address to receive the remaining seed supply (after 8%/4% team+growth allocations)
    function initialMint(address to) external onlyOwner {
        _initialMint(to, INITIAL_SUPPLY);
    }

    /// @notice Initial mint with a caller-chosen seed supply - once, owner-only.
    /// @dev Lets a launch start with a small circulating supply (e.g. just enough to seed the first
    ///      liquidity pool) instead of a large fixed amount; ongoing supply is emitted over time by
    ///      the hub up to MAX_SUPPLY. 8% is allocated to team vesting and 4% to growth fund (carved
    ///      out of `initialSupply`, not added on top), if those addresses are set.
    /// @param to Address to receive the remaining seed supply (after allocations)
    /// @param initialSupply Total tokens to mint at genesis (0 < initialSupply <= MAX_SUPPLY)
    function initialMint(address to, uint256 initialSupply) external onlyOwner {
        _initialMint(to, initialSupply);
    }

    /// @dev Shared one-time genesis mint. Total supply increases by exactly `initialSupply`.
    function _initialMint(address to, uint256 initialSupply) private {
        require(!initialMintDone, "initial mint already done");
        require(to != address(0), "invalid address");
        require(initialSupply > 0, "initialSupply=0");
        require(initialSupply <= MAX_SUPPLY, "exceeds max supply");
        initialMintDone = true;

        // If team vesting and growth fund are set, allocate 8% and 4% respectively
        // Otherwise, mint everything to deployer (backward compatible)
        if (teamVesting != address(0) && growthFund != address(0)) {
            // Calculate allocations: 8% to team vesting, 4% to growth fund
            uint256 teamAmount = (initialSupply * 800) / 10000; // 8% = 800 basis points
            uint256 growthAmount = (initialSupply * 400) / 10000; // 4% = 400 basis points
            uint256 deployerAmount = initialSupply - teamAmount - growthAmount; // Remainder to deployer

            // Mint to deployer
            _mint(to, deployerAmount);

            // Mint to team vesting
            if (teamAmount > 0) {
                _mint(teamVesting, teamAmount);
                // Call accrue on team vesting contract - record pending if it fails
                (bool success,) = teamVesting.call(
                    abi.encodeWithSignature("accrue(uint256)", teamAmount)
                );
                if (!success) {
                    pendingAccruals[teamVesting] += teamAmount;
                    uint256 blockNum = block.number;
                    failedAccrualsByBlock[teamVesting][blockNum] += teamAmount;
                    emit AccrueFailed(teamVesting, teamAmount, blockNum, block.timestamp, "accrue call failed");
                }
            }

            // Mint to growth fund
            if (growthAmount > 0) {
                _mint(growthFund, growthAmount);
                // Call accrue on growth fund contract - record pending if it fails
                (bool success,) = growthFund.call(
                    abi.encodeWithSignature("accrue(uint256)", growthAmount)
                );
                if (!success) {
                    pendingAccruals[growthFund] += growthAmount;
                    uint256 blockNum = block.number;
                    failedAccrualsByBlock[growthFund][blockNum] += growthAmount;
                    emit AccrueFailed(growthFund, growthAmount, blockNum, block.timestamp, "accrue call failed");
                }
            }
        } else {
            // Backward compatible: mint everything to deployer if addresses not set
            _mint(to, initialSupply);
        }

        // Total supply is now exactly initialSupply
        require(totalSupply() == initialSupply, "initial mint supply mismatch");
    }

    /// @notice Mint tokens - only for lottery rewards (capped by MAX_SUPPLY)
    /// @dev Can only be called by addresses with MINTER_ROLE (e.g., lottery contract)
    /// @dev Mints amount to recipient, plus additional 8% to team vesting and 4% to growth fund
    /// @dev Total mint = amount * 1.12 (amount + 8% + 4%)
    /// @dev Total supply increases by totalMint (all mints are counted in total supply)
    /// @dev MAX_SUPPLY is a cap: minting allowed as long as totalSupply() + totalMint <= MAX_SUPPLY
    /// @dev Because burns decrease totalSupply(), we can continue minting up to MAX_SUPPLY after burns
    /// @param to Address to receive the minted tokens (100% of amount)
    /// @param amount Amount of tokens to mint to recipient (before additional allocations)
    function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
        require(initialMintDone, "initial mint not done");

        // Calculate additional allocations: 8% to team vesting, 4% to growth fund
        uint256 teamAmount = (amount * 800) / 10000; // 8% = 800 basis points
        uint256 growthAmount = (amount * 400) / 10000; // 4% = 400 basis points
        uint256 totalMint = amount + teamAmount + growthAmount; // Total = 112% of amount

        // Store current total supply for verification
        uint256 supplyBefore = totalSupply();

        // Check max supply with total mint amount
        require(supplyBefore + totalMint <= MAX_SUPPLY, "exceeds max supply");

        // Mint full amount to recipient
        _mint(to, amount);

        // Mint additional 8% to team vesting contract - ALWAYS required
        require(teamVesting != address(0), "team vesting not set");
        if (teamAmount > 0) {
            _mint(teamVesting, teamAmount);
            // Call accrue on team vesting contract - record pending if it fails
            (bool success,) = teamVesting.call(
                abi.encodeWithSignature("accrue(uint256)", teamAmount)
            );
            if (!success) {
                pendingAccruals[teamVesting] += teamAmount;
                uint256 blockNum = block.number;
                failedAccrualsByBlock[teamVesting][blockNum] += teamAmount;
                emit AccrueFailed(teamVesting, teamAmount, blockNum, block.timestamp, "accrue call failed");
            }
        }

        // Mint additional 4% to growth fund contract and accrue
        if (growthAmount > 0 && growthFund != address(0)) {
            _mint(growthFund, growthAmount);
            // Call accrue on growth fund contract - record pending if it fails
            (bool success,) = growthFund.call(
                abi.encodeWithSignature("accrue(uint256)", growthAmount)
            );
            if (!success) {
                pendingAccruals[growthFund] += growthAmount;
                uint256 blockNum = block.number;
                failedAccrualsByBlock[growthFund][blockNum] += growthAmount;
                emit AccrueFailed(growthFund, growthAmount, blockNum, block.timestamp, "accrue call failed");
            }
        } else if (growthAmount > 0) {
            // If growth fund not set, mint to recipient (backward compatibility)
            _mint(to, growthAmount);
        }

        // Verify total supply increased by exactly totalMint
        assert(totalSupply() == supplyBefore + totalMint);
    }

    /// @notice Burn tokens - decreases total supply
    /// @dev Override to emit event and verify total supply decreases
    /// @param value Amount of tokens to burn
    function burn(uint256 value) public override {
        uint256 supplyBefore = totalSupply();
        super.burn(value);
        // Verify total supply decreased by exactly value
        require(totalSupply() == supplyBefore - value, "burn supply mismatch");
        emit TokensBurned(msg.sender, value, totalSupply());
    }

    /// @notice Burn tokens from account - decreases total supply
    /// @dev Override to emit event and verify total supply decreases
    /// @param account Account to burn tokens from
    /// @param value Amount of tokens to burn
    function burnFrom(address account, uint256 value) public override {
        uint256 supplyBefore = totalSupply();
        super.burnFrom(account, value);
        // Verify total supply decreased by exactly value
        require(totalSupply() == supplyBefore - value, "burnFrom supply mismatch");
        emit TokensBurned(account, value, totalSupply());
    }

    /// @notice Transfers to the zero address or 0xdead are treated as burns.
    /// @dev Standard ERC20 reverts on transfer-to-zero, and a transfer to 0xdead would otherwise
    ///      just park supply there without reducing totalSupply. We redirect both to a real burn so
    ///      "sending to dead" reopens mint headroom (circular emission), consistent with burn().
    function transfer(address to, uint256 value) public override returns (bool) {
        if (to == address(0) || to == DEAD) {
            _burnToDead(_msgSender(), value);
            return true;
        }
        return super.transfer(to, value);
    }

    /// @notice transferFrom to the zero address or 0xdead is treated as a burn (allowance spent).
    function transferFrom(address from, address to, uint256 value) public override returns (bool) {
        if (to == address(0) || to == DEAD) {
            _spendAllowance(from, _msgSender(), value);
            _burnToDead(from, value);
            return true;
        }
        return super.transferFrom(from, to, value);
    }

    /// @dev Shared burn path for dead/zero-address transfers; reduces totalSupply and emits the
    ///      same TokensBurned event as burn()/burnFrom() so all burns look identical off-chain.
    function _burnToDead(address from, uint256 value) private {
        uint256 supplyBefore = totalSupply();
        _burn(from, value);
        require(totalSupply() == supplyBefore - value, "burn supply mismatch");
        emit TokensBurned(from, value, totalSupply());
    }

    /// @notice Get circulating supply (total supply minus tokens in vesting contracts)
    /// @dev Tokens in team vesting and growth fund are not considered circulating until claimed
    /// @return circulating The amount of tokens in circulation (excluding vesting contracts)
    function getCirculatingSupply() external view returns (uint256 circulating) {
        circulating = totalSupply();
        
        // Subtract tokens held by team vesting contract (not yet claimed)
        if (teamVesting != address(0)) {
            uint256 teamBalance = balanceOf(teamVesting);
            if (teamBalance > circulating) {
                // Safety check to prevent underflow
                circulating = 0;
            } else {
                circulating -= teamBalance;
            }
        }
        
        // Subtract tokens held by growth fund contract (not yet claimed)
        if (growthFund != address(0)) {
            uint256 growthBalance = balanceOf(growthFund);
            if (growthBalance > circulating) {
                // Safety check to prevent underflow
                circulating = 0;
            } else {
                circulating -= growthBalance;
            }
        }
    }

    /// @notice Lower the buy tax. DECREASE-ONLY: any attempt to raise it reverts,
    ///         so the launch rate (LAUNCH_TAX_BPS = 2%) is the all-time maximum.
    /// @param taxBps New buy tax in basis points (must be <= current buyTaxBps)
    function setBuyTax(uint16 taxBps) external onlyOwner {
        require(taxBps <= buyTaxBps, "tax can only decrease");
        uint16 oldTax = buyTaxBps;
        buyTaxBps = taxBps;
        emit BuyTaxUpdated(oldTax, taxBps);
    }

    /// @notice Lower the sell tax. DECREASE-ONLY: any attempt to raise it reverts,
    ///         so the launch rate (LAUNCH_TAX_BPS = 2%) is the all-time maximum.
    /// @param taxBps New sell tax in basis points (must be <= current sellTaxBps)
    function setSellTax(uint16 taxBps) external onlyOwner {
        require(taxBps <= sellTaxBps, "tax can only decrease");
        uint16 oldTax = sellTaxBps;
        sellTaxBps = taxBps;
        emit SellTaxUpdated(oldTax, taxBps);
    }

    /// @notice Effective tax rate. Kept for ABI compatibility with earlier versions
    ///         that applied a launch decay on top of the base rate; the tax is now
    ///         simply the stored base rate (2% at launch, decrease-only).
    /// @param baseTaxBps Base tax rate in basis points (buyTaxBps or sellTaxBps)
    /// @return Effective tax rate in basis points
    function getEffectiveTaxRate(uint16 baseTaxBps) public pure returns (uint16) {
        return baseTaxBps;
    }

    /// @notice Set jackpot recipient (lottery contract)
    function setJackpotRecipient(address recipient) external onlyOwner {
        require(recipient != address(0), "recipient=0");
        address oldRecipient = jackpotRecipient;
        jackpotRecipient = recipient;
        emit JackpotRecipientUpdated(oldRecipient, recipient);
    }

    /// @notice Set team vesting contract address
    function setTeamVesting(address vesting) external onlyOwner {
        address oldVesting = teamVesting;
        teamVesting = vesting;
        emit TeamVestingUpdated(oldVesting, vesting);
    }

    /// @notice Set growth fund contract address
    function setGrowthFund(address fund) external onlyOwner {
        address oldFund = growthFund;
        growthFund = fund;
        emit GrowthFundUpdated(oldFund, fund);
    }

    /// @notice Set tax exemption for an address
    /// @dev Use this to exempt liquidity zap contracts and other protocol contracts from tax
    function setTaxExempt(address account, bool exempt) external onlyOwner {
        isTaxExempt[account] = exempt;
        emit TaxExemptUpdated(account, exempt);
    }

    /// @notice Set sell tax exemption for an address (buy tax still applies)
    /// @dev Use this for liquidity zap contracts - they pay buy tax on swaps but not sell tax on liquidity adds
    /// @param account Address to exempt from sell tax
    /// @param exempt Whether to exempt from sell tax
    function setSellTaxExempt(address account, bool exempt) external onlyOwner {
        isSellTaxExempt[account] = exempt;
        emit SellTaxExemptUpdated(account, exempt);
    }

    /// @notice Set Uniswap V2 pair address (for detecting buy/sell trades)
    /// @dev Tax only applies to transfers involving a taxed pool. The legacy pair remains a taxed
    ///      pool automatically; for additional venues (more V2 pairs, V3/V4 pools) use setTaxedPool.
    /// @param pair The Uniswap V2 pair address
    function setUniswapV2Pair(address pair) external onlyOwner {
        require(pair != address(0), "pair=0");
        address oldPair = uniswapV2Pair;
        uniswapV2Pair = pair;
        emit UniswapV2PairUpdated(oldPair, pair);
    }

    /// @notice Register (or unregister) an AMM pool for buy/sell tax detection.
    /// @dev Any registered pool taxes trades to/from it, so the token is not tied to a single V2
    ///      pair — the owner can add other V2 pairs, V3 pools, a V4 PoolManager, etc. Detection here
    ///      is independent of the swapback venue (swapback still uses the configured V2 router).
    /// @param pool The pool/pair address to flag
    /// @param taxed Whether transfers to/from `pool` should be taxed
    function setTaxedPool(address pool, bool taxed) external onlyOwner {
        require(pool != address(0), "pool=0");
        isTaxedPool[pool] = taxed;
        emit TaxedPoolUpdated(pool, taxed);
    }

    /// @notice True if `addr` is treated as an AMM pool for tax purposes (legacy pair or registered).
    function _isTaxedPool(address addr) private view returns (bool) {
        return isTaxedPool[addr] || (addr != address(0) && addr == uniswapV2Pair);
    }

    /// @notice Configure swapback parameters
    /// @dev Interval must be >= MIN_SWAPBACK_INTERVAL (hard limit)
    /// @dev Threshold must be <= MAX_SWAPBACK_THRESHOLD (10 SLVR)
    function setSwapbackConfig(address router, address pairToken_, uint256 threshold, uint256 interval)
        external
        onlyOwner
    {
        require(router != address(0), "router=0");
        require(pairToken_ != address(0), "pairToken=0");
        require(interval >= MIN_SWAPBACK_INTERVAL, "interval below hard limit");
        require(threshold <= MAX_SWAPBACK_THRESHOLD, "threshold above max");

        // Exempt old router and pair if they were set
        if (address(swapRouter) != address(0)) {
            isTaxExempt[address(swapRouter)] = false;
        }
        if (pairToken != address(0)) {
            isTaxExempt[pairToken] = false;
        }

        swapRouter = IUniV2RouterLike(router);
        pairToken = pairToken_;
        swapbackThreshold = threshold;
        swapbackInterval = interval;

        isTaxExempt[address(swapRouter)] = true;

        emit SwapbackConfigUpdated(router, pairToken_, threshold, interval);
    }

    /// @notice Set swapback threshold + interval without configuring a V2 router.
    /// @dev For handler-based setups (setTaxHandler) that don't use the built-in V2 swapback. Same
    ///      bounds as setSwapbackConfig: threshold <= MAX_SWAPBACK_THRESHOLD, interval >= MIN_SWAPBACK_INTERVAL.
    function setSwapbackTiming(uint256 threshold, uint256 interval) external onlyOwner {
        require(interval >= MIN_SWAPBACK_INTERVAL, "interval below hard limit");
        require(threshold <= MAX_SWAPBACK_THRESHOLD, "threshold above max");
        swapbackThreshold = threshold;
        swapbackInterval = interval;
        emit SwapbackConfigUpdated(address(swapRouter), pairToken, threshold, interval);
    }

    /// @notice Set the minimum ETH out enforced by the automatic (sell-triggered) swapback.
    /// @dev 0 = accept any amount (default). Set a floor to reduce the auto path's sandwich exposure.
    function setSwapbackMinOut(uint256 minOut) external onlyOwner {
        uint256 old = swapbackMinOut;
        swapbackMinOut = minOut;
        emit SwapbackMinOutUpdated(old, minOut);
    }

    /// @notice Set (or clear) the pluggable tax handler.
    /// @dev When set (!= 0), collected SLVR tax is routed to the handler for processing instead of
    ///      the built-in V2 swapback. Set to 0 to revert to the built-in behavior. The handler is
    ///      auto tax-exempted so it can move SLVR (pull + swap) without being re-taxed. Swap this to
    ///      a new handler at any time to change swap venue / destinations — no token redeploy.
    /// @param handler The handler contract (must implement ISlvrTaxHandler), or 0 to disable.
    function setTaxHandler(address handler) external onlyOwner {
        address old = taxHandler;
        // Drop the old handler's tax exemption (best-effort; owner can re-exempt if reused elsewhere).
        if (old != address(0)) {
            isTaxExempt[old] = false;
        }
        taxHandler = handler;
        if (handler != address(0)) {
            isTaxExempt[handler] = true;
        }
        emit TaxHandlerUpdated(old, handler);
    }

    /// @notice Permissionlessly flush accumulated tax to the configured handler (keeper entrypoint).
    /// @dev Requires a handler set and the same threshold + hard interval as the automatic path, so
    ///      it can't be used to grief. Processes one `swapbackThreshold` chunk per call.
    function processTax() external nonReentrant {
        require(taxHandler != address(0), "no handler");
        require(accumulatedTax >= swapbackThreshold, "below threshold");
        require(block.timestamp >= lastSwapbackTime + MIN_SWAPBACK_INTERVAL, "hard limit: too soon");
        require(block.timestamp >= lastSwapbackTime + swapbackInterval, "too soon");
        require(!inSwapback, "swapback in progress");

        inSwapback = true;
        uint256 amount = swapbackThreshold;
        accumulatedTax -= amount;
        lastSwapbackTime = block.timestamp;
        _routeToHandler(amount);
        inSwapback = false;
    }

    /// @notice Route `amount` of SLVR tax to the pluggable handler via the PULL model.
    /// @dev Grants the handler an allowance and lets it pull + process. On failure the pull is rolled
    ///      back (nothing moved), so we restore accumulatedTax and clear the approval — no tax lost.
    ///      Caller must have set inSwapback=true so the handler's SLVR moves are not re-taxed.
    function _routeToHandler(uint256 amount) private {
        address handler = taxHandler;
        _approve(address(this), handler, amount);
        try ISlvrTaxHandler(handler).handleTax(amount) {
            // Handler pulled and processed; clear any residual approval defensively.
            if (allowance(address(this), handler) != 0) {
                _approve(address(this), handler, 0);
            }
            emit TaxRoutedToHandler(handler, amount);
        } catch (bytes memory reason) {
            _approve(address(this), handler, 0);
            accumulatedTax += amount; // atomic pull rolled back; nothing left the contract
            emit TaxHandlerFailed(handler, amount, reason);
        }
    }

    /// @notice Internal function to try swapback: checks conditions and executes if met
    /// @dev Called during transfers when selling to pair
    /// @dev Follows the pattern: check threshold, swap if conditions met, then continue with transfer
    function _trySwapback() private {
        // Check if swapback should be triggered
        // Only trigger when accumulatedTax exceeds threshold
        // Enforce hard limit: must wait at least MIN_SWAPBACK_INTERVAL since last swapback
        // Also enforce swapbackInterval: must wait at least swapbackInterval since last swapback
        if (inSwapback) {
            return;
        }

        // Need somewhere to send the tax: either a pluggable handler, or the built-in V2 route.
        if (taxHandler == address(0) && (address(swapRouter) == address(0) || pairToken == address(0))) {
            return;
        }

        if (accumulatedTax < swapbackThreshold) {
            return;
        }

        if (lastSwapbackTime == 0) {
            lastSwapbackTime = block.timestamp;
        }

        // Check interval requirements - must wait swapbackInterval since lastSwapbackTime
        // (which is either the last swapback time, or when threshold was first exceeded)
        uint256 timeSince = block.timestamp - lastSwapbackTime;
        if (block.timestamp < lastSwapbackTime + MIN_SWAPBACK_INTERVAL ||
            block.timestamp < lastSwapbackTime + swapbackInterval) {
            emit SwapbackAttempted(accumulatedTax, swapbackThreshold, timeSince, false);
            return;
        }

        // All conditions met - execute swapback
        emit SwapbackAttempted(accumulatedTax, swapbackThreshold, timeSince, true);
        _executeSwapback();
    }

    /// @notice Internal function to execute swapback: swap accumulated SLVR tax to ETH
    /// @dev Called by _trySwapback when conditions are met
    /// @dev Enforces hard limit: MIN_SWAPBACK_INTERVAL cannot be bypassed
    /// @dev Only swaps swapbackThreshold amount, not all accumulated tax
    function _executeSwapback() private {
        inSwapback = true;

        // Only swap the threshold amount, not all accumulated tax
        uint256 amountToSwap = swapbackThreshold;
        accumulatedTax -= amountToSwap;
        lastSwapbackTime = block.timestamp; // Update to actual swapback time

        // Preferred path: route to the pluggable handler (venue/destination-agnostic).
        if (taxHandler != address(0)) {
            _routeToHandler(amountToSwap);
            inSwapback = false;
            return;
        }

        // Built-in fallback: V2 swapback SLVR -> ETH -> jackpot.
        // Approve router to spend SLVR (safe approve: set to 0 first if needed)
        uint256 currentAllowance = allowance(address(this), address(swapRouter));
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance != 0) {
                _approve(address(this), address(swapRouter), 0);
            }
            _approve(address(this), address(swapRouter), type(uint256).max);
        }

        // Prepare swap path: SLVR -> ETH (WETH)
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = pairToken;

        // Execute swap: SLVR -> ETH. Enforce the owner-configured floor (swapbackMinOut); 0 = any.
        uint256 balanceBefore = address(this).balance;
        try swapRouter.swapExactTokensForETHSupportingFeeOnTransferTokens(
            amountToSwap,
            swapbackMinOut,
            path,
            address(this),
            block.timestamp
        ) {
            uint256 ethReceived = address(this).balance - balanceBefore;
            // Always emit event and accumulate rewards, even if ethReceived is 0 (shouldn't happen but handle gracefully)
            if (ethReceived > 0) {
                accumulatedEthRewards += ethReceived;
            }
            emit SwapbackExecuted(amountToSwap, ethReceived);
        } catch (bytes memory reason) {
            // If swap fails, restore accumulated tax
            // Don't reset lastSwapbackTime - keep it so we still respect the interval
            accumulatedTax += amountToSwap;
            emit SwapbackFailed(amountToSwap, string(reason));
        }

        inSwapback = false;

        // Deposit ETH to jackpot after successful swapback
        _depositEthToJackpot();
    }

    /// @notice Internal function to deposit ETH to jackpot
    /// @dev Called automatically after swapback
    /// @dev Calls addEthToJackpot() on jackpotRecipient (lottery contract, which redirects to jackpot)
    function _depositEthToJackpot() private {
        if (jackpotRecipient == address(0) || accumulatedEthRewards == 0) {
            return;
        }

        uint256 amount = accumulatedEthRewards;
        accumulatedEthRewards = 0;

        // Call addEthToJackpot() on the lottery contract with ETH
        // The lottery contract redirects this to the jackpot contract
        (bool success,) =
            payable(jackpotRecipient).call{value: amount}(abi.encodeWithSignature("addEthToJackpot()"));

        if (success) {
            emit EthDepositedToJackpot(amount);
        } else {
            // If deposit fails, restore accumulated ETH
            accumulatedEthRewards += amount;
        }
    }

    /// @notice Manual execute swapback (for edge cases or manual triggers)
    /// @dev Enforces hard limit: MIN_SWAPBACK_INTERVAL cannot be bypassed
    /// @dev Only swaps swapbackThreshold amount, not all accumulated tax
    /// @param amountOutMin Minimum ETH amount expected from swap
    /// @param deadline Swap deadline
    function executeSwapback(uint256 amountOutMin, uint256 deadline) external nonReentrant {
        require(accumulatedTax >= swapbackThreshold, "below threshold");
        require(block.timestamp >= lastSwapbackTime + MIN_SWAPBACK_INTERVAL, "hard limit: too soon");
        require(block.timestamp >= lastSwapbackTime + swapbackInterval, "too soon");
        require(!inSwapback, "swapback in progress");

        inSwapback = true;

        // Only swap the threshold amount, not all accumulated tax
        uint256 amountToSwap = swapbackThreshold;
        accumulatedTax -= amountToSwap;
        lastSwapbackTime = block.timestamp;

        // When a handler is configured it is the sole tax path — route to it so this permissionless
        // entrypoint can't bypass the handler through the still-configured legacy V2 router. The
        // handler owns venue and slippage, so the caller's amountOutMin/deadline don't apply here.
        if (taxHandler != address(0)) {
            _routeToHandler(amountToSwap);
            inSwapback = false;
            return;
        }

        require(address(swapRouter) != address(0), "router not set");
        require(pairToken != address(0), "pairToken not set");

        // Approve router to spend SLVR (safe approve: set to 0 first if needed)
        uint256 currentAllowance = allowance(address(this), address(swapRouter));
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance != 0) {
                _approve(address(this), address(swapRouter), 0);
            }
            _approve(address(this), address(swapRouter), type(uint256).max);
        }

        // Prepare swap path: SLVR -> ETH (WETH)
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = pairToken;

        // Execute swap: SLVR -> ETH
        uint256 balanceBefore = address(this).balance;
        swapRouter.swapExactTokensForETHSupportingFeeOnTransferTokens(
            amountToSwap, amountOutMin, path, address(this), deadline
        );
        uint256 ethReceived = address(this).balance - balanceBefore;

        accumulatedEthRewards += ethReceived;
        emit SwapbackExecuted(amountToSwap, ethReceived);

        inSwapback = false;

        // Auto-deposit ETH to jackpot
        _depositEthToJackpot();
    }

    /// @notice Owner-only manual swapback that bypasses time interval restrictions
    /// @dev Only swaps swapbackThreshold amount, not all accumulated tax
    /// @param amountOutMin Minimum ETH amount expected from swap
    /// @param deadline Swap deadline
    function ownerExecuteSwapback(uint256 amountOutMin, uint256 deadline) external onlyOwner nonReentrant {
        require(accumulatedTax >= swapbackThreshold, "below threshold");
        require(!inSwapback, "swapback in progress");

        inSwapback = true;

        // Only swap the threshold amount, not all accumulated tax
        uint256 amountToSwap = swapbackThreshold;
        accumulatedTax -= amountToSwap;
        lastSwapbackTime = block.timestamp;

        // Prefer the pluggable handler when set (see executeSwapback).
        if (taxHandler != address(0)) {
            _routeToHandler(amountToSwap);
            inSwapback = false;
            return;
        }

        require(address(swapRouter) != address(0), "router not set");
        require(pairToken != address(0), "pairToken not set");

        // Approve router to spend SLVR (safe approve: set to 0 first if needed)
        uint256 currentAllowance = allowance(address(this), address(swapRouter));
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance != 0) {
                _approve(address(this), address(swapRouter), 0);
            }
            _approve(address(this), address(swapRouter), type(uint256).max);
        }

        // Prepare swap path: SLVR -> ETH (WETH)
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = pairToken;

        // Execute swap: SLVR -> ETH
        uint256 balanceBefore = address(this).balance;
        swapRouter.swapExactTokensForETHSupportingFeeOnTransferTokens(
            amountToSwap, amountOutMin, path, address(this), deadline
        );
        uint256 ethReceived = address(this).balance - balanceBefore;

        accumulatedEthRewards += ethReceived;
        emit SwapbackExecuted(amountToSwap, ethReceived);

        inSwapback = false;

        // Auto-deposit ETH to jackpot
        _depositEthToJackpot();
    }

    /// @notice Retry failed accrue() calls for a given contract
    /// @dev Can be called by anyone to retry failed accruals
    /// @dev This helps fix accounting drift when accrue() calls fail during minting
    /// @param contractAddress The contract address to retry accrual for (teamVesting or growthFund)
    /// @return success Whether the retry was successful
    function retryAccrue(address contractAddress) external nonReentrant returns (bool success) {
        uint256 pendingAmount = pendingAccruals[contractAddress];
        require(pendingAmount > 0, "no pending accrual");
        
        // Only allow retrying for team vesting or growth fund
        require(
            contractAddress == teamVesting || contractAddress == growthFund,
            "invalid contract"
        );
        
        // Reset pending amount before retry (will restore if retry fails)
        pendingAccruals[contractAddress] = 0;
        
        // Retry accrue call
        (success,) = contractAddress.call(
            abi.encodeWithSignature("accrue(uint256)", pendingAmount)
        );
        
        if (success) {
            emit AccrueRetried(contractAddress, pendingAmount, true);
        } else {
            // Restore pending amount if retry failed
            pendingAccruals[contractAddress] = pendingAmount;
            emit AccrueRetried(contractAddress, pendingAmount, false);
        }
    }

    /// @notice Manual deposit of accumulated ETH rewards into jackpot
    /// @dev Usually called automatically, but can be called manually if needed
    function depositEthToJackpot() external nonReentrant {
        require(jackpotRecipient != address(0), "jackpot not set");
        require(accumulatedEthRewards > 0, "no rewards");
        require(!inSwapback, "swapback in progress");

        uint256 amount = accumulatedEthRewards;
        accumulatedEthRewards = 0;

        // Call addEthToJackpot() on the lottery contract with ETH
        (bool success,) =
            payable(jackpotRecipient).call{value: amount}(abi.encodeWithSignature("addEthToJackpot()"));
        require(success, "deposit failed");

        emit EthDepositedToJackpot(amount);
    }

    /// @notice Override _update to apply the flat (decrease-only) tax on taxed-pool trades only
    /// @dev Tax only applies to transfers involving the Uniswap V2 pair (buy/sell)
    /// @dev Automatically triggers swapback and deposit when threshold is met
    function _update(address from, address to, uint256 value) internal override {
        if (inSwapback) {
            super._update(from, to, value);
            return;
        }
        
        // Tax only applies to trades against a taxed pool (any registered venue, plus the legacy pair)
        // Buy:  from is a taxed pool (pool sends SLVR to user)
        // Sell: to   is a taxed pool (user sends SLVR to pool)
        bool isBuy = _isTaxedPool(from);
        bool isSell = _isTaxedPool(to);
        bool isUniswapV2Trade = isBuy || isSell;

        // On a sell, try to process accumulated tax — via the pluggable handler if set, else the
        // built-in V2 route. _trySwapback re-checks all conditions (threshold, interval).
        if (
            !inSwapback &&
            isSell &&
            (taxHandler != address(0) || (address(swapRouter) != address(0) && pairToken != address(0)))
        ) {
            _trySwapback();
        }
        
        // Skip tax for minting, burning, or exempt addresses
        if (from == address(0) || to == address(0) || isTaxExempt[from] || isTaxExempt[to]) {
            super._update(from, to, value);
            return;
        }
        
        if (!isUniswapV2Trade) {
            // Not a UniswapV2 trade, no tax
            super._update(from, to, value);
            return;
        }

        // Check if sender is exempt from sell tax (for liquidity operations)
        // This allows liquidity zaps to pay buy tax on swaps but skip sell tax on liquidity adds
        if (!isBuy && isSellTaxExempt[from]) {
            // This is a sell, but sender is exempt from sell tax - skip tax
            super._update(from, to, value);
            return;
        }
        
        // Get base tax rate (manual setting)
        uint16 baseTaxBps = isBuy ? buyTaxBps : sellTaxBps;
        
        // Effective tax rate (flat base rate; decrease-only)
        uint16 taxRateBps = getEffectiveTaxRate(baseTaxBps);

        // If tax is 0, skip tax calculation
        if (taxRateBps == 0) {
            super._update(from, to, value);
            return;
        }

        uint256 tax = (value * taxRateBps) / 10000;
        uint256 transferAmount = value - tax;
        
        uint256 fromBalance = balanceOf(from);
        require(fromBalance >= value, "ERC20: insufficient balance for transfer with tax");

        // Transfer amount minus tax to recipient
        super._update(from, to, transferAmount);

        if (tax > 0) {
            super._update(from, address(this), tax);
            emit TaxCollected(from, tax, taxRateBps, isBuy);

            accumulatedTax += tax;
            
            if (lastSwapbackTime == 0 && accumulatedTax >= swapbackThreshold) {
                lastSwapbackTime = block.timestamp;
            }
        }
    }

    /// @notice Allow contract to receive native tokens (ETH)
    receive() external payable {}
}
Chain explorer1238msChain node98ms