Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/SPX6900Strategy.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ERC20} from "solady/tokens/ERC20.sol";
import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {Ownable} from "solady/auth/Ownable.sol";
import {SignatureCheckerLib} from "solady/utils/SignatureCheckerLib.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {IV4Router} from "@uniswap/v4-periphery/src/interfaces/IV4Router.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
interface IUniswapV2Router02 {
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
function getAmountsOut(uint256 amountIn, address[] calldata path)
external
view
returns (uint256[] memory amounts);
function WETH() external pure returns (address);
}
interface IUniversalRouter {
function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable;
}
library Commands {
uint256 internal constant V4_SWAP = 0x10;
}
contract SPX6900Strategy is ERC20, ReentrancyGuard, Ownable {
using PoolIdLibrary for PoolKey;
using StateLibrary for IPoolManager;
/* ═══════════════════════════════════════════════════ */
/* CONSTANTS */
/* ═══════════════════════════════════════════════════ */
IPositionManager private constant POSM = IPositionManager(0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e);
IAllowanceTransfer private constant PERMIT2 = IAllowanceTransfer(0x000000000022D473030F116dDEE9F6B43aC78BA3);
IUniversalRouter private constant UNIVERSAL_ROUTER = IUniversalRouter(0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af);
IUniswapV2Router02 private constant UNISWAP_ROUTER = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
address public constant SPX6900 = 0xE0f63A424a4439cBE457D80E4f4b51aD25b2c56C;
address public constant AEON_COLLECTION = 0xc374a204334d4Edd4C6a62f0867C752d65E9579c;
address public constant SEAPORT = 0x0000000000000068F116a894984e2DB1123eB395;
address public constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;
uint256 public constant MAX_SUPPLY = 1_000_000_000 * 1e18; // 1 Billion tokens
string private tokenName;
string private tokenSymbol;
address public immutable teamWallet;
/* ═══════════════════════════════════════════════════ */
/* STATE VARIABLES */
/* ═══════════════════════════════════════════════════ */
// Uniswap V4 Pool variables
bool public tradingEnabled;
bool public poolInitialized;
bool private loadingLiquidity;
PoolKey public poolKey;
PoolId public poolId;
int24 public tickLower;
int24 public tickUpper;
uint256 public positionTokenId;
// Cumulative tracking (all-time totals)
uint256 public totalEthForSPX; // Total ETH spent on SPX6900 purchases
uint256 public totalSPXPurchased; // Total SPX6900 tokens acquired
uint256 public totalEthForNFTs; // Total ETH sent to NFT wallet (deprecated for Aeon system)
uint256 public aeonNFTLockedEth; // ETH currently locked in unsold Aeon NFTs
// SPX6900 position tracking for profit taking
uint256 public spxHoldingBalance; // Current SPX6900 balance held
uint256 public spxCostBasis; // Total ETH cost basis for current holdings
uint256 public profitThreshold = 20; // Profit threshold in percentage (default 20%)
// Position-based SPX buy system
uint256 public pendingEthForSPX; // ETH accumulated waiting to reach buy threshold
uint256 public pendingEthForBurn; // ETH from NFT sales waiting to be burned
uint256 public spxBuyThreshold = 1 ether; // Minimum ETH to accumulate before buying SPX (default 1 ETH)
uint256 public spxBuyCount; // Number of times SPX has been purchased
struct SPXPosition {
uint256 ethAmount; // ETH spent on this position
uint256 spxAmount; // SPX tokens received
uint256 timestamp; // When the position was opened
uint256 blockNumber; // Block number of purchase
bool closed; // Whether the position has been closed/sold
uint256 closedAt; // Timestamp when position was closed
uint256 soldForEth; // ETH received when sold
}
SPXPosition[] public spxPositions; // Array of all SPX positions
uint256 public openPositionCount; // Number of open (unsold) positions
// Aeon NFT tracking
struct AeonNFTListing {
uint256 tokenId; // Aeon NFT token ID
uint256 purchasePrice; // ETH spent to buy the NFT
uint256 listPrice; // ETH listed for (purchasePrice * 1.2)
uint256 listedAt; // Timestamp when listed
bool sold; // Whether the NFT has been sold
uint256 soldAt; // Timestamp when sold
}
AeonNFTListing[] public aeonListings; // Array of all Aeon NFT listings
uint256 public aeonMarkupPercent = 20; // Markup percentage for listings (default 20%)
// Processing flag
bool private isProcessing;
// Auto-collect threshold
uint256 public autoCollectThreshold = 0.01 ether; // Collect fees when accumulated ETH fees exceed this
/* ═══════════════════════════════════════════════════ */
/* CUSTOM EVENTS */
/* ═══════════════════════════════════════════════════ */
event TradingEnabled();
event TaxCollected(uint256 totalEth, uint256 nftAmount, uint256 spxAmount);
event SPXPurchased(uint256 ethAmount, uint256 spxReceived, uint256 totalSPXPurchased);
event SPXSold(uint256 spxAmount, uint256 ethReceived, uint256 profit);
event AutoCollectThresholdUpdated(uint256 newThreshold);
event ProfitThresholdUpdated(uint256 newThreshold);
event SPXBuyThresholdUpdated(uint256 newThreshold);
event PendingEthAccumulated(uint256 amount, uint256 totalPending);
event SPXPositionOpened(uint256 indexed positionId, uint256 ethAmount, uint256 spxAmount);
event SPXPositionClosed(uint256 indexed positionId, uint256 spxAmount, uint256 ethReceived, uint256 profit);
event NFTTradeExecuted(address indexed target, uint256 value, bytes data, bool success);
event AeonNFTListed(uint256 indexed listingId, uint256 indexed tokenId, uint256 purchasePrice, uint256 listPrice);
event AeonNFTSold(uint256 indexed listingId, uint256 indexed tokenId, uint256 salePrice, uint256 tokensBurned);
event AeonMarkupPercentUpdated(uint256 newPercent);
/* ═══════════════════════════════════════════════════ */
/* CUSTOM ERRORS */
/* ═══════════════════════════════════════════════════ */
error TradingNotEnabled();
error TradingAlreadyEnabled();
error TransferFailed();
constructor(address _teamWallet, string memory _tokenName, string memory _tokenSymbol) {
teamWallet = _teamWallet;
tokenName = _tokenName;
tokenSymbol = _tokenSymbol;
_initializeOwner(msg.sender);
// Mint entire supply to contract
_mint(address(this), MAX_SUPPLY);
// Approve Seaport to transfer Aeon NFTs once (saves gas on every purchase)
IERC721(AEON_COLLECTION).setApprovalForAll(SEAPORT, true);
}
function name() public view override returns (string memory) {
return tokenName;
}
function symbol() public view override returns (string memory) {
return tokenSymbol;
}
/// @notice Override _beforeTokenTransfer hook to auto-collect fees on swaps
/// @dev Solady ERC20 uses _beforeTokenTransfer instead of _transfer
function _beforeTokenTransfer(address from, address to, uint256 amount) internal override {
// Skip fee collection if:
// - Loading initial liquidity
// - Already processing fees
// - Pool not initialized
// - Transfer is from/to this contract (during fee collection)
if (!loadingLiquidity && !isProcessing && poolInitialized && from != address(this) && to != address(this)) {
// Check if accumulated fees exceed threshold
(uint256 ethFees,) = getAccumulatedFees();
if (ethFees >= autoCollectThreshold) {
// Try to collect fees (non-reentrant, will fail silently if already processing)
try this.collectFees() {} catch {}
}
}
super._beforeTokenTransfer(from, to, amount);
}
/// @notice Enable trading by loading initial liquidity into V4 pool
/// @dev Can only be called once by owner. Caller must send ETH for the initial liquidity (amount0Max).
function enableTrading() external payable onlyOwner {
if (poolInitialized) revert TradingAlreadyEnabled();
loadingLiquidity = true;
// Create the pool with ETH (currency0) and TOKEN (currency1)
Currency currency0 = Currency.wrap(address(0)); // ETH
Currency currency1 = Currency.wrap(address(this)); // TOKEN
uint24 lpFee = 100000; // 10% fee
int24 tickSpacing = 60;
uint256 token0Amount = 1; // 1 wei
uint256 token1Amount = MAX_SUPPLY; // 1B TOKEN
// Starting price
uint160 startingPrice = 501082896750095888663770159906816;
tickLower = TickMath.minUsableTick(tickSpacing);
tickUpper = int24(175020);
PoolKey memory key = PoolKey(currency0, currency1, lpFee, tickSpacing, IHooks(address(0)));
bytes memory hookData = new bytes(0);
// Store pool information
poolKey = key;
poolId = key.toId();
uint128 liquidity = 158372218983990412488087;
uint256 amount0Max = token0Amount + 1 wei;
uint256 amount1Max = token1Amount + 1 wei;
(bytes memory actions, bytes[] memory mintParams) =
_mintLiquidityParams(key, tickLower, tickUpper, liquidity, amount0Max, amount1Max, address(this), hookData);
bytes[] memory params = new bytes[](2);
params[0] = abi.encodeWithSelector(POSM.initializePool.selector, key, startingPrice, hookData);
params[1] = abi.encodeWithSelector(
POSM.modifyLiquidities.selector, abi.encode(actions, mintParams), block.timestamp + 60
);
uint256 valueToPass = amount0Max;
require(msg.value >= valueToPass, "Insufficient ETH sent");
// Approve Permit2 to spend our tokens
_approve(address(this), address(PERMIT2), type(uint256).max);
PERMIT2.approve(address(this), address(POSM), type(uint160).max, type(uint48).max);
// Get the next token ID before minting
positionTokenId = POSM.nextTokenId();
POSM.multicall{value: valueToPass}(params);
poolInitialized = true;
tradingEnabled = true;
loadingLiquidity = false;
emit TradingEnabled();
}
/// @notice Creates parameters for minting liquidity in Uniswap V4
function _mintLiquidityParams(
PoolKey memory key,
int24 _tickLower,
int24 _tickUpper,
uint128 liquidity,
uint256 amount0Max,
uint256 amount1Max,
address recipient,
bytes memory hookData
) internal pure returns (bytes memory, bytes[] memory) {
bytes memory actions = abi.encodePacked(uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR));
bytes[] memory params = new bytes[](2);
params[0] = abi.encode(key, _tickLower, _tickUpper, liquidity, amount0Max, amount1Max, recipient, hookData);
params[1] = abi.encode(key.currency0, key.currency1);
return (actions, params);
}
/// @notice Swap ETH for SPX6900 using Uniswap V2 (position-based)
function _swapEthForSPX(uint256 ethAmount) internal {
if (ethAmount == 0) return;
address[] memory path = new address[](2);
path[0] = UNISWAP_ROUTER.WETH();
path[1] = SPX6900;
uint256 spxBalanceBefore = IERC20(SPX6900).balanceOf(address(this));
UNISWAP_ROUTER.swapExactETHForTokensSupportingFeeOnTransferTokens{value: ethAmount}(
0, // Accept any amount of SPX6900
path,
address(this),
block.timestamp + 300
);
uint256 spxReceived = IERC20(SPX6900).balanceOf(address(this)) - spxBalanceBefore;
// Track cumulative totals
totalEthForSPX += ethAmount;
totalSPXPurchased += spxReceived;
// Update position tracking
spxHoldingBalance += spxReceived;
spxCostBasis += ethAmount;
// Create new position entry
spxPositions.push(
SPXPosition({
ethAmount: ethAmount,
spxAmount: spxReceived,
timestamp: block.timestamp,
blockNumber: block.number,
closed: false,
closedAt: 0,
soldForEth: 0
})
);
spxBuyCount++;
openPositionCount++;
emit SPXPurchased(ethAmount, spxReceived, totalSPXPurchased);
emit SPXPositionOpened(spxPositions.length - 1, ethAmount, spxReceived);
}
/// @notice Get accumulated fees from our LP position
/// @return fees0 ETH fees accumulated
/// @return fees1 Token fees accumulated
function getAccumulatedFees() public view returns (uint256 fees0, uint256 fees1) {
if (!poolInitialized) return (0, 0);
// Get pool manager
IPoolManager poolManager = POSM.poolManager();
// Get position info - the position is owned by POSM with positionTokenId as salt
(uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128) =
poolManager.getPositionInfo(poolId, address(POSM), tickLower, tickUpper, bytes32(positionTokenId));
// Get current fee growth inside the position range
(uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) =
poolManager.getFeeGrowthInside(poolId, tickLower, tickUpper);
// Calculate fees owed
fees0 = ((feeGrowthInside0X128 - feeGrowthInside0LastX128) * liquidity) / (1 << 128);
fees1 = ((feeGrowthInside1X128 - feeGrowthInside1LastX128) * liquidity) / (1 << 128);
return (fees0, fees1);
}
/// @notice Collect accumulated LP fees from Uniswap V4 pool
/// @dev Anyone can call this to trigger fee collection and distribution
function collectFees() external nonReentrant returns (uint256 totalEth) {
require(poolInitialized, "Pool not initialized");
require(!isProcessing, "Already processing");
isProcessing = true;
// Check accumulated fees
(uint256 ethFees, uint256 tokenFees) = getAccumulatedFees();
// If no fees, return early
if (ethFees == 0 && tokenFees == 0) {
isProcessing = false;
return 0;
}
// Collect fees by decreasing 0 liquidity (fee collection without removing liquidity)
bytes memory actions = abi.encodePacked(uint8(Actions.DECREASE_LIQUIDITY), uint8(Actions.TAKE_PAIR));
bytes[] memory params = new bytes[](2);
// DECREASE_LIQUIDITY with 0 liquidity to collect fees only
params[0] = abi.encode(
positionTokenId,
0, // liquidityDelta = 0
0, // amount0Min
0, // amount1Min
"" // hookData
);
// TAKE_PAIR - transfer collected fees to this contract
params[1] = abi.encode(
poolKey.currency0, // ETH
poolKey.currency1, // Token
address(this) // recipient
);
// Track ETH balance before operations
uint256 ethBalanceBefore = address(this).balance;
// Execute fee collection
POSM.modifyLiquidities(abi.encode(actions, params), block.timestamp + 60);
// Now swap ALL collected token fees to ETH
if (tokenFees > 0) {
uint256 tokenBalance = balanceOf(address(this));
if (tokenBalance > 0) {
_swapTokensForEth(tokenBalance);
}
}
// Calculate total ETH collected (including from swapping tokens)
totalEth = address(this).balance - ethBalanceBefore;
// Distribute total ETH (50% for NFTs, 50% for SPX6900)
if (totalEth > 0) {
uint256 nftAmount = (totalEth * 50) / 100; // 50% for buying NFTs
uint256 spxAmount = (totalEth * 50) / 100; // 50% for SPX6900
// 1. Send 50% to airdrop wallet (for buying NFTs)
if (nftAmount > 0) {
totalEthForNFTs += nftAmount;
}
// 2. Accumulate ETH for SPX6900 until threshold is reached
if (spxAmount > 0) {
pendingEthForSPX += spxAmount;
emit PendingEthAccumulated(spxAmount, pendingEthForSPX);
// Check if we've reached the buy threshold
if (pendingEthForSPX >= spxBuyThreshold) {
uint256 amountToBuy = pendingEthForSPX;
pendingEthForSPX = 0;
_swapEthForSPX(amountToBuy);
}
}
}
isProcessing = false;
emit TaxCollected(totalEth, totalEth / 2, totalEth / 2);
return totalEth;
}
/// @notice Swap tokens for ETH using V4 pool
function _swapTokensForEth(uint256 tokenAmount) internal {
if (tokenAmount == 0) return;
// Encode the Universal Router command
bytes memory commands = abi.encodePacked(uint8(Commands.V4_SWAP));
bytes[] memory inputs = new bytes[](1);
// Encode V4Router actions
bytes memory actions =
abi.encodePacked(uint8(Actions.SWAP_EXACT_IN_SINGLE), uint8(Actions.SETTLE_ALL), uint8(Actions.TAKE_ALL));
// Prepare parameters for each action
bytes[] memory params = new bytes[](3);
params[0] = abi.encode(
IV4Router.ExactInputSingleParams({
poolKey: poolKey,
zeroForOne: false, // TOKEN (currency1) -> ETH (currency0)
amountIn: uint128(tokenAmount),
amountOutMinimum: 1, // Accept any amount
hookData: ""
})
);
params[1] = abi.encode(poolKey.currency1, tokenAmount);
params[2] = abi.encode(poolKey.currency0, 1);
// Combine actions and params into inputs
inputs[0] = abi.encode(actions, params);
// Approve Permit2 to spend tokens (if not already approved)
if (allowance(address(this), address(PERMIT2)) < tokenAmount) {
_approve(address(this), address(PERMIT2), type(uint256).max);
}
// Approve UniversalRouter via Permit2 (for V4 swaps)
PERMIT2.approve(address(this), address(UNIVERSAL_ROUTER), type(uint160).max, type(uint48).max);
// Execute the swap
UNIVERSAL_ROUTER.execute(commands, inputs, block.timestamp + 300);
}
/// @notice Swap ETH for tokens using V4 pool (for burning)
function _swapEthForTokens(uint256 ethAmount) internal {
if (ethAmount == 0) return;
// Encode the Universal Router command
bytes memory commands = abi.encodePacked(uint8(Commands.V4_SWAP));
bytes[] memory inputs = new bytes[](1);
// Encode V4Router actions
bytes memory actions =
abi.encodePacked(uint8(Actions.SWAP_EXACT_IN_SINGLE), uint8(Actions.SETTLE_ALL), uint8(Actions.TAKE_ALL));
// Prepare parameters for each action
bytes[] memory params = new bytes[](3);
params[0] = abi.encode(
IV4Router.ExactInputSingleParams({
poolKey: poolKey,
zeroForOne: true, // ETH (currency0) -> TOKEN (currency1)
amountIn: uint128(ethAmount),
amountOutMinimum: 1, // Accept any amount of tokens
hookData: ""
})
);
params[1] = abi.encode(poolKey.currency0, ethAmount);
params[2] = abi.encode(poolKey.currency1, 1);
// Combine actions and params into inputs
inputs[0] = abi.encode(actions, params);
// Execute the swap with ETH
UNIVERSAL_ROUTER.execute{value: ethAmount}(commands, inputs, block.timestamp + 300);
}
/// @notice Close/sell a specific SPX position - ONLY SELLS PROFITS (owner only)
/// @dev Calculates profit portion of SPX and sells only that. The remaining SPX (principal)
/// stays in the contract as a long-term hold (untracked after position closes).
/// @param positionId The ID of the position to close
function sellSPXPosition(uint256 positionId) external onlyOwner nonReentrant {
require(positionId < spxPositions.length, "Invalid position ID");
SPXPosition storage position = spxPositions[positionId];
require(!position.closed, "Position already closed");
uint256 spxBalance = IERC20(SPX6900).balanceOf(address(this));
require(spxBalance >= position.spxAmount, "Insufficient SPX balance");
// Get current value of the entire position
address[] memory path = new address[](2);
path[0] = SPX6900;
path[1] = UNISWAP_ROUTER.WETH();
uint256[] memory amounts = UNISWAP_ROUTER.getAmountsOut(position.spxAmount, path);
uint256 currentValue = amounts[1]; // Total ETH value
// Calculate profit
require(currentValue > position.ethAmount, "Position not in profit");
uint256 profitEth = currentValue - position.ethAmount;
// Calculate what portion of SPX represents the profit
// profitEth / currentValue = profitPortion
// profitSpxAmount = position.spxAmount * profitPortion
uint256 profitSpxAmount = (position.spxAmount * profitEth) / currentValue;
require(profitSpxAmount > 0, "Profit amount too small");
require(profitSpxAmount <= position.spxAmount, "Invalid profit calculation");
// Swap ONLY the profit portion of SPX for ETH
uint256 ethBalanceBefore = address(this).balance;
SafeTransferLib.safeApprove(SPX6900, address(UNISWAP_ROUTER), profitSpxAmount);
UNISWAP_ROUTER.swapExactTokensForETHSupportingFeeOnTransferTokens(
profitSpxAmount,
0, // Accept any amount of ETH
path,
address(this),
block.timestamp + 300
);
uint256 ethReceived = address(this).balance - ethBalanceBefore;
// Update position tracking - remove ENTIRE position from holdings
spxHoldingBalance -= position.spxAmount;
spxCostBasis -= position.ethAmount;
openPositionCount--;
// Mark position as closed
position.closed = true;
position.closedAt = block.timestamp;
position.soldForEth = ethReceived; // Only the profit ETH
emit SPXSold(profitSpxAmount, ethReceived, ethReceived); // Sold amount, received, profit
emit SPXPositionClosed(positionId, profitSpxAmount, ethReceived, ethReceived);
}
/// @notice Sell all open SPX positions (owner only)
function sellAllSPXPositions() external onlyOwner nonReentrant {
require(openPositionCount > 0, "No open positions");
// Collect all position IDs to close
uint256[] memory positionsToClose = new uint256[](openPositionCount);
uint256 count = 0;
for (uint256 i = 0; i < spxPositions.length; i++) {
if (!spxPositions[i].closed) {
positionsToClose[count] = i;
count++;
}
}
// Close all positions
for (uint256 i = 0; i < count; i++) {
this.sellSPXPosition(positionsToClose[i]);
}
}
/// @notice Update the profit threshold for auto-selling
/// @param newThreshold New threshold percentage (e.g., 20 for 20%)
function setProfitThreshold(uint256 newThreshold) external onlyOwner {
require(newThreshold > 0 && newThreshold <= 100, "Invalid threshold");
profitThreshold = newThreshold;
emit ProfitThresholdUpdated(newThreshold);
}
/// @notice Execute an NFT trade or marketplace interaction
/// @param target The address to call (NFT marketplace, contract, etc.)
/// @param value Amount of ETH to send with the call
/// @param data Calldata to execute
/// @return success Whether the call succeeded
/// @return returnData Data returned from the call
function tradeNFT(address target, uint256 value, bytes calldata data)
external
onlyOwner
nonReentrant
returns (bool success, bytes memory returnData)
{
require(target != address(0), "Invalid target address");
require(address(this).balance >= value, "Insufficient ETH balance");
// Execute the call
(success, returnData) = target.call{value: value}(data);
emit NFTTradeExecuted(target, value, data, success);
return (success, returnData);
}
/// @notice Execute multiple NFT trades or marketplace interactions in a single transaction
/// @dev All operations are atomic - if any call fails, the entire transaction reverts
/// @param targets Array of addresses to call
/// @param values Array of ETH amounts to send with each call
/// @param dataArray Array of calldata to execute
function batchTradeNFT(address[] calldata targets, uint256[] calldata values, bytes[] calldata dataArray)
external
onlyOwner
nonReentrant
{
require(targets.length == values.length && values.length == dataArray.length, "Array length mismatch");
require(targets.length > 0, "Empty arrays");
uint256 totalValue = 0;
for (uint256 i = 0; i < values.length; i++) {
totalValue += values[i];
}
require(address(this).balance >= totalValue, "Insufficient ETH balance");
for (uint256 i = 0; i < targets.length; i++) {
require(targets[i] != address(0), "Invalid target address");
// Execute the call - revert if it fails
(bool success,) = targets[i].call{value: values[i]}(dataArray[i]);
require(success, "Batch operation failed");
emit NFTTradeExecuted(targets[i], values[i], dataArray[i], success);
}
}
/// @notice Update the auto-collect threshold
/// @param newThreshold New threshold in wei
function setAutoCollectThreshold(uint256 newThreshold) external onlyOwner {
autoCollectThreshold = newThreshold;
emit AutoCollectThresholdUpdated(newThreshold);
}
/// @notice Update the SPX buy threshold
/// @param newThreshold New threshold in wei (e.g., 1 ether)
function setSPXBuyThreshold(uint256 newThreshold) external onlyOwner {
require(newThreshold > 0, "Threshold must be > 0");
spxBuyThreshold = newThreshold;
emit SPXBuyThresholdUpdated(newThreshold);
}
/// @notice Manually execute SPX buy with pending ETH (owner only)
/// @dev Bypasses threshold check and buys with whatever is pending
function executePendingSPXBuy() external onlyOwner nonReentrant {
require(pendingEthForSPX > 0, "No pending ETH");
uint256 amountToBuy = pendingEthForSPX;
pendingEthForSPX = 0;
_swapEthForSPX(amountToBuy);
}
/// @notice Get total number of SPX positions
/// @return Total number of positions opened
function getSPXPositionCount() external view returns (uint256) {
return spxPositions.length;
}
/// @notice Get a specific SPX position by index
/// @param index Position index
/// @return ethAmount ETH spent on position
/// @return spxAmount SPX tokens received
/// @return timestamp When position was opened
/// @return blockNumber Block number of purchase
function getSPXPosition(uint256 index)
external
view
returns (uint256 ethAmount, uint256 spxAmount, uint256 timestamp, uint256 blockNumber)
{
require(index < spxPositions.length, "Invalid index");
SPXPosition memory pos = spxPositions[index];
return (pos.ethAmount, pos.spxAmount, pos.timestamp, pos.blockNumber);
}
/// @notice Get all SPX positions
/// @return Array of all positions
function getAllSPXPositions() external view returns (SPXPosition[] memory) {
return spxPositions;
}
/// @notice Get pending ETH status
/// @return pending Current pending ETH amount
/// @return threshold SPX buy threshold
/// @return percentOfThreshold Percentage of threshold reached (100 = 100%)
function getPendingETHStatus()
external
view
returns (uint256 pending, uint256 threshold, uint256 percentOfThreshold)
{
pending = pendingEthForSPX;
threshold = spxBuyThreshold;
percentOfThreshold = threshold > 0 ? (pending * 100) / threshold : 0;
}
/// @notice Get available ETH for NFT purchases
/// @dev NFT sale proceeds are reserved for burning and NOT available for buying more NFTs
/// @return available ETH currently available for buying NFTs (balance - pendingEthForSPX - pendingEthForBurn)
/// @return totalAllocated ETH currently locked in unsold Aeon NFT inventory
function getAvailableNFTEth() external view returns (uint256 available, uint256 totalAllocated) {
// Calculate reserved ETH (for SPX purchases and burning)
uint256 reservedEth = pendingEthForSPX + pendingEthForBurn;
// Available = contract balance minus all reserved ETH
// NFT sale proceeds are reserved for burning ONLY
available = address(this).balance > reservedEth ? address(this).balance - reservedEth : 0;
// TotalAllocated = ETH locked in unsold Aeon NFTs (inventory cost basis)
totalAllocated = aeonNFTLockedEth;
}
/// @notice Get all open (unsold) position IDs
/// @return Array of open position IDs
function getOpenPositions() external view returns (uint256[] memory) {
uint256[] memory openPositions = new uint256[](openPositionCount);
uint256 count = 0;
for (uint256 i = 0; i < spxPositions.length; i++) {
if (!spxPositions[i].closed) {
openPositions[count] = i;
count++;
}
}
return openPositions;
}
/// @notice Get all closed (sold) position IDs
/// @return Array of closed position IDs
function getClosedPositions() external view returns (uint256[] memory) {
uint256 closedCount = spxPositions.length - openPositionCount;
uint256[] memory closedPositions = new uint256[](closedCount);
uint256 count = 0;
for (uint256 i = 0; i < spxPositions.length; i++) {
if (spxPositions[i].closed) {
closedPositions[count] = i;
count++;
}
}
return closedPositions;
}
/// @notice Calculate profit/loss for a specific position
/// @param positionId Position ID
/// @return currentValue Current value in ETH
/// @return profitLoss Profit or loss in ETH
/// @return profitPercent Profit percentage (can be negative)
/// @return isProfitable Whether position meets profit threshold
function getPositionProfitLoss(uint256 positionId)
external
view
returns (uint256 currentValue, int256 profitLoss, int256 profitPercent, bool isProfitable)
{
require(positionId < spxPositions.length, "Invalid position ID");
SPXPosition memory position = spxPositions[positionId];
if (position.closed) {
// For closed positions, use the actual sold amount
currentValue = position.soldForEth;
profitLoss = int256(position.soldForEth) - int256(position.ethAmount);
} else {
// For open positions, get current market value
address[] memory path = new address[](2);
path[0] = SPX6900;
path[1] = UNISWAP_ROUTER.WETH();
try UNISWAP_ROUTER.getAmountsOut(position.spxAmount, path) returns (uint256[] memory amounts) {
currentValue = amounts[1]; // ETH we'd receive
profitLoss = int256(currentValue) - int256(position.ethAmount);
} catch {
currentValue = 0;
profitLoss = 0;
}
}
if (position.ethAmount > 0) {
profitPercent = (profitLoss * 100) / int256(position.ethAmount);
} else {
profitPercent = 0;
}
isProfitable = profitPercent >= int256(profitThreshold);
}
/// @notice Check if a specific position should be sold
/// @param positionId Position ID
/// @return Whether position meets profit threshold
function shouldSellPosition(uint256 positionId) external view returns (bool) {
(,,, bool isProfitable) = this.getPositionProfitLoss(positionId);
return isProfitable;
}
/* ═══════════════════════════════════════════════════ */
/* AEON NFT FUNCTIONS */
/* ═══════════════════════════════════════════════════ */
/// @notice Record a new Aeon NFT listing
/// @param tokenId The Aeon NFT token ID
/// @param purchasePrice ETH spent to buy the NFT
/// @param listPrice ETH listed for (should be purchasePrice * 1.2)
function recordAeonListing(uint256 tokenId, uint256 purchasePrice, uint256 listPrice) external onlyOwner {
aeonListings.push(
AeonNFTListing({
tokenId: tokenId,
purchasePrice: purchasePrice,
listPrice: listPrice,
listedAt: block.timestamp,
sold: false,
soldAt: 0
})
);
// Track ETH locked in this NFT
aeonNFTLockedEth += purchasePrice;
emit AeonNFTListed(aeonListings.length - 1, tokenId, purchasePrice, listPrice);
}
/// @notice Check for Aeon NFT sales and execute buy/burn for sold NFTs
/// @dev Loops through all unsold listings, checks ownership, and processes sales
function processAeonSales() external onlyOwner nonReentrant {
for (uint256 i = 0; i < aeonListings.length; i++) {
AeonNFTListing storage listing = aeonListings[i];
// Skip if already marked as sold
if (listing.sold) continue;
// Check if we still own this NFT
address currentOwner = IERC721(AEON_COLLECTION).ownerOf(listing.tokenId);
// If we don't own it anymore, it was sold
if (currentOwner != address(this)) {
// Mark as sold
listing.sold = true;
listing.soldAt = block.timestamp;
// Release locked ETH (NFT is no longer in inventory)
if (aeonNFTLockedEth >= listing.purchasePrice) {
aeonNFTLockedEth -= listing.purchasePrice;
} else {
aeonNFTLockedEth = 0; // Safety: prevent underflow
}
// Calculate actual ETH received after OpenSea fee (2.5%)
// We list at listPrice, but receive listPrice * 97.5%
uint256 actualProceeds = (listing.listPrice * 975) / 1000;
// Reserve this ETH for burning only (not available for NFT purchases)
pendingEthForBurn += actualProceeds;
// Buy and burn tokens with the actual sale proceeds
uint256 tokensBurned = _buyAndBurnTokens(actualProceeds);
emit AeonNFTSold(i, listing.tokenId, actualProceeds, tokensBurned);
}
}
}
/// @notice Internal function to buy tokens with ETH and burn them
/// @dev Uses Uniswap V4 pool to swap ETH for tokens
/// @param ethAmount Amount of ETH to use for buying tokens
/// @return tokensBurned Amount of tokens burned
function _buyAndBurnTokens(uint256 ethAmount) internal returns (uint256 tokensBurned) {
require(ethAmount > 0, "ETH amount must be > 0");
// Use available balance if slightly less than requested (safety margin)
uint256 availableBalance = address(this).balance;
if (availableBalance < ethAmount) {
// Only proceed if shortage is small (within 1%)
if (ethAmount - availableBalance > ethAmount / 100) {
return 0; // Skip if shortage is > 1%
}
ethAmount = availableBalance; // Use what we have
}
uint256 balanceBefore = balanceOf(address(this));
// Swap ETH for tokens using Uniswap V4
_swapEthForTokens(ethAmount);
uint256 balanceAfter = balanceOf(address(this));
tokensBurned = balanceAfter - balanceBefore;
// Burn the tokens by transferring to dead address
if (tokensBurned > 0) {
_transfer(address(this), DEAD_ADDRESS, tokensBurned);
}
// Deduct the ETH used from pending burn amount
if (pendingEthForBurn >= ethAmount) {
pendingEthForBurn -= ethAmount;
} else {
pendingEthForBurn = 0; // Safety: prevent underflow
}
return tokensBurned;
}
/// @notice Get total number of Aeon listings
function getAeonListingCount() external view returns (uint256) {
return aeonListings.length;
}
/// @notice Get a specific Aeon listing by index
function getAeonListing(uint256 index)
external
view
returns (uint256 tokenId, uint256 purchasePrice, uint256 listPrice, uint256 listedAt, bool sold, uint256 soldAt)
{
require(index < aeonListings.length, "Invalid index");
AeonNFTListing memory listing = aeonListings[index];
return
(listing.tokenId, listing.purchasePrice, listing.listPrice, listing.listedAt, listing.sold, listing.soldAt);
}
/// @notice Get all unsold Aeon listings
function getUnsoldAeonListings() external view returns (uint256[] memory) {
uint256 unsoldCount = 0;
// Count unsold listings
for (uint256 i = 0; i < aeonListings.length; i++) {
if (!aeonListings[i].sold) {
unsoldCount++;
}
}
// Build array of unsold listing indices
uint256[] memory unsoldIndices = new uint256[](unsoldCount);
uint256 currentIndex = 0;
for (uint256 i = 0; i < aeonListings.length; i++) {
if (!aeonListings[i].sold) {
unsoldIndices[currentIndex] = i;
currentIndex++;
}
}
return unsoldIndices;
}
/// @notice Update the Aeon markup percentage
/// @param newPercent New markup percentage (e.g., 20 for 20%)
function setAeonMarkupPercent(uint256 newPercent) external onlyOwner {
require(newPercent > 0 && newPercent <= 100, "Invalid markup percent");
aeonMarkupPercent = newPercent;
emit AeonMarkupPercentUpdated(newPercent);
}
/* ═══════════════════════════════════════════════════ */
/* EIP-1271 SIGNATURE */
/* ═══════════════════════════════════════════════════ */
/// @notice EIP-1271 signature validation for Seaport listings
/// @dev Validates that the signature was created by the contract owner
/// @param hash Hash of the data being signed (Seaport order hash)
/// @param signature Signature bytes created by the owner
/// @return magicValue EIP-1271 magic value (0x1626ba7e) if valid, 0xffffffff if invalid
function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue) {
// Verify that the signature was created by the owner
bool isValid = SignatureCheckerLib.isValidSignatureNow(owner(), hash, signature);
// Return EIP-1271 magic value if valid
return isValid ? bytes4(0x1626ba7e) : bytes4(0xffffffff);
}
/// @notice Allows the contract to receive ETH
receive() external payable {}
}
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
}
interface IERC721 {
function ownerOf(uint256 tokenId) external view returns (address);
function setApprovalForAll(address operator, bool approved) external;
}
"
},
"dependencies/solady-0.1.26/src/tokens/ERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple ERC20 + EIP-2612 implementation.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol)
///
/// @dev Note:
/// - The ERC20 standard allows minting and transferring to and from the zero address,
/// minting and transferring zero tokens, as well as self-approvals.
/// For performance, this implementation WILL NOT revert for such actions.
/// Please add any checks with overrides if desired.
/// - The `permit` function uses the ecrecover precompile (0x1).
///
/// If you are overriding:
/// - NEVER violate the ERC20 invariant:
/// the total sum of all balances must be equal to `totalSupply()`.
/// - Check that the overridden function is actually used in the function you want to
/// change the behavior of. Much of the code has been manually inlined for performance.
abstract contract ERC20 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The total supply has overflowed.
error TotalSupplyOverflow();
/// @dev The allowance has overflowed.
error AllowanceOverflow();
/// @dev The allowance has underflowed.
error AllowanceUnderflow();
/// @dev Insufficient balance.
error InsufficientBalance();
/// @dev Insufficient allowance.
error InsufficientAllowance();
/// @dev The permit is invalid.
error InvalidPermit();
/// @dev The permit has expired.
error PermitExpired();
/// @dev The allowance of Permit2 is fixed at infinity.
error Permit2AllowanceIsFixedAtInfinity();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted when `amount` tokens is transferred from `from` to `to`.
event Transfer(address indexed from, address indexed to, uint256 amount);
/// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`.
event Approval(address indexed owner, address indexed spender, uint256 amount);
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
/// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
uint256 private constant _APPROVAL_EVENT_SIGNATURE =
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The storage slot for the total supply.
uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c;
/// @dev The balance slot of `owner` is given by:
/// ```
/// mstore(0x0c, _BALANCE_SLOT_SEED)
/// mstore(0x00, owner)
/// let balanceSlot := keccak256(0x0c, 0x20)
/// ```
uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2;
/// @dev The allowance slot of (`owner`, `spender`) is given by:
/// ```
/// mstore(0x20, spender)
/// mstore(0x0c, _ALLOWANCE_SLOT_SEED)
/// mstore(0x00, owner)
/// let allowanceSlot := keccak256(0x0c, 0x34)
/// ```
uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20;
/// @dev The nonce slot of `owner` is given by:
/// ```
/// mstore(0x0c, _NONCES_SLOT_SEED)
/// mstore(0x00, owner)
/// let nonceSlot := keccak256(0x0c, 0x20)
/// ```
uint256 private constant _NONCES_SLOT_SEED = 0x38377508;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev `(_NONCES_SLOT_SEED << 16) | 0x1901`.
uint256 private constant _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX = 0x383775081901;
/// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
bytes32 private constant _DOMAIN_TYPEHASH =
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
/// @dev `keccak256("1")`.
/// If you need to use a different version, override `_versionHash`.
bytes32 private constant _DEFAULT_VERSION_HASH =
0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6;
/// @dev `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`.
bytes32 private constant _PERMIT_TYPEHASH =
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
/// @dev The canonical Permit2 address.
/// For signature-based allowance granting for single transaction ERC20 `transferFrom`.
/// Enabled by default. To disable, override `_givePermit2InfiniteAllowance()`.
/// [Github](https://github.com/Uniswap/permit2)
/// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
address internal constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 METADATA */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the name of the token.
function name() public view virtual returns (string memory);
/// @dev Returns the symbol of the token.
function symbol() public view virtual returns (string memory);
/// @dev Returns the decimals places of the token.
function decimals() public view virtual returns (uint8) {
return 18;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the amount of tokens in existence.
function totalSupply() public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(_TOTAL_SUPPLY_SLOT)
}
}
/// @dev Returns the amount of tokens owned by `owner`.
function balanceOf(address owner) public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`.
function allowance(address owner, address spender)
public
view
virtual
returns (uint256 result)
{
if (_givePermit2InfiniteAllowance()) {
if (spender == _PERMIT2) return type(uint256).max;
}
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x34))
}
}
/// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
///
/// Emits a {Approval} event.
function approve(address spender, uint256 amount) public virtual returns (bool) {
if (_givePermit2InfiniteAllowance()) {
/// @solidity memory-safe-assembly
assembly {
// If `spender == _PERMIT2 && amount != type(uint256).max`.
if iszero(or(xor(shr(96, shl(96, spender)), _PERMIT2), iszero(not(amount)))) {
mstore(0x00, 0x3f68539a) // `Permit2AllowanceIsFixedAtInfinity()`.
revert(0x1c, 0x04)
}
}
}
/// @solidity memory-safe-assembly
assembly {
// Compute the allowance slot and store the amount.
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x34), amount)
// Emit the {Approval} event.
mstore(0x00, amount)
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c)))
}
return true;
}
/// @dev Transfer `amount` tokens from the caller to `to`.
///
/// Requirements:
/// - `from` must at least have `amount`.
///
/// Emits a {Transfer} event.
function transfer(address to, uint256 amount) public virtual returns (bool) {
_beforeTokenTransfer(msg.sender, to, amount);
/// @solidity memory-safe-assembly
assembly {
// Compute the balance slot and load its value.
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, caller())
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c)))
}
_afterTokenTransfer(msg.sender, to, amount);
return true;
}
/// @dev Transfers `amount` tokens from `from` to `to`.
///
/// Note: Does not update the allowance if it is the maximum uint256 value.
///
/// Requirements:
/// - `from` must at least have `amount`.
/// - The caller must have at least `amount` of allowance to transfer the tokens of `from`.
///
/// Emits a {Transfer} event.
function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
_beforeTokenTransfer(from, to, amount);
// Code duplication is for zero-cost abstraction if possible.
if (_givePermit2InfiniteAllowance()) {
/// @solidity memory-safe-assembly
assembly {
let from_ := shl(96, from)
if iszero(eq(caller(), _PERMIT2)) {
// Compute the allowance slot and load its value.
mstore(0x20, caller())
mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED))
let allowanceSlot := keccak256(0x0c, 0x34)
let allowance_ := sload(allowanceSlot)
// If the allowance is not the maximum uint256 value.
if not(allowance_) {
// Revert if the amount to be transferred exceeds the allowance.
if gt(amount, allowance_) {
mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated allowance.
sstore(allowanceSlot, sub(allowance_, amount))
}
}
// Compute the balance slot and load its value.
mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
}
} else {
/// @solidity memory-safe-assembly
assembly {
let from_ := shl(96, from)
// Compute the allowance slot and load its value.
mstore(0x20, caller())
mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED))
let allowanceSlot := keccak256(0x0c, 0x34)
let allowance_ := sload(allowanceSlot)
// If the allowance is not the maximum uint256 value.
if not(allowance_) {
// Revert if the amount to be transferred exceeds the allowance.
if gt(amount, allowance_) {
mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated allowance.
sstore(allowanceSlot, sub(allowance_, amount))
}
// Compute the balance slot and load its value.
mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
}
}
_afterTokenTransfer(from, to, amount);
return true;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EIP-2612 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev For more performance, override to return the constant value
/// of `keccak256(bytes(name()))` if `name()` will never change.
function _constantNameHash() internal view virtual returns (bytes32 result) {}
/// @dev If you need a different value, override this function.
function _versionHash() internal view virtual returns (bytes32 result) {
result = _DEFAULT_VERSION_HASH;
}
/// @dev For inheriting contracts to increment the nonce.
function _incrementNonce(address owner) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _NONCES_SLOT_SEED)
mstore(0x00, owner)
let nonceSlot := keccak256(0x0c, 0x20)
sstore(nonceSlot, add(1, sload(nonceSlot)))
}
}
/// @dev Returns the current nonce for `owner`.
/// This value is used to compute the signature for EIP-2612 permit.
function nonces(address owner) public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
// Compute the nonce slot and load its value.
mstore(0x0c, _NONCES_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`,
/// authorized by a signed approval by `owner`.
///
/// Emits a {Approval} event.
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
if (_givePermit2InfiniteAllowance()) {
/// @solidity memory-safe-assembly
assembly {
// If `spender == _PERMIT2 && value != type(uint256).max`.
if iszero(or(xor(shr(96, shl(96, spender)), _PERMIT2), iszero(not(value)))) {
mstore(0x00, 0x3f68539a) // `Permit2AllowanceIsFixedAtInfinity()`.
revert(0x1c, 0x04)
}
}
}
bytes32 nameHash = _constantNameHash();
// We simply calculate it on-the-fly to allow for cases where the `name` may change.
if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name()));
bytes32 versionHash = _versionHash();
/// @solidity memory-safe-assembly
assembly {
// Revert if the block timestamp is greater than `deadline`.
if gt(timestamp(), deadline) {
mstore(0x00, 0x1a15a3cc) // `PermitExpired()`.
revert(0x1c, 0x04)
}
let m := mload(0x40) // Grab the free memory pointer.
// Clean the upper 96 bits.
owner := shr(96, shl(96, owner))
spender := shr(96, shl(96, spender))
// Compute the nonce slot and load its value.
mstore(0x0e, _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX)
mstore(0x00, owner)
let nonceSlot := keccak256(0x0c, 0x20)
let nonceValue := sload(nonceSlot)
// Prepare the domain separator.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), nameHash)
mstore(add(m, 0x40), versionHash)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
mstore(0x2e, keccak256(m, 0xa0))
// Prepare the struct hash.
mstore(m, _PERMIT_TYPEHASH)
mstore(add(m, 0x20), owner)
mstore(add(m, 0x40), spender)
mstore(add(m, 0x60), value)
mstore(add(m, 0x80), nonceValue)
mstore(add(m, 0xa0), deadline)
mstore(0x4e, keccak256(m, 0xc0))
// Prepare the ecrecover calldata.
mstore(0x00, keccak256(0x2c, 0x42))
mstore(0x20, and(0xff, v))
mstore(0x40, r)
mstore(0x60, s)
let t := staticcall(gas(), 1, 0x00, 0x80, 0x20, 0x20)
// If the ecrecover fails, the returndatasize will be 0x00,
// `owner` will be checked if it equals the hash at 0x00,
// which evaluates to false (i.e. 0), and we will revert.
// If the ecrecover succeeds, the returndatasize will be 0x20,
// `owner` will be compared against the returned address at 0x20.
if iszero(eq(mload(returndatasize()), owner)) {
mstore(0x00, 0xddafbaef) // `InvalidPermit()`.
revert(0x1c, 0x04)
}
// Increment and store the updated nonce.
sstore(nonceSlot, add(nonceValue, t)) // `t` is 1 if ecrecover succeeds.
// Compute the allowance slot and store the value.
// The `owner` is already at slot 0x20.
mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender))
sstore(keccak256(0x2c, 0x34), value)
// Emit the {Approval} event.
log3(add(m, 0x60), 0
Submitted on: 2025-10-09 09:18:16
Comments
Log in to comment.
No comments yet.