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;
}
}
}
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));
}
}
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);
}
}
¡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)
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
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.
Gracias, lo voy a ver
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.
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
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
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
Hola, una pregunta para el caso de crear 3 fee
Programa de ayuda apersonas con cancer 1%
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.
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
Hola Ahmed, adquirí el token para unirme al canal de discord pero no encuentro el canal, puedes enviarme el link?
Con mucho gusto discord.gg/Y62dNx4k
y en caso contrario como se podria hacer para poder mandar a como 4 wallets distintas que cobren fees. sin recolector?
¿A que te refieres sin recolector?