Created
May 9, 2025 10:18
-
-
Save wenakita/fa09a829d25f412bf8b7cdbcaffcb6ec to your computer and use it in GitHub Desktop.
Contract Verification
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
/** | |
* ████████╗ ██████╗ ██╗ ██╗███████╗███╗ ██╗ | |
* ╚══██╔══╝██╔═══██╗██║ ██╔╝██╔════╝████╗ ██║ | |
* ██║ ██║ ██║█████╔╝ █████╗ ██╔██╗ ██║ | |
* ██║ ██║ ██║██╔═██╗ ██╔══╝ ██║╚██╗██║ | |
* ██║ ╚██████╔╝██║ ██╗███████╗██║ ╚████║ | |
* ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ | |
* SONIC RED DRAGON | |
* | |
* LayerZero V2 Compatible Token | |
* | |
* https://x.com/sonicreddragon | |
* https://t.me/sonic_reddragon_bot | |
*/ | |
// IMPORTANT DRAGON PROJECT RULES: | |
// - 10% fee is applied to all buys and sells of $DRAGON | |
// - Buy fees: 6.9% to jackpot, 2.41% to ve69LPfeedistributor | |
// - Sell fees: 6.9% to jackpot, 2.41% to ve69LPfeedistributor | |
// - 0.69% of DRAGON is burned for all transfers | |
pragma solidity ^0.8.0; | |
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | |
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | |
import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; | |
import "@openzeppelin/contracts/access/Ownable.sol"; | |
import "./interfaces/IDragonSwapTrigger.sol"; | |
import "./interfaces/IDragonJackpotVault.sol"; | |
import "./interfaces/IChainRegistry.sol"; | |
/** | |
* @title OmniDragon | |
* @dev Specialized OFTv2 implementation with direct LayerZero V2 integration | |
*/ | |
contract OmniDragon is ERC20, Ownable, ReentrancyGuard { | |
using SafeERC20 for IERC20; | |
// ======== Storage ======== | |
address public lzEndpoint; // LayerZero endpoint | |
address public jackpotVault; // Jackpot vault | |
address public ve69LPFeeDistributor; // ve69LPFeeDistributor | |
address public wrappedNativeToken; // WSONIC/WETH - Placeholder in constructor | |
address public swapTrigger; // Swap trigger for lottery entries | |
address public chainRegistry; // Chain registry for chain ID verification | |
// Fee structures | |
struct Fees { | |
uint256 jackpot; | |
uint256 ve69LP; | |
uint256 burn; | |
uint256 total; | |
} | |
Fees public buyFees; | |
Fees public sellFees; | |
// Fee exclusions | |
mapping(address => bool) public isExcludedFromFees; | |
// LayerZero specific variables | |
uint16 public constant SONIC_CHAIN_ID = 332; // Sonic's LayerZero chain ID | |
mapping(uint16 => bytes) public trustedRemoteLookup; | |
mapping(uint16 => uint) public minDstGasLookup; | |
// Flag to track if configuration has been completed | |
bool public hasBeenFullyConfigured; | |
// Add flag for initial minting | |
bool public initialMintingDone; | |
// ======== Errors ======== | |
error JackpotVaultZeroAddress(); | |
error Ve69LPFeeDistributorZeroAddress(); | |
error WrappedNativeTokenNotSet(); | |
error LzEndpointZeroAddress(); | |
error ChainRegistryZeroAddress(); | |
error ZeroAddress(); | |
error ZeroAmount(); | |
error NotAuthorized(); | |
error RegistrationFailed(); | |
error InvalidEndpoint(); | |
error InvalidPayload(); | |
error InvalidSource(); | |
error AlreadyConfigured(); | |
error NotSonicChain(); | |
error InitialMintingAlreadyDone(); | |
error MaxSupplyExceeded(); | |
// ======== Events ======== | |
event ExcludedFromFees(address indexed account, bool isExcluded); | |
event FeesUpdated(string feeType, uint256 jackpotFee, uint256 ve69Fee, uint256 burnFee, uint256 totalFee); | |
event FeeTransferred(address indexed recipient, uint256 amount, string feeType); | |
event TokensBurned(uint256 amount); | |
event SetTrustedRemote(uint16 _srcChainId, bytes _path); | |
event SendToChain( | |
uint16 indexed _dstChainId, | |
address indexed _from, | |
bytes indexed _toAddress, | |
uint _amount | |
); | |
event ReceiveFromChain( | |
uint16 indexed _srcChainId, | |
bytes indexed _srcAddress, | |
address indexed _toAddress, | |
uint _amount | |
); | |
event WrappedNativeTokenSet(address indexed oldAddress, address indexed newAddress); | |
event LzEndpointUpdated(address indexed oldEndpoint, address indexed newEndpoint); | |
event ContractFullyConfigured(); | |
event InitialMintingPerformed(address indexed to, uint256 amount); | |
// Max supply for the token (6.942 million) | |
uint256 public constant MAX_SUPPLY = 6942000 * 10**18; | |
// Initial supply to mint (6.942 million) | |
uint256 public constant INITIAL_SUPPLY = 6942000 * 10**18; | |
/** | |
* @dev Constructor to initialize the token with deployer address as placeholder for wrapped token | |
* @param _name Token name | |
* @param _symbol Token symbol | |
* @param _jackpotVault Jackpot vault address | |
* @param _ve69LPFeeDistributor ve69LP fee distributor address | |
* @param _lzEndpoint LayerZero endpoint address | |
* @param _chainRegistry Chain registry address | |
*/ | |
constructor( | |
string memory _name, | |
string memory _symbol, | |
address _jackpotVault, | |
address _ve69LPFeeDistributor, | |
address _lzEndpoint, | |
address _chainRegistry | |
) ERC20(_name, _symbol) Ownable() { | |
if (_jackpotVault == address(0)) revert JackpotVaultZeroAddress(); | |
if (_ve69LPFeeDistributor == address(0)) revert Ve69LPFeeDistributorZeroAddress(); | |
if (_lzEndpoint == address(0)) revert InvalidEndpoint(); | |
if (_chainRegistry == address(0)) revert ChainRegistryZeroAddress(); | |
jackpotVault = _jackpotVault; | |
ve69LPFeeDistributor = _ve69LPFeeDistributor; | |
lzEndpoint = _lzEndpoint; | |
chainRegistry = _chainRegistry; | |
// Use deployer address as placeholder for wrapped native token | |
// This ensures bytecode is identical across chains | |
wrappedNativeToken = msg.sender; | |
// Set up default fees | |
buyFees.jackpot = 690; // 6.9% | |
buyFees.ve69LP = 241; // 2.41% | |
buyFees.burn = 69; // 0.69% | |
buyFees.total = 1000; // 10% | |
sellFees.jackpot = 690; // 6.9% | |
sellFees.ve69LP = 241; // 2.41% | |
sellFees.burn = 69; // 0.69% | |
sellFees.total = 1000; // 10% | |
// Exclude addresses from fees | |
isExcludedFromFees[address(this)] = true; | |
isExcludedFromFees[_jackpotVault] = true; | |
isExcludedFromFees[_ve69LPFeeDistributor] = true; | |
isExcludedFromFees[owner()] = true; | |
// NOTE: In the updated version, we DO NOT mint tokens in the constructor | |
// Initial minting must be done explicitly only on Sonic chain | |
initialMintingDone = false; | |
} | |
/** | |
* @dev Sets the wrapped native token address after deployment | |
* This allows us to deploy with identical bytecode across chains | |
* @param _wrappedNativeToken The actual wrapped native token address (WSONIC/WETH) | |
*/ | |
function setWrappedNativeToken(address _wrappedNativeToken) external onlyOwner { | |
if (_wrappedNativeToken == address(0)) revert WrappedNativeTokenNotSet(); | |
// Store old address for event | |
address oldAddress = wrappedNativeToken; | |
// Set new address | |
wrappedNativeToken = _wrappedNativeToken; | |
// Exclude from fees | |
isExcludedFromFees[_wrappedNativeToken] = true; | |
emit WrappedNativeTokenSet(oldAddress, _wrappedNativeToken); | |
} | |
/** | |
* @dev Sets the LayerZero endpoint address after deployment | |
* This allows us to deploy with identical bytecode across chains | |
* @param _lzEndpoint The actual LayerZero endpoint address | |
*/ | |
function setLzEndpoint(address _lzEndpoint) external onlyOwner { | |
if (_lzEndpoint == address(0)) revert LzEndpointZeroAddress(); | |
if (hasBeenFullyConfigured) revert AlreadyConfigured(); | |
// Store old address for event | |
address oldEndpoint = lzEndpoint; | |
// Set new address | |
lzEndpoint = _lzEndpoint; | |
emit LzEndpointUpdated(oldEndpoint, _lzEndpoint); | |
} | |
/** | |
* @dev Mark contract as fully configured | |
* This is a safety measure to prevent accidental changes after full configuration | |
*/ | |
function markAsFullyConfigured() external onlyOwner { | |
hasBeenFullyConfigured = true; | |
emit ContractFullyConfigured(); | |
} | |
/** | |
* @dev OFTv2 functionality: Set trusted remote address for a chain | |
*/ | |
function setTrustedRemote(uint16 _srcChainId, bytes calldata _path) external onlyOwner { | |
trustedRemoteLookup[_srcChainId] = _path; | |
emit SetTrustedRemote(_srcChainId, _path); | |
} | |
/** | |
* @dev OFTv2 functionality: Set the minimum gas required on the destination | |
*/ | |
function setMinDstGas(uint16 _dstChainId, uint _minGas) external onlyOwner { | |
minDstGasLookup[_dstChainId] = _minGas; | |
} | |
/** | |
* @dev V2 compatibility: Check if a peer is trusted for a specific chain | |
*/ | |
function isPeer(uint16 _srcEid, bytes32 _srcAddress) public view returns (bool) { | |
if (trustedRemoteLookup[_srcEid].length == 0) return false; | |
return keccak256(abi.encodePacked(_srcAddress)) == keccak256(trustedRemoteLookup[_srcEid]); | |
} | |
/** | |
* @dev V2 compatibility: Allow path initialization | |
*/ | |
function allowInitializePath(Origin calldata _origin) external view returns (bool) { | |
return trustedRemoteLookup[_origin.srcEid].length > 0; | |
} | |
/** | |
* @dev OFTv2 functionality: Estimate send fee for a cross-chain transfer | |
*/ | |
function estimateSendFee( | |
uint16 _dstChainId, | |
bytes calldata _toAddress, | |
uint _amount, | |
bool _useZro, | |
bytes calldata _adapterParams | |
) public view returns (uint nativeFee, uint zroFee) { | |
bytes memory payload = abi.encode(_toAddress, _amount); | |
return _estimateFee(_dstChainId, payload, _useZro, _adapterParams); | |
} | |
/** | |
* @dev Internal function to estimate fees for a cross-chain transfer | |
*/ | |
function _estimateFee( | |
uint16 _dstChainId, | |
bytes memory _payload, | |
bool _useZro, | |
bytes memory _adapterParams | |
) internal view returns (uint nativeFee, uint zroFee) { | |
try ILayerZeroEndpoint(lzEndpoint).estimateFees( | |
_dstChainId, | |
address(this), | |
_payload, | |
_useZro, | |
_adapterParams | |
) returns (uint _nativeFee, uint _zroFee) { | |
return (_nativeFee, _zroFee); | |
} catch { | |
// If estimation fails, return a default fee | |
return (0.01 ether, 0); | |
} | |
} | |
/** | |
* @dev OFTv2 functionality: Send tokens to another chain | |
*/ | |
function sendTokens( | |
uint16 _dstChainId, | |
bytes32 _toAddress, | |
uint _amount, | |
address payable _refundAddress, | |
address _zroPaymentAddress, | |
bytes calldata _adapterParams | |
) external payable { | |
_debitFrom(msg.sender, _amount); | |
bytes memory payload = abi.encode(_toAddress, _amount); | |
_lzSend( | |
_dstChainId, | |
payload, | |
_refundAddress, | |
_zroPaymentAddress, | |
_adapterParams | |
); | |
emit SendToChain(_dstChainId, msg.sender, abi.encodePacked(_toAddress), _amount); | |
} | |
/** | |
* @dev Send a message via LayerZero with proper error handling | |
*/ | |
function _lzSend( | |
uint16 _dstChainId, | |
bytes memory _payload, | |
address payable _refundAddress, | |
address _zroPaymentAddress, | |
bytes memory _adapterParams | |
) internal { | |
try ILayerZeroEndpoint(lzEndpoint).send{value: msg.value}( | |
_dstChainId, | |
trustedRemoteLookup[_dstChainId], | |
_payload, | |
_refundAddress, | |
_zroPaymentAddress, | |
_adapterParams | |
) {} catch (bytes memory) { | |
// Revert all operations if the send fails | |
revert InvalidEndpoint(); | |
} | |
} | |
/** | |
* @dev OFTv2 functionality: Receive tokens from another chain - Updated for V2 | |
*/ | |
function lzReceive( | |
Origin calldata _origin, | |
bytes32 _guid, | |
bytes calldata _message, | |
address _executor, | |
bytes calldata _extraData | |
) external { | |
// Verify source | |
if (msg.sender != lzEndpoint) revert InvalidSource(); | |
// Verify trusted remote | |
if (!isPeer(_origin.srcEid, _origin.sender)) revert InvalidSource(); | |
// Decode payload | |
(bytes memory toAddressBytes, uint amount) = abi.decode(_message, (bytes, uint)); | |
address toAddress; | |
assembly { | |
toAddress := mload(add(toAddressBytes, 20)) | |
} | |
// Credit tokens to the recipient | |
_creditTo(_origin.srcEid, toAddress, amount); | |
emit ReceiveFromChain(_origin.srcEid, abi.encodePacked(_origin.sender), toAddress, amount); | |
} | |
/** | |
* @dev Debit tokens from an account (burn) | |
*/ | |
function _debitFrom(address _from, uint _amount) internal returns (uint) { | |
address spender = _msgSender(); | |
if (_from != spender) { | |
_spendAllowance(_from, spender, _amount); | |
} | |
_burn(_from, _amount); | |
return _amount; | |
} | |
/** | |
* @dev Credit tokens to an account (mint) | |
*/ | |
function _creditTo(uint16 _srcChainId, address _toAddress, uint _amount) internal returns (uint) { | |
_mint(_toAddress, _amount); | |
return _amount; | |
} | |
/** | |
* @dev Sets jackpot vault address | |
*/ | |
function setJackpotVault(address _jackpotVault) external onlyOwner { | |
if (hasBeenFullyConfigured) revert AlreadyConfigured(); | |
if (_jackpotVault == address(0)) revert JackpotVaultZeroAddress(); | |
jackpotVault = _jackpotVault; | |
isExcludedFromFees[_jackpotVault] = true; | |
} | |
/** | |
* @dev Sets ve69LP fee distributor address | |
*/ | |
function setVe69LPFeeDistributor(address _ve69LPFeeDistributor) external onlyOwner { | |
if (hasBeenFullyConfigured) revert AlreadyConfigured(); | |
if (_ve69LPFeeDistributor == address(0)) revert Ve69LPFeeDistributorZeroAddress(); | |
ve69LPFeeDistributor = _ve69LPFeeDistributor; | |
isExcludedFromFees[_ve69LPFeeDistributor] = true; | |
} | |
/** | |
* @dev Sets swap trigger address | |
*/ | |
function setSwapTrigger(address _swapTrigger) external onlyOwner { | |
swapTrigger = _swapTrigger; | |
} | |
/** | |
* @dev Excludes account from fees | |
*/ | |
function setExcludedFromFees(address account, bool excluded) external onlyOwner { | |
isExcludedFromFees[account] = excluded; | |
emit ExcludedFromFees(account, excluded); | |
} | |
/** | |
* @dev Validates fee values to ensure they don't exceed limits | |
*/ | |
function _validateFees(uint256 jackpotFee, uint256 ve69Fee, uint256 burnFee) internal pure returns (uint256) { | |
uint256 total = jackpotFee + ve69Fee + burnFee; | |
require(total <= 1500, "Fees too high"); // Max 15% | |
return total; | |
} | |
/** | |
* @dev Sets buy fees | |
*/ | |
function setBuyFees(uint256 jackpotFee, uint256 ve69Fee, uint256 burnFee) external onlyOwner { | |
buyFees.total = _validateFees(jackpotFee, ve69Fee, burnFee); | |
buyFees.jackpot = jackpotFee; | |
buyFees.ve69LP = ve69Fee; | |
buyFees.burn = burnFee; | |
emit FeesUpdated("Buy", jackpotFee, ve69Fee, burnFee, buyFees.total); | |
} | |
/** | |
* @dev Sets sell fees | |
*/ | |
function setSellFees(uint256 jackpotFee, uint256 ve69Fee, uint256 burnFee) external onlyOwner { | |
sellFees.total = _validateFees(jackpotFee, ve69Fee, burnFee); | |
sellFees.jackpot = jackpotFee; | |
sellFees.ve69LP = ve69Fee; | |
sellFees.burn = burnFee; | |
emit FeesUpdated("Sell", jackpotFee, ve69Fee, burnFee, sellFees.total); | |
} | |
/** | |
* @dev Gets buy fees | |
*/ | |
function getBuyFees() external view returns ( | |
uint256 jackpotFee, uint256 ve69Fee, uint256 burnFee, uint256 totalFee | |
) { | |
return (buyFees.jackpot, buyFees.ve69LP, buyFees.burn, buyFees.total); | |
} | |
/** | |
* @dev Gets sell fees | |
*/ | |
function getSellFees() external view returns ( | |
uint256 jackpotFee, uint256 ve69Fee, uint256 burnFee, uint256 totalFee | |
) { | |
return (sellFees.jackpot, sellFees.ve69LP, sellFees.burn, sellFees.total); | |
} | |
/** | |
* @dev Calculate fees for a transfer | |
*/ | |
function _calculateFees( | |
uint256 amount, | |
Fees memory fees | |
) internal pure returns ( | |
uint256 jackpotAmount, | |
uint256 ve69Amount, | |
uint256 burnAmount, | |
uint256 remaining | |
) { | |
if (amount == 0) { | |
return (0, 0, 0, 0); | |
} | |
jackpotAmount = (amount * fees.jackpot) / 10000; | |
ve69Amount = (amount * fees.ve69LP) / 10000; | |
burnAmount = (amount * fees.burn) / 10000; | |
remaining = amount - (jackpotAmount + ve69Amount + burnAmount); | |
return (jackpotAmount, ve69Amount, burnAmount, remaining); | |
} | |
/** | |
* @dev Override _beforeTokenTransfer to apply burn fee | |
*/ | |
function _beforeTokenTransfer( | |
address from, | |
address to, | |
uint256 amount | |
) internal override { | |
super._beforeTokenTransfer(from, to, amount); | |
// Skip for mints, burns, excluded accounts | |
if (from == address(0) || to == address(0) || isExcludedFromFees[from] || isExcludedFromFees[to]) { | |
return; | |
} | |
// Apply burn fee (0.69%) | |
uint256 burnAmount = (amount * 69) / 10000; | |
if (burnAmount > 0) { | |
_burn(from, burnAmount); | |
emit TokensBurned(burnAmount); | |
} | |
} | |
/** | |
* @dev Distribute fees to jackpot and ve69LP | |
*/ | |
function _distributeFees(uint256 jackpotAmount, uint256 ve69Amount) internal { | |
if (jackpotAmount > 0 && jackpotVault != address(0)) { | |
IERC20(wrappedNativeToken).safeTransfer(jackpotVault, jackpotAmount); | |
IDragonJackpotVault(jackpotVault).addToJackpot(jackpotAmount); | |
emit FeeTransferred(jackpotVault, jackpotAmount, "Jackpot"); | |
} | |
if (ve69Amount > 0 && ve69LPFeeDistributor != address(0)) { | |
IERC20(wrappedNativeToken).safeTransfer(ve69LPFeeDistributor, ve69Amount); | |
emit FeeTransferred(ve69LPFeeDistributor, ve69Amount, "Ve69LP"); | |
} | |
} | |
/** | |
* @dev Burns tokens | |
*/ | |
function burn(uint256 amount) public { | |
if (amount == 0) return; | |
_burn(msg.sender, amount); | |
emit TokensBurned(amount); | |
} | |
/** | |
* @dev Adds to jackpot | |
*/ | |
function addToJackpot(uint256 amount) external onlyOwner { | |
if (amount == 0) revert ZeroAmount(); | |
IERC20(wrappedNativeToken).safeTransferFrom(msg.sender, jackpotVault, amount); | |
IDragonJackpotVault(jackpotVault).addToJackpot(amount); | |
emit FeeTransferred(jackpotVault, amount, "Jackpot"); | |
} | |
/** | |
* @dev Process entry for lottery | |
*/ | |
function processEntry(address user, uint256 nativeAmount) internal { | |
if (user == address(0)) revert ZeroAddress(); | |
if (msg.sender == tx.origin && swapTrigger != address(0)) { | |
IDragonSwapTrigger(swapTrigger).onSwapNativeTokenToDragon(user, nativeAmount); | |
} | |
} | |
/** | |
* @dev Process buy | |
*/ | |
function processBuy(address user, uint256 amount) public { | |
if (user == address(0)) revert ZeroAddress(); | |
if (amount == 0) revert ZeroAmount(); | |
processEntry(user, amount); | |
} | |
/** | |
* @dev Callback for swaps | |
*/ | |
function afterSwap(address, address to, uint256 amount) external { | |
if (msg.sender != swapTrigger) revert NotAuthorized(); | |
if (swapTrigger != address(0)) { | |
IDragonSwapTrigger(swapTrigger).onSwapNativeTokenToDragon(to, amount); | |
} | |
} | |
/** | |
* @dev Register on Sonic FeeM | |
*/ | |
function registerMe() external { | |
(bool success,) = address(0xDC2B0D2Dd2b7759D97D50db4eabDC36973110830).call( | |
abi.encodeWithSignature("selfRegister(uint256)", 143) | |
); | |
if (!success) revert RegistrationFailed(); | |
} | |
/** | |
* @dev Performs the initial minting of tokens (only on Sonic chain) | |
* Can only be called once and only on Sonic chain | |
*/ | |
function performInitialMinting(address recipient) external onlyOwner { | |
// Check if initial minting has already been done | |
if (initialMintingDone) revert InitialMintingAlreadyDone(); | |
// Check if we're on Sonic Chain | |
uint16 currentChainId = IChainRegistry(chainRegistry).getCurrentChainId(); | |
if (currentChainId != SONIC_CHAIN_ID) revert NotSonicChain(); | |
// Set flag to prevent future minting | |
initialMintingDone = true; | |
// Mint initial supply to the specified recipient | |
_mint(recipient, INITIAL_SUPPLY); | |
emit InitialMintingPerformed(recipient, INITIAL_SUPPLY); | |
} | |
/** | |
* @dev Mint tokens (owner only) | |
* Can only mint up to MAX_SUPPLY across all chains | |
*/ | |
function mint(address to, uint256 amount) external onlyOwner { | |
if (to == address(0)) revert ZeroAddress(); | |
if (amount == 0) revert ZeroAmount(); | |
uint256 totalSupplyValue = totalSupply(); | |
if (totalSupplyValue + amount > MAX_SUPPLY) revert MaxSupplyExceeded(); | |
_mint(to, amount); | |
} | |
// Allow the contract to receive ETH | |
receive() external payable {} | |
} | |
/** | |
* @dev Simplified LayerZero Endpoint interface | |
*/ | |
interface ILayerZeroEndpoint { | |
function send( | |
uint16 _dstChainId, | |
bytes calldata _destination, | |
bytes calldata _payload, | |
address payable _refundAddress, | |
address _zroPaymentAddress, | |
bytes calldata _adapterParams | |
) external payable; | |
function estimateFees( | |
uint16 _dstChainId, | |
address _userApplication, | |
bytes calldata _payload, | |
bool _payInZRO, | |
bytes calldata _adapterParams | |
) external view returns (uint nativeFee, uint zroFee); | |
} | |
/** | |
* @dev LayerZero V2 Origin struct | |
*/ | |
struct Origin { | |
uint16 srcEid; | |
bytes32 sender; | |
uint64 nonce; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment