• Developer Docs

A quick read on how to Utilize the Swap 2.0 as a developer looking to launch tokens on our Dex.

DETA: Swap 2.0 Contract Upgrade Intro

Traditional Automated Market Makers (AMMs) and Exchanges typically sell project tokens to gather fees and taxes for each transaction. Consequently, every swap generates negative selling pressure, which gradually hampers the project's progress. DETA, the only exchange compatible with Swap 2.0, and Swap 2.0 smart contracts address this significant drawback by collecting taxes and fees in the base coin or token of the trading pairs, like BNB, ETH, BUSD, or USDC. This approach reduces unnecessary selling during each transaction, directly supporting genuine markets and ensuring the long-term success of projects. There are two methods to incorporate Swap 2.0 capability into your token smart contract:

  1. Utilize DETA Lab's Token Creator feature to swiftly and effortlessly generate a Swap 2.0 compatible token contract - no programming skills needed. Choose from a list of pre-defined contracts with optional tokenomics, such as automatic buybacks and liquidity contributions, which help mitigate selling pressure.

  2. Manually include the following essential logic in your token contract:

Pair + Router Setup

  1. Begin by importing the DETA:SwapRouter and DETA:SwapFactory from:

import "@DETA-Library/../../IDETASwapFactory.sol"; (replace with actual public repository)
import "@DETA-Library/../../IDETASwapRouter.sol"; (replace with actual public repository)
  1. Now, instantiate the DETA:SwapRouter and DETA:SwapPair address in your contract as such:

IDETASwapRouter public detaSwapRouter;
address public detaSwapPair;
  1. In the constructor function for your contract, pass the DETA:SwapRouter address and cast the address as an interface. Additionally, deploy your new trading pair:

detaSwapRouter = IDETASwapRouter(_router);
WBNB = detaSwapRouter.WETH();
detaSwapPair = IDETASwapFactory(detaSwapRouter.factory())
.createPair(address(this), WBNB, true, address(this));

tokens[pairsLength] = WBNB;
pairs[pairsLength] = detaSwapPair;
pairsLength += 1;
_isPairAddress[detaSwapPair] = true;
  1. If you plan on adding your token pair to an exchange other than DETA, you can do so by adding the following functions to your contract:

function addOutsideSwapPair(address account) public onlyOwner {
     _includeSwapFee[account] = true;
}

function removeOutsideSwapPair(address account) public onlyOwner {
     _includeSwapFee[account] = false;
}

These functions take a pair contract address "account" as a parameter and includes or excludes them from swap fees accordingly. Also include the following functions to add token pairs to the pair list:

// adds a token pair to the factory pair list.
function addPair(address _pair, address _token) public {
    address factory = detaSwapRouter.factory();
    require(msg.sender == factory || msg.sender == address(detaSwapRouter) || msg.sender == address(this), "DETA: NOT_ALLOWED");
    if (!_checkPairRegistered(_pair)) {
        _isExcludedFromFee[_pair] = true;
        _isPairAddress[_pair] = true;
        pairs[pairsLength] = _pair;
        tokens[pairsLength] = _token;
        pairsLength += 1;
    }
}

// internal function to check if a pair address is already registered.
function _checkPairRegistered(address _pair) internal view returns (bool) {
    bool isPair = false;
    for (uint256 i = 0; i < pairsLength; i++) {
        if (pairs[i] == _pair) isPair = true;
    }
    return isPair;
}

// internal function to return the index of a pair address
function _getTokenIndex(address _token) internal view returns (uint256) {
    uint256 index = pairsLength + 1;
    for (uint256 i = 0; i < pairsLength; i++) {
        if (tokens[i] == _token) index = i;
    }
    return index;
}

// Crucially, you should add this function to update the DETA:SwapRouter address should a new version come out. This function also affords you the ability to update your token's pair contract:

function updateRouterAndPair(address _router, address _pair) public onlyOwner {
    _isExcludedFromFee[detaSwapPair] = false;
    detaSwapRouter = IDETASwapRouter(_router);
    detaSwapPair = _pair;
    WBNB = detaSwapRouter.WETH();
    _isExcludedFromFee[detaSwapPair] = true;
    _isPairAddress[detaSwapPair] = true;
    pairs[0] = detaSwapPair;
    tokens[0] = WBNB;
}

// Lastly, add the onlyExchange modifier to restrict the set of addresses which can execute certain swap 2.0 functions:

modifier onlyExchange() {
    bool isPair = false;
    for (uint i = 0; i < pairsLength; i++) {
        if (pairs[i] == msg.sender) isPair = true;
    }
    require(msg.sender == address(detaSwapRouter) || isPair, "DETA: NOT_ALLOWED");
    _;
}

Fee Structure Setup

  1. Begin your fee structuring by declaring two structs: Fees and FeeValues. The Fees struct contains the numeric fee value corresponding to a particular fee. For example, a 2% marketing fee would be entered as "200". The FeeValues struct contains the token amounts corresponding to the fees for a particular transaction.

The Fees struct should include the fee value and the associated address where that fee is collected. For example, a token contract with one fee would be declared as follows:

struct Fees {         
    uint256 wallet1Fee;         
    address wallet1Address;     
}

You can add as many fees as you'd like, making sure to instantiate the fee and its collection address in the Fees struct. A FeeValues struct for the above example would look like:

struct FeeValues {         
    uint256 transferAmount;         
    uint256 wallet1;     
}
  1. Create structs for each fee class as follows:

Fees private _defaultFees;
Fees public _buyFees;
Fees private _previousFees;
Fees private _emptyFees;
Fees public _sellFees;
Fees private _outsideBuyFees;
Fees private _outsideSellFees;
  1. Include a mapping to set certain addresses (e.g., routers, wallets) as excluded from fees.

mapping (address => bool) private _isExcludedFromFee;
  1. In the constructor function of your contract, pass the fee values for buys and sells (e.g., 200 for 2%) and associated collection addresses as deployment parameters, along with any other necessary values. These structs contain the values passed for the fee amounts and wallets in the constructor.

// Edit the constructor in order to declare default fees on deployment
constructor( existingParams..., address _wallet1, uint256 _wallet1FeeBuy, uint256 _wallet1FeeSell) {
    // ...
    // existing contructor code
    // ...
    
    _defaultFees = Fees(
        _wallet1FeeBuy,
        _wallet1
    );

    _buyFees = Fees(
        _wallet1FeeBuy,
        _wallet1
    );

    _sellFees = Fees(
        _wallet1FeeSell,
        _wallet1
    );

    _outsideBuyFees = Fees(
        _wallet1FeeBuy,
        _wallet1
    );

    _outsideSellFees = Fees(
        _wallet1FeeSell,
        _wallet1
    );
}
  1. Include functions in the body of your contract to adjust the various buy and sell fees as necessary. Also, add two functions to set the _isExcludedFromFee mapping mentioned above. SetBuyFees and setSellFees are used to manually change the associated fees in your contract, giving you the flexibility to control how fees are collected based on the nature of the transaction. Use the setWalletAddress functions to change the collection location for the wallet receiving the fees.

function excludeFromFee(address account) public onlyOwner {
    _isExcludedFromFee[account] = true;
}

function includeInFee(address account) public onlyOwner {
    _isExcludedFromFee[account] = false;
}

// Functions to update fees and addresses

// set fee values on buys
function setBuyFees(uint256 _wallet1Fee) external onlyOwner {
    _defaultFees.wallet1Fee = _wallet1Fee;
    _buyFees.wallet1Fee = _wallet1Fee;
    _outsideBuyFees.wallet1Fee = _wallet1Fee;
}

// set fee values on sells
function setSellFees(uint256 _wallet1Fee) external onlyOwner {
    _defaultFees.wallet1Fee = _wallet1Fee;
    _sellFees.wallet1Fee = _wallet1Fee;
    _outsideSellFees.wallet1Fee = _wallet1Fee;
}

function setWallet1Address(address _wallet1) external onlyOwner {
    require(_wallet1 != address(0), "DETA: Address Zero is not allowed");
    _defaultFees.wallet1Address = _wallet1;
    _buyFees.wallet1Address = _wallet1;
    _sellFees.wallet1Address = _wallet1;
    _outsideBuyFees.wallet1Address = _wallet1;
    _outsideSellFees.wallet1Address = _wallet1;
}
  1. If you want to list your token on an additional exchange, such as PancakeSwap, include the following function to add outside pair contracts:

function addOutsideSwapPair(address account) public onlyOwner {
    _includeSwapFee[account] = true;
}

function removeOutsideSwapPair(address account) public onlyOwner {
    _includeSwapFee[account] = false;
}   
  1. Include the following private helper functions; these are required for internal calls within the contract to correctly calculate fees. _getValues and _calculateFee are internal helper functions to fetch fees for calculations.

function _getValues(uint256 tAmount) private view returns (FeeValues memory) {
    FeeValues memory values = FeeValues(
        0,
        calculateFee(tAmount, _defaultFees.marketingFee),
        calculateFee(tAmount, _defaultFees.developmentFee)
    );

    values.transferAmount = tAmount.sub(values.marketing).sub(values.development);
    return values;
}

function calculateFee(uint256 _amount, uint256 _fee) private pure returns (uint256) {
    if(_fee == 0) return 0;
    return _amount.mul(_fee).div(
        10**4
    );
}

function removeAllFee() private {
    _previousFees = _defaultFees;
    _defaultFees = _emptyFees;
}

function setSellFee() private {
    _defaultFees = _sellFees;
}

function setOutsideBuyFee() private {
    _previousFees = _defaultFees;
    _defaultFees = _outsideBuyFees;
}

function setOutsideSellFee() private {
    _previousFees = _defaultFees;
    _defaultFees = _outsideSellFees;
}

function restoreAllFee() private {
    _defaultFees = _previousFees;
}

function getTotalFee(address account) public view returns (uint256) {
    if(_isExcludedFromFee[account]) {
        return 0;
    } else {
    return _defaultFees.marketingFee
        .add(_defaultFees.developmentFee);
    }
}

function getFee() public view returns (uint256) {
    return _defaultFees.marketingFee
        .add(_defaultFees.developmentFee);
}

function _takeFees(FeeValues memory values) private {
    _takeFee(values.marketing, _defaultFees.marketingAddress);
    _takeFee(values.development, _defaultFees.developmentAddress);
}

function _takeFee(uint256 tAmount, address recipient) private {
    if(recipient == address(0)) return;
    if(tAmount == 0) return;

    _balances[address(this)] = _balances[address(this)].add(tAmount);
}
  1. In your token's transfer function, include the following logic to ensure fees are taken appropriately depending on the transaction details:

// Indicates if fee should be deducted from token transfer
uint8 takeFee = 0;
if (_isPairAddress[to] && from != address(detaSwapRouter) && !_isExcludedFromFee[from]) {
    takeFee = 1;
} else if (_includeSwapFee[from]) {
    takeFee = 2;
} else if (_includeSwapFee[to]) {
    takeFee = 3;
}

// Transfer the amount, applying the appropriate tax
_tokenTransfer(from, to, amount, takeFee);

9. Create a new function in your contract called _tokenTransfer:

FeeValues memory _values = _getValues(amount);
_balances[sender] = _balances[sender].sub(amount, "Insufficient Balance");
_balances[recipient] = _balances[recipient].add(_values.transferAmount);
_takeFees(_values);

emit Transfer(sender, recipient, _values.transferAmount);

if (takeFee == 0) {
    restoreAllFee();
} else if (takeFee == 1) {
    setSellFee();
} else if (takeFee == 2 || takeFee == 3) {
    restoreAllFee();
    emit Transfer(sender, _defaultFees.developmentAddress, _values.development);
    emit Transfer(sender, _defaultFees.marketingAddress, _values.marketing);
}
  1. To actually transfer your fees to the contract and distribute them, include the handleFee() function:

// This function transfers the fees to the correct addresses. 
function handleFee(uint256 amount, address token) public onlyExchange {
    uint256 tokenIndex = _getTokenIndex(token);
    if(tokenIndex < pairsLength) {
        uint256 allowanceT = IERC20(token).allowance(msg.sender, address(this));
        if(allowanceT >= amount) {
            IERC20(token).transferFrom(msg.sender, address(this), amount);

            // All fees to be declared here in order to be calculated and sent
            uint256 totalFee = getFee();
            uint256 wallet1FeeAmount = amount.mul(_defaultFees.wallet1Fee).div(totalFee);

            IERC20(token).transfer(_defaultFees.wallet1Address, wallet1FeeAmount);

            restoreAllFee();
        }
    }
}

Last updated