Skip to content

Instantly share code, notes, and snippets.

@wenakita
Created May 9, 2025 10:18
Show Gist options
  • Save wenakita/fa09a829d25f412bf8b7cdbcaffcb6ec to your computer and use it in GitHub Desktop.
Save wenakita/fa09a829d25f412bf8b7cdbcaffcb6ec to your computer and use it in GitHub Desktop.
Contract Verification
// 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