Lanzamiento de Tokens: Nivel Dios
Tags: web3, tokens, defi
Published: 2022-08-18 16:15:20

---
title: Lanzamiento de Tokens: Nivel Dios
published: true
description: 
tags: web3, tokens, defi
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4fq5er8g4vn5j5n4u96i.png
---

{% youtube FPto5N14bVA %}

El ecosistema web3 sigue creciendo así como las herramientas que usamos para crear y lanzar smart contracts. En este video exploramos las herramientas nuevas que he estado usando para lanzar proyectos tokens y cualquier tipo de smart contracts.

## Antes de comenzar

Para este tutorial ocuparás [NodeJs](https://nodejs.org/es/) que recomiendo descargarlo en Linux via [NVM](https://github.com/nvm-sh/nvm#installing-and-updating), y también necesitarás [Metamask](https://metamask.io/) u otra wallet compatible con fondos en Goerli que puedes obetener desde un [faucet](https://goerlifaucet.com/).

## 1. Creamos un proyecto de Hardhat

```bash
mkdir MyToken
cd MyToken
npm install --save hardhat
npx hardhat
```

## 2. Instalamos librerías adicionales

Instalamos `dotenv` para proteger nuestra llave privada, `@nomiclabs/hardhat-etherscan` para auto verificar en etherscan y `@openzeppelin/contracts` para hacer uso de los contratos de OpenZeppelin.

```bash
npm install --save dotenv @nomiclabs/hardhat-etherscan @openzeppelin/contracts
```

## 3. Configuramos Hardhat

Configuramos Hardhat para poder usar la versión ahora mas reciente de Solidity `0.8.16`, también para habilitar el forkeo de mainnet y el autoverificador de contratos.

`hardhat.config.js`
```js
require("@nomicfoundation/hardhat-toolbox");
require("@nomiclabs/hardhat-etherscan");
require('dotenv').config()

module.exports = {
  solidity: "0.8.16",
  networks: {
    hardhat: {
      forking: {
        url: process.env.MAINNET_RPC_URL,
      },
    },
    goerli: {
      url: process.env.GOERLI_RPC_URL,
      accounts: [process.env.GOERLI_PRIVATE_KEY],
    }
  },
  etherscan: {
    apiKey: process.env.ETHERSCAN_API_KEY
  }
};
```

También colocamos nuestras variables ocultas en el archivo `.env`. Las `RPC_URL` las conseguimos en [infura](https://infura.io/) o [alchemy](https://www.alchemy.com/), la llave privada en metamask y la API Key desde [etherscan](https://etherscan.io/).

`.env`
```bash
MAINNET_RPC_URL=https://mainnet.infura.io/v3/TULLAVE
GOERLI_RPC_URL=https://goerli.infura.io/v3/TULLAVE
GOERLI_PRIVATE_KEY=TULLAVE
ETHERSCAN_API_KEY=TULLAVE
```

## 4. Compilamos nuestro token

Borramos el archivo de ejemplo que viene por defecto `contracts/Lock.sol` y colocamos nuestro contrato de token en la misma carpeta.

`contracts/MyToken.sol`
```solidity
// SPDX-License-Identifier: MIT

pragma solidity 0.8.16;

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);

    function addLiquidityETH(
        address token,
        uint256 amountTokenDesired,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) external payable returns (uint256 amountToken, uint256 amountETH, uint256 liquidity);

    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;

    function swapETHForExactTokens(
        uint amountOut,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable returns (uint[] memory amounts);
}

contract MyToken is Context, IERC20, IERC20Metadata {
    // Openzeppelin variables
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    // My variables

    address public vaultAddress;

    address public pair;

    uint public feeDecimal = 2;
    enum FeesIndex{ BUY, SELL, P2P }
    uint[] public feePercentages;

    mapping(address => bool) public is_taxless;

    bool private isInFeeTransfer;

    constructor(address swapRouter, address _vaultAddress)
    {
        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(swapRouter);
        pair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(address(this), _uniswapV2Router.WETH());

        // Edit here
        _name = "My Token";
        _symbol = "TKN";
        vaultAddress = _vaultAddress;

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

        is_taxless[msg.sender] = true;
        is_taxless[vaultAddress] = true;
        is_taxless[address(this)] = true;
        is_taxless[address(0)] = true;
        _mint(msg.sender, 1_000_000 ether);
    }

    function name() public view virtual override returns (string memory) {
        return _name;
    }

    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, _allowances[owner][spender] + addedValue);
        return true;
    }

    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = _allowances[owner][spender];
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        // My implementation
        uint feesCollected;
        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);

            feesCollected += fee;
        }

        amount -= feesCollected;
        _balances[from] -= feesCollected;
        _balances[vaultAddress] += feesCollected;
        // End my implementation

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
        }
        _balances[to] += amount;

        emit Transfer(from, to, amount);
    }

    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);
    }

    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
        }
        _totalSupply -= amount;

        emit Transfer(account, address(0), amount);
    }

    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    function _spendAllowance(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

    // My Functions

    function calculateFee(FeesIndex fee_index, uint amount) internal view returns(uint) {
        return (amount * feePercentages[uint(fee_index)])  / (10**(feeDecimal + 2));
    }
}
```

Ahora lo compilamos.

```
npx hardhat compile
```

## 5. Pruebas unitarias forkeando mainnet

Borramos el archivo ejemplo `test/MyToken.js` y agregamos el nuestro propio. Las pruebas unitarias nos ayudan a aseguramos que todo esté funcionando bien.

`test/MyToken.js`
```js
const {
  time,
  loadFixture,
} = require("@nomicfoundation/hardhat-network-helpers");
const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs");
const { expect } = require("chai");

describe("Lock", function () {
  // We define a fixture to reuse the same setup in every test.
  // We use loadFixture to run this setup once, snapshot that state,
  // and reset Hardhat Network to that snapshopt in every test.
  async function launchAndFundWalletA() {
    const blockNumBefore = await ethers.provider.getBlockNumber();
    const blockBefore = await ethers.provider.getBlock(blockNumBefore);
    deadline = blockBefore.timestamp + 500;

    const [deployer, vaultWallet, walletA, walletB] = await ethers.getSigners();

    const MyToken = await ethers.getContractFactory("MyToken");
    const uniswapRouter = await ethers.getContractAt("IUniswapV2Router02", "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D");
    const myToken = await MyToken.deploy(uniswapRouter.address, vaultWallet.address);

    await myToken.approve(uniswapRouter.address, ethers.utils.parseEther("500000"))
    await uniswapRouter.addLiquidityETH(
      myToken.address,
      ethers.utils.parseEther("500000"),
      ethers.utils.parseEther("0"),
      ethers.utils.parseEther("0"),
      deployer.address,
      deadline,
      { value: ethers.utils.parseEther("1") })

    await myToken.transfer(walletA.address, ethers.utils.parseEther("1000"))
    return { uniswapRouter, myToken, vaultWallet, walletA, walletB, deadline };
  }

  describe("Fee collection", function () {
    it("Should collect fees on P2P", async function () {
      const { uniswapRouter, myToken, vaultWallet, walletA, walletB } = await loadFixture(launchAndFundWalletA);
      await myToken.connect(walletA).transfer(walletB.address, ethers.utils.parseEther("100"))
      expect(
        ethers.utils.parseEther("95.0")
      ).to.equal(
        await myToken.balanceOf(walletB.address)
      );
      expect(
        ethers.utils.parseEther("5.0")
      ).to.equal(
        await myToken.balanceOf(vaultWallet.address)
      );
    });

    it("Should collect fees on Buy", async function () {
      const { uniswapRouter, myToken, vaultWallet, walletA, walletB, deadline } = await loadFixture(launchAndFundWalletA);

      await myToken.connect(walletA).approve(uniswapRouter.address, ethers.utils.parseEther("100.0"))
      await uniswapRouter.connect(walletA).swapETHForExactTokens(
          ethers.utils.parseEther("100.0"),
          [await uniswapRouter.WETH(), myToken.address],
          walletA.address,
          deadline,
          { value: ethers.utils.parseEther("0.1") })
      
      expect(
        ethers.utils.parseEther("10.0")
      ).to.equal(
        await myToken.balanceOf(vaultWallet.address)
      );
    });

    it("Should collect fees on Sell", async function () {
      const { uniswapRouter, myToken, vaultWallet, walletA, walletB, deadline } = await loadFixture(launchAndFundWalletA);

      await myToken.connect(walletA).approve(uniswapRouter.address, ethers.utils.parseEther("100.0"))
      await uniswapRouter.connect(walletA).swapExactTokensForETHSupportingFeeOnTransferTokens(
        ethers.utils.parseEther("100.0"),
        ethers.utils.parseEther("0"),
        [myToken.address, await uniswapRouter.WETH()],
        walletA.address,
        deadline)

      expect(
        ethers.utils.parseEther("15.0")
      ).to.equal(
        await myToken.balanceOf(vaultWallet.address)
      );
    });
  });
});
```

Ejecutamos los tests y nos aseguramos que todo funcionará bien haciendo las pruebas en el estado actual de mainnet forkeando.

```
npx hardhat test
```

## 6. Lanzamiento y Auto Verificación

Creamos nuestro archivo de deploy donde configuramos la rutina de lanzamientos y definimos los parametros de los constructores.

`scripts/deploy.js`
```js
const hre = require("hardhat");

async function main() {
  const MyToken = await ethers.getContractFactory("MyToken");
  uniswapRouterAddress = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
  vaultAddress = "0x18747BE67c5886881075136eb678cEADaf808028"
  const myToken = await MyToken.deploy(uniswapRouterAddress, vaultAddress);
  await myToken.deployed();
  console.log("myToken launched at:", myToken.address);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
```

Ahora lanzamos. Enste caso lo hacemos en gerli pero en el archivo `hardhat.config.js` podemos configurar el destino.

```bash
npx hardhat run scripts/deploy.js --network goerli
```

Y lo auto verificamos si no lo hizo automáticamente

```bash
npx hardhat verify --network goerli ADDRESSDELCONTRATO PARAM1 PARAM2
```

Por ejemplo debería quedar algo así

```bash
npx hardhat verify --network goerli 0xFA4A4d277A8F8FF6158615c8FaE2Abfe147fb64c 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D 0x730bF3B67090511A64ABA060FbD2F7903536321E
```

## Bono: Test automáticos con Github Actions

Los tests automáticos nos van a permitir detectar errores en cada cambio en el código. Nuestros tests se ejecutarán autómaticamente cada vez que se haga una push o pull request y nos avisará si algún test no pasó.

Primero debemos hacer un par de modificaciones en el archivo de configuración de hardhat para que pueda operar sin necesidad de tener todas las variables.

`hardhat.config.js`
```js
require("@nomicfoundation/hardhat-toolbox");
require("@nomiclabs/hardhat-etherscan");
require('dotenv').config()

module.exports = {
  solidity: "0.8.16",
  networks: {
    hardhat: {
      forking: {
        url: process.env.MAINNET_RPC_URL,
      },
    }
  }
};

if(process.env.GOERLI_RPC_URL && process.env.GOERLI_PRIVATE_KEY)
{
  module.exports["networks"]["goerli"] = {
    url: process.env.GOERLI_RPC_URL,
    accounts: [process.env.GOERLI_PRIVATE_KEY],
  }
}

if(process.env.ETHERSCAN_API_KEY)
{
  module.exports["etherscan"] = {
    apiKey: process.env.ETHERSCAN_API_KEY
  }
}
```

`.github/workflows/unit-tests.yml`
```yaml
name: Node.js CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [14.x]

    steps:
      - uses: actions/checkout@v3
      - name: Mainnet Forking Tests
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm install
      - run: export MAINNET_RPC_URL=${{ secrets.MAINNET_RPC_URL }}; npx hardhat test
```

Para final agrega esta badge a tu Readme para presumir tu workflow.

`README.md`
```
![workflow](https://github.com/TUUSUARIO/TUREPO/actions/workflows/unit-tests.yml/badge.svg)
```

**Gracias por ver este video!**

Sígannos en dev.to y en [Youtube](https://www.youtube.com/channel/UCNRB4tgwp09z4391JRjEsRA) para todo lo relacionado al desarrollo en Blockchain en Español.