DEV Community

Cover image for Fees de transacción ¿Cómo se agregan?
Ahmed Castro
Ahmed Castro

Posted on • Updated on

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

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.

Top comments (13)

Collapse
 
csxrobert profile image
CSXRobert

Hola, estoy tratando de agregar la función cambiar de vault_address en el segundo caso y no logro hacerlo.
También quería saber como recuperar los tokens cuando han enviados a la billetera del contrato original.
Saludos

Collapse
 
turupawn profile image
Ahmed Castro

Los tokens deberían de ir al bold address, no quedan en el contrato, pero te recomiendo que mires el video que viene donde explico una versión mejorada de todo esto.

Collapse
 
csxrobert profile image
CSXRobert

Gracias, lo voy a ver

Collapse
 
cossst profile image
Cossst

Saludos, utilice modo avanzado completo, le agregue mas de dos wallets para distribuir los fee, los cobra excelente, se acumulan perfectamente en el contrato pero no logro que se distribuyan. (todo en la test net de bsc). Cuando doy click a la funcion colect en remix me arroja un error :

(Gas estimation errored with the following message (see below). The transaction execution will likely fail. Do you want to force sending?
Internal JSON-RPC error).

Este mismo error lo da si intento correrlo en la mainnet de la bsc.

Collapse
 
turupawn profile image
Ahmed Castro

Seguro que las wallets tienen asignado un address que no sea el address 0, de todas maneras te recomiendo ver este video, que funciona mejor: https://www.youtube.com/watch?v=FPto5N14bVA&t=783s

Collapse
 
beboobs profile image
Beboobs

Hola Ahmed, estoy aprendiendo una barbaridad con tus videos y posts. Muchisimas gracias!

Una pregunta, cuando le doy a recolectar las fee (los token guardados en el contrato) desde la wallet a, por ejemplo, no me envia ningun token. He visto que la variable walle_a_collected tiene valor 0, para que enviara los token entiendo que deberia tener un valor. Que deberia poner ahi? Muchas gracias

Collapse
 
turupawn profile image
Ahmed Castro

Hola! Este video está un poco desfazado, te recomiendo mejor ver el token que hice en este otro:
https://www.youtube.com/watch?v=FPto5N14bVA&t=131s

Collapse
 
casolucion profile image
casolucion • Edited

Hola, una pregunta para el caso de crear 3 fee

  1. Programa de ayuda apersonas con cancer 1%

  2. Marketing 1%

3 Liquidity 1%

Para Binance smart chain tiene un ejemplo?
porque en este caso veo es que se conecta al router de Uniswap
y este token se creará pero para lanzarlo en una plataforma de Presales.
Gracias por su ayuda.

Collapse
 
turupawn profile image
Ahmed Castro

Tengo este ejemplo que tiene autoliquidez y autoswap: github.com/Turupawn/ERC20AutoLP/bl...
Este también tiene otras cosas como max wallet y max transaction, blacklist y otras restricciones pero te puede ayudar. En todo caso si queres agarrar un código que esta funcionando en mainnet si queres convertirlo a bsc tenes que cambiar el address del router, por ejemplo este es el address de uniswap: 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D, lo remplazas por este otro, que es el de pancake: 0x10ED43C718714eb63d5aA57B78B54704E256024E

Collapse
 
csxrobert profile image
CSXRobert

Hola Ahmed, adquirí el token para unirme al canal de discord pero no encuentro el canal, puedes enviarme el link?

Collapse
 
turupawn profile image
Ahmed Castro

Con mucho gusto discord.gg/Y62dNx4k

Collapse
 
draco profile image
john carlos

y en caso contrario como se podria hacer para poder mandar a como 4 wallets distintas que cobren fees. sin recolector?

Collapse
 
turupawn profile image
Ahmed Castro

¿A que te refieres sin recolector?