Fees de transacción ¿Cómo se agregan?

Ahmed Castro - Apr 5 '22 - - Dev Community

Una manera de mantener con oxígeno a nuestros tokens o ecosistemas es mediante los fees por transacción, o sea cobrar una comisión cada vez que se mueven fondos. Esta comisión puede ir a una billetera de los creadores o a una bóveda. Solidity y las librerías de OpenZeppelin nos dan todas las herramientas necesarias para hacerlo. En este video exploramos diferentes formas de lograrlo, para ambos principiantes y avanzados.

Dependencias

Para este tutorial ocuparás Metamask con fondos de Rinkeby Testnet que puedes conseguir desde el Faucet. También necesitarás conectar tu wallet a Polygon Mainnet y conseguir MATIC desde algún exchange.

1. Fee fijo por transacción simple

Los contratos ERC-20 de OpenZeppelin exponen la función _afterTokenTransfer que nos hacen muy conveniente implementar tax fees.

// SPDX-License-Identifier: MIT

pragma solidity 0.8.12;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MyToken is ERC20
{    
    address public vault_address;
    uint public fee;

    bool private is_in_fee_transfer;

    constructor () ERC20("My Token", "TKN")
    {
        // Edit here
        vault_address = 0xb6F5414bAb8d5ad8F33E37591C02f7284E974FcB;
        fee = 1 ether;
        _mint(msg.sender, 1_000_000 ether);
        // End edit
    }

    function _afterTokenTransfer(
        address from,
        address to,
        uint amount
    ) internal virtual override(ERC20)
    {
        require(amount >= fee, "Amount must be greater than fee");
        super._afterTokenTransfer(from, to, amount);

        if(!is_in_fee_transfer)
        {
            is_in_fee_transfer = true;
            _transfer(to, vault_address, fee);
            is_in_fee_transfer = false;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Método avanzado de capturas de fees

  • Con ayuda del Router de Uniswap podemos capturar un porcentaje diferente de fees en caso de Buy, Sell y P2P: Compra, Venta y transacción normal a otra billetera
  • Esta vez capturamos un porcentaje de la tansacción, no un valor fijo
  • Además agragamos addresses operadores como Taxless, exemptos de fees

Recuerda ajustar la dirección del router dependiendo de tu network:

  • Polygon Quickswap: 0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff
  • Ethereum Uniswap V2: 0x10ED43C718714eb63d5aA57B78B54704E256024E
  • BSC Mainnet Pancake: 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
  • BSC Testnet Pancake: 0xD99D1c33F9fC3444f8101754aBC46c52416550D1
// SPDX-License-Identifier: MIT

pragma solidity 0.8.12;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

interface IUniswapV2Factory {
    function createPair(address tokenA, address tokenB) external returns (address pair);
}

interface IUniswapV2Router02 {
    function factory() external pure returns (address);
    function WETH() external pure returns (address);
}

contract MyToken is ERC20
{    
    address public vault_address;

    address public pair;

    uint public fee_decimal = 2;
    enum FeesIndex{ BUY, SELL, P2P }
    uint[] public fee_percentages;

    mapping(address => bool) public is_taxless;

    bool private is_in_fee_transfer;

    constructor () ERC20("My Token", "TKN")
    {
        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff);
        pair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(address(this), _uniswapV2Router.WETH());

        // Edit here
        vault_address = 0xb6F5414bAb8d5ad8F33E37591C02f7284E974FcB;

        fee_percentages.push(1000); // Buy  fee is 10.00%
        fee_percentages.push(1500); // Sell fee is 15.00%
        fee_percentages.push(500);  // Buy  fee is  5.00%
        // End edit

        is_taxless[msg.sender] = true;
        is_taxless[vault_address] = true;
        is_taxless[address(this)] = true;
        is_taxless[address(0)] = true;

        _mint(msg.sender, 1_000_000 ether);
    }

    function _afterTokenTransfer(
        address from,
        address to,
        uint amount
    ) internal virtual override(ERC20)
    {
        super._afterTokenTransfer(from, to, amount);

        if(!is_in_fee_transfer)
        {
            uint fees_collected;
            if (!is_taxless[from] && !is_taxless[to]) {
                bool sell = to == pair;
                bool p2p = from != pair && to != pair;

                uint fee = calculateFee(p2p ? FeesIndex.P2P : sell ? FeesIndex.SELL : FeesIndex.BUY, amount);

                fees_collected += fee;
            }

            if(fees_collected > 0)
            {
                is_in_fee_transfer = true;
                _transfer(to, vault_address, fees_collected);
                is_in_fee_transfer = false;
            }
        }
    }

    function calculateFee(FeesIndex fee_index, uint amount) internal view returns(uint) {
        return (amount * fee_percentages[uint(fee_index)])  / (10**(fee_decimal + 2));
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Método avanzado más completo

  • Funciones editables onlyOwner
  • Split de fees a diferentes billeteras
// SPDX-License-Identifier: MIT

pragma solidity 0.8.12;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

interface IUniswapV2Factory {
    function createPair(address tokenA, address tokenB) external returns (address pair);
}

interface IUniswapV2Router02 {
    function factory() external pure returns (address);
    function WETH() external pure returns (address);
}

contract MyToken is ERC20, Ownable
{
    uint internal wallet_a_collected;
    uint internal wallet_b_collected;

    address public wallet_a;
    address public wallet_b;

    address public pair;

    enum FeesIndex{ BUY, SELL, P2P }
    uint[] public wallet_a_fee_percentages;
    uint[] public wallet_b_fee_percentages;
    uint public fee_decimal = 2;

    mapping(address => bool) public is_taxless;

    bool private is_in_fee_transfer;

    constructor () ERC20("My Token", "TKN")
    {
        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff);
        pair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(address(this), _uniswapV2Router.WETH());

        // Edit here
        wallet_a = 0xb6F5414bAb8d5ad8F33E37591C02f7284E974FcB;
        wallet_a = 0xb6F5414bAb8d5ad8F33E37591C02f7284E974FcB;

        wallet_a_fee_percentages.push(500); // Buy  fee is 5.00%
        wallet_a_fee_percentages.push(500); // Sell fee is 5.00%
        wallet_a_fee_percentages.push(500);  // Buy  fee is  5.00%

        wallet_b_fee_percentages.push(1000); // Buy  fee is 10.00%
        wallet_b_fee_percentages.push(1500); // Sell fee is 15.00%
        wallet_b_fee_percentages.push(500);  // Buy  fee is  5.00%
        // End edit

        is_taxless[msg.sender] = true;
        is_taxless[wallet_a] = true;
        is_taxless[wallet_b] = true;
        is_taxless[address(this)] = true;
        is_taxless[address(0)] = true;

        _mint(msg.sender, 1_000_000 ether);
    }

    function _afterTokenTransfer(
        address from,
        address to,
        uint amount
    ) internal virtual override(ERC20)
    {
        super._afterTokenTransfer(from, to, amount);

        if(!is_in_fee_transfer)
        {
            uint fees_collected;
            if (!is_taxless[from] && !is_taxless[to]) {
                uint wallet_a_fee;
                uint wallet_b_fee;

                bool sell = to == pair;
                bool p2p = from != pair && to != pair;
                (wallet_a_fee, wallet_b_fee) = calculateFee(p2p ? FeesIndex.P2P : sell ? FeesIndex.SELL : FeesIndex.BUY, amount);

                wallet_a_collected += wallet_a_fee;
                wallet_b_collected += wallet_b_fee;
                fees_collected += wallet_a_fee + wallet_b_fee;
            }

            if(fees_collected > 0)
            {
                is_in_fee_transfer = true;
                _transfer(to, address(this), fees_collected);
                is_in_fee_transfer = false;
            }
        }
    }

    function calculateFee(FeesIndex fee_index, uint amount) internal view returns(uint, uint) {
        uint wallet_a_fee = (amount * wallet_a_fee_percentages[uint(fee_index)])  / (10**(fee_decimal + 2));
        uint wallet_b_fee = (amount * wallet_b_fee_percentages[uint(fee_index)])  / (10**(fee_decimal + 2));
        return (wallet_a_fee, wallet_b_fee);
    }

    // Owner admin functions

    function setWalletA(address wallet)  external onlyOwner {
        wallet_a = wallet;
    }

    function setWalletB(address wallet)  external onlyOwner {
        wallet_b = wallet;
    }

    function setWalletAFee(uint buy, uint sell, uint p2p) external onlyOwner {
        wallet_a_fee_percentages[0] = buy;
        wallet_a_fee_percentages[1] = sell;
        wallet_a_fee_percentages[2] = p2p;
    }

    function setWalletBFee(uint buy, uint sell, uint p2p) external onlyOwner {
        wallet_b_fee_percentages[0] = buy;
        wallet_b_fee_percentages[1] = sell;
        wallet_b_fee_percentages[2] = p2p;
    }

    function setIsTaxless(address _address, bool value) external onlyOwner {
        is_taxless[_address] = value;
    }

    // Fee collector functions

    function collectWalletAFee() external {
        require(msg.sender == wallet_a, "Sender must be buy address");
        wallet_a_collected = 0;
        transfer(wallet_a, wallet_a_collected);
    }

    function collectWalletBFee() external {
        require(msg.sender == wallet_b, "Sender must be buy address");
        wallet_b_collected = 0;
        transfer(wallet_b, wallet_b_collected);
    }
}
Enter fullscreen mode Exit fullscreen mode

¡Gracias por ver este tutorial!

Sígueme en dev.to y en Youtube para todo lo relacionado al desarrollo en Blockchain en Español.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .