Skip to content

Instantly share code, notes, and snippets.

@nazreen
Last active February 26, 2026 20:22
Show Gist options
  • Select an option

  • Save nazreen/bb89573f4e961b44c559d3274bebe163 to your computer and use it in GitHub Desktop.

Select an option

Save nazreen/bb89573f4e961b44c559d3274bebe163 to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
// OZ
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
// LZ
import { NativeOFTAdapter } from "./../lib/devtools/packages/oft-evm/contracts/NativeOFTAdapter.sol";
import { MessagingFee, MessagingReceipt } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
import { SendParam, OFTReceipt } from "./../lib/devtools/packages/oft-evm/contracts/interfaces/IOFT.sol";
contract XNativeAdapter is NativeOFTAdapter, Ownable2Step {
// XToken has a fixed total supply of 8_888_888_888 * 1e18
uint256 internal constant TOTAL_GLOBAL_SUPPLY = 8_888_888_888 ether;
uint256 public lockedTokens;
//--------------------------- Mappings ---------------------------
// Outbound limits
mapping(uint32 eid => uint256 outboundLimit) public outboundLimits;
mapping(uint32 eid => uint256 sentTokenAmount) public sentTokenAmounts;
mapping(uint32 eid => uint256 lastSentTimestamp) public lastSentTimestamps;
// Inbound limits
mapping(uint32 eid => uint256 inboundLimit) public inboundLimits;
mapping(uint32 eid => uint256 receivedTokenAmount) public receivedTokenAmounts;
mapping(uint32 eid => uint256 lastReceivedTimestamp) public lastReceivedTimestamps;
// If an address is whitelisted, limit checks are skipped
mapping(address addr => bool isWhitelisted) public whitelist;
// if an address is an operator is can disconnect/connect bridges via setPeers
mapping(address addr => bool isOperator) public operators;
//--------------------------- Events ---------------------------
event SetOutboundLimit(uint32 indexed eid, uint256 limit);
event SetInboundLimit(uint32 indexed eid, uint256 limit);
event SetWhitelist(address indexed addr, bool isWhitelist);
event SetOperator(address indexed addr, bool isWhitelist);
event TokensLocked(uint256 amount, uint256 totalLocked);
event ReceivedTokenAmountReset(uint32 indexed eid);
event SentTokenAmountReset(uint32 indexed eid);
//--------------------------- Errors ---------------------------
error ExceedInboundLimit(uint256 limit, uint256 amount);
error ExceedOutboundLimit(uint256 limit, uint256 amount);
error SendAndCallBlocked();
error ZeroValue();
error ExceedTotalGlobalSupply();
error NotOperator();
//--------------------------- Constructor ---------------------------
// OAppCore inherits from Ownable
/**
* @dev decimals of token on the local chain is fixed to 18 (https://github.com/x/x/blob/main/src/x.sol#L36)
* @param owner contract owner
* @param lzEndpoint LayerZero endpoint address on the local chain
* @param delegate The address capable of making OApp configurations inside of the endpoint.
*/
constructor(address owner, address lzEndpoint, address delegate) NativeOFTAdapter(18, lzEndpoint, delegate) Ownable(owner) {
}
//------------------------------------------- RATE LIMITS --------------------------------------------------------------
/**
* @dev Owner to set the max daily limit on outbound x-chain transfers
* @param eid Destination eid, as per LayerZero
* @param limit Daily outbound limit
*/
function setOutboundLimit(uint32 eid, uint256 limit) external onlyOwner {
outboundLimits[eid] = limit;
emit SetOutboundLimit(eid, limit);
}
/**
* @dev Owner to set the max daily limit on inbound x-chain transfers
* @param eid Destination eid, as per LayerZero
* @param limit Daily inbound limit
*/
function setInboundLimit(uint32 eid, uint256 limit) external onlyOwner {
inboundLimits[eid] = limit;
emit SetInboundLimit(eid, limit);
}
/**
* @dev Owner to set whitelisted addresses - limits do not apply to these addresses
* @param addr address
* @param isWhitelisted true/false
*/
function setWhitelist(address addr, bool isWhitelisted) external onlyOwner {
whitelist[addr] = isWhitelisted;
emit SetWhitelist(addr, isWhitelisted);
}
/**
* @notice Resets the accrued received amount for specified chain
* @param eid The endpoint ID.
* @dev Only owner of the OApp can call this function.
*/
function resetReceivedTokenAmount(uint32 eid) external onlyOwner {
delete receivedTokenAmounts[eid];
emit ReceivedTokenAmountReset(eid);
}
/**
* @notice Resets the accrued sent amount for specified chain
* @param eid The endpoint ID.
* @dev Only owner of the OApp can call this function.
*/
function resetSentTokenAmount(uint32 eid) external onlyOwner {
delete sentTokenAmounts[eid];
emit SentTokenAmountReset(eid);
}
//------------------------------------------- OPERATORS --------------------------------------------------------------
/**
* @dev Owner to set operator addresses - these addresses can call setPeers
* @param addr address
* @param isOperator true/false
*/
function setOperator(address addr, bool isOperator) external onlyOwner {
operators[addr] = isOperator;
emit SetOperator(addr, isOperator);
}
/**
* @notice Resets the peer address (OApp instance) for a corresponding endpoint.
* @param eid The endpoint ID.
* @dev Only an operator or owner of the OApp can call this function.
*/
function resetPeer(uint32 eid) external {
require(operators[msg.sender] || msg.sender == owner(), NotOperator());
peers[eid] = bytes32(0);
emit PeerSet(eid, bytes32(0));
}
//------------------------------------------- LZ Override --------------------------------------------------------------
/**
* @dev Overwrite _debit to implement rate limits.
* @dev Native tokens are not burned, but locked in the contract instead
* @param from The address to debit from.
* @param amountLD The amount of tokens to send in local decimals.
* @param minAmountLD The minimum amount to send in local decimals.
* @param dstEid The destination chain ID.
* @return amountSentLD The amount sent in local decimals.
* @return amountReceivedLD The amount received in local decimals on the remote.
*/
function _debit(address from, uint256 amountLD, uint256 minAmountLD, uint32 dstEid) internal override returns (uint256, uint256) {
// Call parent logic (removes dust, checks fees, slippage, etc)
(uint256 amountSentLD, uint256 amountReceivedLD) = super._debit(from, amountLD, minAmountLD, dstEid);
// whitelisted addresses have no limits: proceed to send tokens
if (whitelist[from]) return (amountSentLD, amountReceivedLD);
// calculate sent token amount for this epoch
uint256 sentTokenAmountsInThisEpoch;
uint256 lastSentTimestamp = lastSentTimestamps[dstEid];
uint256 currTimestamp = block.timestamp;
// Round down timestamps to the nearest day.
if ((currTimestamp / (1 days)) > (lastSentTimestamp / (1 days))) {
sentTokenAmountsInThisEpoch = amountSentLD;
lastSentTimestamps[dstEid] = currTimestamp;
} else {
// sentTokenAmountsInThisEpoch = recentSentAmount + incomingSendAmount
sentTokenAmountsInThisEpoch = sentTokenAmounts[dstEid] + amountSentLD;
}
// check against outboundLimit
uint256 outboundLimit = outboundLimits[dstEid];
if (sentTokenAmountsInThisEpoch > outboundLimit) revert ExceedOutboundLimit(outboundLimit, sentTokenAmountsInThisEpoch);
// update storage
sentTokenAmounts[dstEid] = sentTokenAmountsInThisEpoch;
return (amountSentLD, amountReceivedLD);
}
/**
* @dev Credits tokens to the specified address.
* @param to The address to credit the tokens to.
* @param amountLD The amount of tokens to credit in local decimals.
* @param srcEid The source chain ID.
* @return amountReceivedLD The amount of tokens ACTUALLY received in local decimals.
*/
function _credit(address to, uint256 amountLD, uint32 srcEid) internal override returns (uint256) {
uint256 amountReceivedLD = super._credit(to, amountLD, srcEid);
// whiteslisted address have no limits
if (whitelist[to]) return amountReceivedLD;
uint256 receivedTokenAmountInThisEpoch;
uint256 lastReceivedTimestamp = lastReceivedTimestamps[srcEid];
uint256 currTimestamp = block.timestamp;
// Round down timestamps to the nearest day.
if ((currTimestamp / (1 days)) > (lastReceivedTimestamp / (1 days))) {
receivedTokenAmountInThisEpoch = amountReceivedLD;
lastReceivedTimestamps[srcEid] = currTimestamp;
} else {
receivedTokenAmountInThisEpoch = receivedTokenAmounts[srcEid] + amountReceivedLD;
}
// ensure limit not exceeded
uint256 inboundLimit = inboundLimits[srcEid];
if (receivedTokenAmountInThisEpoch > inboundLimit) revert ExceedInboundLimit(inboundLimit, receivedTokenAmountInThisEpoch);
// update storage
receivedTokenAmounts[srcEid] = receivedTokenAmountInThisEpoch;
return amountReceivedLD;
}
/** Note: Override to block composed messages
* @dev Executes the send operation.
* @param _sendParam The parameters for the send operation.
* @param _fee The calculated fee for the send() operation.
* - nativeFee: The native fee.
* - lzTokenFee: The lzToken fee.
* @param _refundAddress The address to receive any excess funds.
* @return msgReceipt The receipt for the send operation.
* @return oftReceipt The OFT receipt information.
*
* @dev MessagingReceipt: LayerZero msg receipt
* - guid: The unique identifier for the sent message.
* - nonce: The nonce of the sent message.
* - fee: The LayerZero fee incurred for the message.
*/
function _send(SendParam calldata _sendParam, MessagingFee calldata _fee, address _refundAddress) internal override returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {
// @dev Applies the token transfers regarding this send() operation.
// - amountSentLD is the amount in local decimals that was ACTUALLY sent from the sender.
// - amountReceivedLD is the amount in local decimals that will be credited to the recipient on the remote OFT instance.
(uint256 amountSentLD, uint256 amountReceivedLD) = _debit(
msg.sender,
_sendParam.amountLD,
_sendParam.minAmountLD,
_sendParam.dstEid
);
// @dev Builds the options and OFT message to quote in the endpoint
(bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);
// note: Block composed messages - custom logic
if(isComposed(message)) revert SendAndCallBlocked();
// @dev Sends the message to the LayerZero endpoint and returns the LayerZero msg receipt.
msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress);
// @dev Formulate the OFT receipt.
oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);
emit OFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, amountSentLD, amountReceivedLD);
}
/**
* @dev Checks if the OFT message is composed. Copied from OFTMsgCodec.sol
* @param _msg The OFT message.
* @return A boolean indicating whether the message is composed.
*/
function isComposed(bytes memory _msg) internal pure returns (bool) {
// uint8 private constant SEND_AMOUNT_SD_OFFSET = 40;
// return _msg.length > SEND_AMOUNT_SD_OFFSET
return _msg.length > 40;
}
//------------------------------------------- OWNABLE2STEP --------------------------------------------------------------
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public override(Ownable, Ownable2Step) onlyOwner {
Ownable2Step.transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal override(Ownable, Ownable2Step) {
Ownable2Step._transferOwnership(newOwner);
}
//--------------------------- Special Function ---------------------------
/**
* @notice For initial setup of the bridge.
* @dev Only owner can lock native tokens; from the owner's balance
* Reverts if the total locked tokens exceed the total global supply
* Expects msg.value to be greater than 0.
*/
function lockTokens() external payable onlyOwner {
require(msg.value > 0, ZeroValue());
require(lockedTokens + msg.value <= TOTAL_GLOBAL_SUPPLY, ExceedTotalGlobalSupply());
lockedTokens += msg.value;
emit TokensLocked(msg.value, lockedTokens);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment