¿Qué Es Un Contrato De Token ERC-1404?

El estándar de contrato de token Solidity ERC-1404 está diseñado para permitir que el participante cumpla con los requisitos de cumplimiento en la industria de valores. Este estándar de contrato es lo suficientemente flexible como para considerar las leyes de gobierno corporativo, banca y valores. Como resultado, las empresas pueden diseñar y desarrollar un token que pueda cumplir con varias regulaciones en diferentes jurisdicciones.

¿Qué es un contrato de token ERC-1404?

Un contrato de token ERC-1404 tiene todas las mismas características y beneficios de un contrato de token ERC-20 con algunas mejoras para permitir que los emisores hagan cumplir las restricciones reglamentarias. El nuevo estándar de contrato agrega una restricción de transferencia de detección y un mensaje que indica el motivo de la restricción. Este contrato estándar permite al creador implementar las reglas requeridas para seguir cumpliendo con su jurisdicción.

El estándar de token ERC-20 proporciona las características básicas que se enumeran a continuación. Estas características son estándar y existen en todos los tokens ERC-20.

contract ERC20 {
  function totalSupply() public view returns (uint256);
  function balanceOf(address who) public view returns (uint256);
  function transfer(address to, uint256 value) public returns (bool);
  function allowance(address owner, address spender) public view returns (uint256);
  function transferFrom(address from, address to, uint256 value) public returns (bool);
  function approve(address spender, uint256 value) public returns (bool);
  event Approval(address indexed owner, address indexed spender, uint256 value);
  event Transfer(address indexed from, address indexed to, uint256 value);
}

 

Por diseño, el estándar de token ERC-1404 es totalmente compatible con el estándar de token ERC-20 . El estándar ERC-1404 se basa en la interfaz de ERC-20 al agregar dos funciones.

  • detectTransferRestriction: devuelve un código de restricción (0 está reservado para el éxito)
  • messageForTransferRestriction: devuelve una cadena de mensaje legible por humanos que detalla el código de restricción
contract ERC1404 is ERC20 {
  function detectTransferRestriction (address from, address to, uint256 value) public view returns (uint8);
  function messageForTransferRestriction (uint8 restrictionCode) public view returns (string);
}

 

Estas dos funciones permiten al emisor hacer cumplir las restricciones de transferencia dentro del contrato inteligente. El emisor del token puede implementar cómo se comportan estas dos funciones, lo que permite una mayor flexibilidad. Se requiere que detectTransferRestriction se evalúe dentro de las funciones transfer y transferFrom de un token. Si detectTransferRestriction devuelve un valor diferente al de  0la transacción, debe revertirse.

Estas dos funciones agregan el cumplimiento necesario para un token de valores y pueden ser valiosas en muchas situaciones. Como ejemplo, un contrato ERC-1404 puede tener lógica para determinar;

  • Cuentas incluidas en la lista blanca
  • período de bloqueo
  • etc.

Visite esta página de Github para ver ejemplos de restricciones en un contrato ERC-1404. Además, revise los patrones comunes de restricción de transferencia a continuación.

  1. Porcentaje de propiedad de la cuenta
  2. Número de cuentas
  3. Listas blancas de cuentas
  4. Divisibilidad de fichas

Ejemplo del mundo real

INX Crypto Currency Exchange ha creado un token de valores en la cadena de bloques Ethereum utilizando el estándar de token ERC-1404. Lea su contrato a continuación o en Etherscan.io . Concéntrese en las funciones detectTransferRestriction para aprender cómo se evalúan e implementan las restricciones.

/**
 *Submitted for verification at Etherscan.io on 2020-02-03
*/

// File: contracts/1404/IERC1404.sol

pragma solidity 0.5.8;

interface IERC1404 {
    /// @notice Detects if a transfer will be reverted and if so returns an appropriate reference code
    /// @param from Sending address
    /// @param to Receiving address
    /// @param value Amount of tokens being transferred
    /// @return Code by which to reference message for rejection reasoning
    /// @dev Overwrite with your custom transfer restriction logic
    function detectTransferRestriction (address from, address to, uint256 value) external view returns (uint8);

    /// @notice Detects if a transferFrom will be reverted and if so returns an appropriate reference code
    /// @param sender Transaction sending address
    /// @param from Source of funds address
    /// @param to Receiving address
    /// @param value Amount of tokens being transferred
    /// @return Code by which to reference message for rejection reasoning
    /// @dev Overwrite with your custom transfer restriction logic
    function detectTransferFromRestriction (address sender, address from, address to, uint256 value) external view returns (uint8);

    /// @notice Returns a human-readable message for a given restriction code
    /// @param restrictionCode Identifier for looking up a message
    /// @return Text showing the restriction's reasoning
    /// @dev Overwrite with your custom message and restrictionCode handling
    function messageForTransferRestriction (uint8 restrictionCode) external view returns (string memory);
}

interface IERC1404getSuccessCode {
    /// @notice Return the uint256 that represents the SUCCESS_CODE
    /// @return uint256 SUCCESS_CODE
    function getSuccessCode () external view returns (uint256);
}

/**
 * @title IERC1404Success
 * @dev Combines IERC1404 and IERC1404getSuccessCode interfaces, to be implemented by the TransferRestrictions contract
 */
contract IERC1404Success is IERC1404getSuccessCode, IERC1404 {
}

// File: contracts/1404/IERC1404Validators.sol

pragma solidity 0.5.8;

/**
 * @title IERC1404Validators
 * @dev Interfaces implemented by the token contract to be called by the TransferRestrictions contract
 */
interface IERC1404Validators {
    /// @notice Returns the token balance for an account
    /// @param account The address to get the token balance of
    /// @return uint256 representing the token balance for the account
    function balanceOf (address account) external view returns (uint256);

    /// @notice Returns a boolean indicating the paused state of the contract
    /// @return true if contract is paused, false if unpaused
    function paused () external view returns (bool);

    /// @notice Determine if sender and receiver are whitelisted, return true if both accounts are whitelisted
    /// @param from The address sending tokens.
    /// @param to The address receiving tokens.
    /// @return true if both accounts are whitelisted, false if not
    function checkWhitelists (address from, address to) external view returns (bool);

    /// @notice Determine if a users tokens are locked preventing a transfer
    /// @param _address the address to retrieve the data from
    /// @param amount the amount to send
    /// @param balance the token balance of the sending account
    /// @return true if user has sufficient unlocked token to transfer the requested amount, false if not
    function checkTimelock (address _address, uint256 amount, uint256 balance) external view returns (bool);
}

// File: @openzeppelin/contracts/access/Roles.sol

pragma solidity ^0.5.0;

/**
 * @title Roles
 * @dev Library for managing addresses assigned to a Role.
 */
library Roles {
    struct Role {
        mapping (address => bool) bearer;
    }

    /**
     * @dev Give an account access to this role.
     */
    function add(Role storage role, address account) internal {
        require(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;
    }

    /**
     * @dev Remove an account's access to this role.
     */
    function remove(Role storage role, address account) internal {
        require(has(role, account), "Roles: account does not have role");
        role.bearer[account] = false;
    }

    /**
     * @dev Check if an account has this role.
     * @return bool
     */
    function has(Role storage role, address account) internal view returns (bool) {
        require(account != address(0), "Roles: account is the zero address");
        return role.bearer[account];
    }
}

// File: contracts/roles/OwnerRole.sol

pragma solidity 0.5.8;


contract OwnerRole {
    using Roles for Roles.Role;

    event OwnerAdded(address indexed addedOwner, address indexed addedBy);
    event OwnerRemoved(address indexed removedOwner, address indexed removedBy);

    Roles.Role private _owners;

    modifier onlyOwner() {
        require(isOwner(msg.sender), "OwnerRole: caller does not have the Owner role");
        _;
    }

    function isOwner(address account) public view returns (bool) {
        return _owners.has(account);
    }

    function addOwner(address account) public onlyOwner {
        _addOwner(account);
    }

    function removeOwner(address account) public onlyOwner {
        require(msg.sender != account, "Owners cannot remove themselves as owner");
        _removeOwner(account);
    }

    function _addOwner(address account) internal {
        _owners.add(account);
        emit OwnerAdded(account, msg.sender);
    }

    function _removeOwner(address account) internal {
        _owners.remove(account);
        emit OwnerRemoved(account, msg.sender);
    }
}

// File: @openzeppelin/contracts/GSN/Context.sol

pragma solidity ^0.5.0;

/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with GSN meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
contract Context {
    // Empty internal constructor, to prevent people from mistakenly deploying
    // an instance of this contract, which should be used via inheritance.
    constructor () internal { }
    // solhint-disable-previous-line no-empty-blocks

    function _msgSender() internal view returns (address payable) {
        return msg.sender;
    }

    function _msgData() internal view returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

// File: @openzeppelin/contracts/token/ERC20/IERC20.sol

pragma solidity ^0.5.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
 * the optional functions; to access them see {ERC20Detailed}.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

// File: @openzeppelin/contracts/math/SafeMath.sol

pragma solidity ^0.5.0;

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     *
     * _Available since v2.4.0._
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     *
     * _Available since v2.4.0._
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     *
     * _Available since v2.4.0._
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}

// File: @openzeppelin/contracts/token/ERC20/ERC20.sol

pragma solidity ^0.5.0;




/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20Mintable}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of ERC20 applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20 {
    using SafeMath for uint256;

    mapping (address => uint256) private _balances;

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

    uint256 private _totalSupply;

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20};
     *
     * Requirements:
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for `sender`'s tokens of at least
     * `amount`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal {
        require(account != address(0), "ERC20: mint to the zero address");

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

     /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal {
        require(account != address(0), "ERC20: burn from the zero address");

        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
     *
     * This is internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal {
        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);
    }

    /**
     * @dev Destroys `amount` tokens from `account`.`amount` is then deducted
     * from the caller's allowance.
     *
     * See {_burn} and {_approve}.
     */
    function _burnFrom(address account, uint256 amount) internal {
        _burn(account, amount);
        _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
    }
}

// File: contracts/roles/RevokerRole.sol

pragma solidity 0.5.8;


contract RevokerRole is OwnerRole {

    event RevokerAdded(address indexed addedRevoker, address indexed addedBy);
    event RevokerRemoved(address indexed removedRevoker, address indexed removedBy);

    Roles.Role private _revokers;

    modifier onlyRevoker() {
        require(isRevoker(msg.sender), "RevokerRole: caller does not have the Revoker role");
        _;
    }

    function isRevoker(address account) public view returns (bool) {
        return _revokers.has(account);
    }

    function addRevoker(address account) public onlyOwner {
        _addRevoker(account);
    }

    function removeRevoker(address account) public onlyOwner {
        _removeRevoker(account);
    }

    function _addRevoker(address account) internal {
        _revokers.add(account);
        emit RevokerAdded(account, msg.sender);
    }

    function _removeRevoker(address account) internal {
        _revokers.remove(account);
        emit RevokerRemoved(account, msg.sender);
    }
}

// File: contracts/capabilities/Revocable.sol

pragma solidity 0.5.8;



/**
 * Allows an administrator to move tokens from a target account to their own.
 */
contract Revocable is ERC20, RevokerRole {

  event Revoke(address indexed revoker, address indexed from, uint256 amount);

  function revoke(
    address _from,
    uint256 _amount
  )
    public
    onlyRevoker
    returns (bool)
  {
    ERC20._transfer(_from, msg.sender, _amount);
    emit Revoke(msg.sender, _from, _amount);
    return true;
  }
}

// File: contracts/roles/WhitelisterRole.sol

pragma solidity 0.5.8;


contract WhitelisterRole is OwnerRole {

    event WhitelisterAdded(address indexed addedWhitelister, address indexed addedBy);
    event WhitelisterRemoved(address indexed removedWhitelister, address indexed removedBy);

    Roles.Role private _whitelisters;

    modifier onlyWhitelister() {
        require(isWhitelister(msg.sender), "WhitelisterRole: caller does not have the Whitelister role");
        _;
    }

    function isWhitelister(address account) public view returns (bool) {
        return _whitelisters.has(account);
    }

    function addWhitelister(address account) public onlyOwner {
        _addWhitelister(account);
    }

    function removeWhitelister(address account) public onlyOwner {
        _removeWhitelister(account);
    }

    function _addWhitelister(address account) internal {
        _whitelisters.add(account);
        emit WhitelisterAdded(account, msg.sender);
    }

    function _removeWhitelister(address account) internal {
        _whitelisters.remove(account);
        emit WhitelisterRemoved(account, msg.sender);
    }
}

// File: contracts/capabilities/Whitelistable.sol

pragma solidity 0.5.8;


/**
 * @title Whitelistable
 * @dev Allows tracking whether addressess are allowed to hold tokens.
 */
contract Whitelistable is WhitelisterRole {

    event WhitelistUpdate(address _address, bool status, string data);

    // Tracks whether an address is whitelisted
    // data field can track any external field (like a hash of personal details)
    struct whiteListItem {
        bool status;
        string data;
    }

    // white list status
    mapping (address => whiteListItem) public whitelist;

    /**
    * @dev Set a white list address
    * @param to the address to be set
    * @param status the whitelisting status (true for yes, false for no)
    * @param data a string with data about the whitelisted address
    */
    function setWhitelist(address to, bool status, string memory data)  public onlyWhitelister returns(bool){
        whitelist[to] = whiteListItem(status, data);
        emit WhitelistUpdate(to, status, data);
        return true;
    }

    /**
    * @dev Get the status of the whitelist
    * @param _address the address to be check
    */
    function getWhitelistStatus(address _address) public view returns(bool){
        return whitelist[_address].status;
    }

    /**
    * @dev Get the data of and address in the whitelist
    * @param _address the address to retrieve the data from
    */
    function getWhitelistData(address _address) public view returns(string memory){
        return whitelist[_address].data;
    }

    /**
    * @dev Determine if sender and receiver are whitelisted, return true if both accounts are whitelisted
    * @param from The address sending tokens.
    * @param to The address receiving tokens.
    */
    function checkWhitelists(address from, address to) external view returns (bool) {
        return whitelist[from].status && whitelist[to].status;
    }
}

// File: contracts/roles/TimelockerRole.sol

pragma solidity 0.5.8;


contract TimelockerRole is OwnerRole {

    event TimelockerAdded(address indexed addedTimelocker, address indexed addedBy);
    event TimelockerRemoved(address indexed removedTimelocker, address indexed removedBy);

    Roles.Role private _timelockers;

    modifier onlyTimelocker() {
        require(isTimelocker(msg.sender), "TimelockerRole: caller does not have the Timelocker role");
        _;
    }

    function isTimelocker(address account) public view returns (bool) {
        return _timelockers.has(account);
    }

    function addTimelocker(address account) public onlyOwner {
        _addTimelocker(account);
    }

    function removeTimelocker(address account) public onlyOwner {
        _removeTimelocker(account);
    }

    function _addTimelocker(address account) internal {
        _timelockers.add(account);
        emit TimelockerAdded(account, msg.sender);
    }

    function _removeTimelocker(address account) internal {
        _timelockers.remove(account);
        emit TimelockerRemoved(account, msg.sender);
    }
}

// File: contracts/capabilities/Timelockable.sol

pragma solidity 0.5.8;



/**
 * @title INX Timelockable
 * @dev Lockup all or a portion of an accounts tokens until an expiration date
 */
contract Timelockable is TimelockerRole {

    using SafeMath for uint256;

    struct lockupItem {
        uint256 amount;
        uint256 releaseTime;
    }

    mapping (address => lockupItem) lockups;

    event AccountLock(address _address, uint256 amount, uint256 releaseTime);
    event AccountRelease(address _address, uint256 amount);


    /**
    * @dev lock address and amount and lock it, set the release time
    * @param _address the address to lock
    * @param amount the amount to lock
    * @param releaseTime of the locked amount (in seconds since the epoch)
    */
    function lock( address _address, uint256 amount, uint256 releaseTime) public onlyTimelocker returns (bool) {
        require(releaseTime > block.timestamp, "Release time needs to be in the future");
        require(_address != address(0), "Address must be valid for lockup");

        lockupItem memory _lockupItem = lockupItem(amount, releaseTime);
        lockups[_address] = _lockupItem;
        emit AccountLock(_address, amount, releaseTime);
        return true;
    }

    /**
    * @dev release locked amount
    * @param _address the address to retrieve the data from
    * @param amountToRelease the amount to check
    */
    function release( address _address, uint256 amountToRelease) public onlyTimelocker returns(bool) {
        require(_address != address(0), "Address must be valid for release");

        uint256 _lockedAmount = lockups[_address].amount;

        // nothing to release
        if(_lockedAmount == 0){
            emit AccountRelease(_address, 0);
            return true;
        }

        // extract release time for re-locking
        uint256 _releaseTime = lockups[_address].releaseTime;

        // delete the lock entry
        delete lockups[_address];

        if(_lockedAmount >= amountToRelease){
           uint256 newLockedAmount = _lockedAmount.sub(amountToRelease);

           // re-lock the new locked balance
           lock(_address, newLockedAmount, _releaseTime);
           emit AccountRelease(_address, amountToRelease);
           return true;
        } else {
            // if they requested to release more than the locked amount emit the event with the locked amount that has been released
            emit AccountRelease(_address, _lockedAmount);
            return true;
        }
    }

    /**
    * @dev return true if the given account has enough unlocked tokens to send the requested amount
    * @param _address the address to retrieve the data from
    * @param amount the amount to send
    * @param balance the token balance of the sending account
    */
    function checkTimelock(address _address, uint256 amount, uint256 balance) external view returns (bool) {
        // if the user does not have enough tokens to send regardless of lock return true here
        // the failure will still fail but this should make it explicit that the transfer failure is not
        // due to locked tokens but because of too low token balance
        if (balance < amount) {
            return true;
        }

        // get the sending addresses token balance that is not locked
        uint256 nonLockedAmount = balance.sub(lockups[_address].amount);

        // determine if the sending address has enough free tokens to send the entire amount
        bool notLocked = amount <= nonLockedAmount;

        // if the timelock is greater then the release time the time lock is expired
        bool timeLockExpired = block.timestamp > lockups[_address].releaseTime;

        // if the timelock is expired OR the requested amount is available the transfer is not locked
        if(timeLockExpired || notLocked){
            return true;

        // if the timelocked is not expired AND the requested amount is not available the tranfer is locked
        } else {
            return false;
        }
    }

    /**
    * @dev get address lockup info
    * @param _address the address to retrieve the data from
    * @return array of 2 uint256, release time (in seconds since the epoch) and amount (in INX)
    */
    function checkLockup(address _address) public view returns(uint256, uint256) {
        // copy lockup data into memory
        lockupItem memory _lockupItem = lockups[_address];

        return (_lockupItem.releaseTime, _lockupItem.amount);
    }
}

// File: contracts/roles/PauserRole.sol

pragma solidity 0.5.8;


contract PauserRole is OwnerRole {

    event PauserAdded(address indexed addedPauser, address indexed addedBy);
    event PauserRemoved(address indexed removedPauser, address indexed removedBy);

    Roles.Role private _pausers;

    modifier onlyPauser() {
        require(isPauser(msg.sender), "PauserRole: caller does not have the Pauser role");
        _;
    }

    function isPauser(address account) public view returns (bool) {
        return _pausers.has(account);
    }

    function addPauser(address account) public onlyOwner {
        _addPauser(account);
    }

    function removePauser(address account) public onlyOwner {
        _removePauser(account);
    }

    function _addPauser(address account) internal {
        _pausers.add(account);
        emit PauserAdded(account, msg.sender);
    }

    function _removePauser(address account) internal {
        _pausers.remove(account);
        emit PauserRemoved(account, msg.sender);
    }
}

// File: contracts/capabilities/Pausable.sol

pragma solidity 0.5.8;


/**
 * Allows transfers on a token contract to be paused by an administrator.
 */
contract Pausable is PauserRole {
    event Paused();
    event Unpaused();

    bool private _paused;

    /**
     * @return true if the contract is paused, false otherwise.
     */
    function paused() external view returns (bool) {
        return _paused;
    }

    /**
     * @dev internal function, triggers paused state
     */
    function _pause() internal {
        _paused = true;
        emit Paused();
    }

    /**
     * @dev internal function, returns to unpaused state
     */
    function _unpause() internal {
        _paused = false;
        emit Unpaused();
    }

     /**
     * @dev called by pauser role to pause, triggers stopped state
     */
    function pause() public onlyPauser {
        _pause();
    }

    /**
     * @dev called by pauer role to unpause, returns to normal state
     */
    function unpause() public onlyPauser {
        _unpause();
    }
}

// File: @openzeppelin/contracts/token/ERC20/ERC20Detailed.sol

pragma solidity ^0.5.0;


/**
 * @dev Optional functions from the ERC20 standard.
 */
contract ERC20Detailed is IERC20 {
    string private _name;
    string private _symbol;
    uint8 private _decimals;

    /**
     * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of
     * these values are immutable: they can only be set once during
     * construction.
     */
    constructor (string memory name, string memory symbol, uint8 decimals) public {
        _name = name;
        _symbol = symbol;
        _decimals = decimals;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5,05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view returns (uint8) {
        return _decimals;
    }
}

// File: contracts/InxToken.sol

pragma solidity 0.5.8;

contract InxToken is IERC1404, IERC1404Validators, IERC20, ERC20Detailed, OwnerRole, Revocable, Whitelistable, Timelockable, Pausable {

    // Token Details
    string constant TOKEN_NAME = "INX Token";
    string constant TOKEN_SYMBOL = "INX";
    uint8 constant TOKEN_DECIMALS = 18;

    // Token supply - 2 Hundred Million Tokens, with 18 decimal precision
    uint256 constant HUNDRED_MILLION = 100000000;
    uint256 constant TOKEN_SUPPLY = 2 * HUNDRED_MILLION * (10 ** uint256(TOKEN_DECIMALS));

    // This tracks the external contract where restriction logic is executed
    IERC1404Success private transferRestrictions;

    // Event tracking when restriction logic contract is updated
    event RestrictionsUpdated (address newRestrictionsAddress, address updatedBy);

    /**
    Constructor for the token to set readable details and mint all tokens
    to the specified owner.
    */
    constructor(address owner) public
        ERC20Detailed(TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS)
    {
        _mint(owner, TOKEN_SUPPLY);
        _addOwner(owner);
    }

    /**
    Function that can only be called by an owner that updates the address
    with the ERC1404 Transfer Restrictions defined
    */
    function updateTransferRestrictions(address _newRestrictionsAddress)
        public
        onlyOwner
        returns (bool)
    {
        transferRestrictions = IERC1404Success(_newRestrictionsAddress);
        emit RestrictionsUpdated(address(transferRestrictions), msg.sender);
        return true;
    }

    /**
    The address with the Transfer Restrictions contract
    */
    function getRestrictionsAddress () public view returns (address) {
        return address(transferRestrictions);
    }


    /**
    This function detects whether a transfer should be restricted and not allowed.
    If the function returns SUCCESS_CODE (0) then it should be allowed.
    */
    function detectTransferRestriction (address from, address to, uint256 amount)
        public
        view
        returns (uint8)
    {
        // Verify the external contract is valid
        require(address(transferRestrictions) != address(0), 'TransferRestrictions contract must be set');

        // call detectTransferRestriction on the current transferRestrictions contract
        return transferRestrictions.detectTransferRestriction(from, to, amount);
    }

    /**
    This function detects whether a transferFrom should be restricted and not allowed.
    If the function returns SUCCESS_CODE (0) then it should be allowed.
    */
    function detectTransferFromRestriction (address sender, address from, address to, uint256 amount)
        public
        view
        returns (uint8)
    {
        // Verify the external contract is valid
        require(address(transferRestrictions) != address(0), 'TransferRestrictions contract must be set');

        // call detectTransferFromRestriction on the current transferRestrictions contract
        return  transferRestrictions.detectTransferFromRestriction(sender, from, to, amount);
    }

    /**
    This function allows a wallet or other client to get a human readable string to show
    a user if a transfer was restricted.  It should return enough information for the user
    to know why it failed.
    */
    function messageForTransferRestriction (uint8 restrictionCode)
        external
        view
        returns (string memory)
    {
        // call messageForTransferRestriction on the current transferRestrictions contract
        return transferRestrictions.messageForTransferRestriction(restrictionCode);
    }

    /**
    Evaluates whether a transfer should be allowed or not.
    */
    modifier notRestricted (address from, address to, uint256 value) {
        uint8 restrictionCode = transferRestrictions.detectTransferRestriction(from, to, value);
        require(restrictionCode == transferRestrictions.getSuccessCode(), transferRestrictions.messageForTransferRestriction(restrictionCode));
        _;
    }

    /**
    Evaluates whether a transferFrom should be allowed or not.
    */
    modifier notRestrictedTransferFrom (address sender, address from, address to, uint256 value) {
        uint8 transferFromRestrictionCode = transferRestrictions.detectTransferFromRestriction(sender, from, to, value);
        require(transferFromRestrictionCode == transferRestrictions.getSuccessCode(), transferRestrictions.messageForTransferRestriction(transferFromRestrictionCode));
        _;
    }

    /**
    Overrides the parent class token transfer function to enforce restrictions.
    */
    function transfer (address to, uint256 value)
        public
        notRestricted(msg.sender, to, value)
        returns (bool success)
    {
        success = ERC20.transfer(to, value);
    }

    /**
    Overrides the parent class token transferFrom function to enforce restrictions.
    */
    function transferFrom (address from, address to, uint256 value)
        public
        notRestrictedTransferFrom(msg.sender, from, to, value)
        returns (bool success)
    {
        success = ERC20.transferFrom(from, to, value);
    }
}

Pruébalo en  Remix

Fuente: https://cryptomarketpool.com/what-is-an-erc-1404-token-contract/

 #token #erc 

¿Qué Es Un Contrato De Token ERC-1404?
坂本  篤司

坂本 篤司

1651285440

ERC-20交換プラットフォームの構築方法

トークンとNFTのアプリケーションは毎日増加しています。それらはテーブルに多くの新しい可能性をもたらします。それらをゲームで使用することから、イベント用に独自の仮想通貨を作成することまで、 Web3テクノロジーを使用して多くのことを行うことができます。

今日は、ユーザーが特定のトークンを売買できるトークン交換プラットフォームを作成します。ここで使用するトークンタイプはERC-20であり、PolygonMumbaiネットワークに展開します。

私たちが従うアプローチは、最初にトークンを作成するスマートコントラクトを作成し、次にトークンの売買を容易にする別のスマートコントラクトを作成することです。次に、Next.jsアプリケーションで両方のスマートコントラクトを使用して、ユーザーが売買にアクセスできるようにします。

前提条件

  • Reactの実用的な知識
  • Next.jsの実用的な知識
  • Solidityの実用的な知識
  • Node.jsがインストールされました
  • コードエディタ– VisualStudioCodeが好きです
  • 少なくとも1つのウォレットがインストールされたMetaMask拡張機能
  • ポリゴンムンバイテストネットに接続されたMetaMask

チュートリアルに行き詰まっていると感じた場合は、GitHubリポジトリを参照してください。

トークンスマートコントラクトの作成

トークンスマートコントラクトは、トークンを作成するのに役立ちます。Remix IDEを開き、フォルダTestToken.solの下にという新しいファイルを作成します。contractsファイルで次のコードを使用します。

// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract TestToken is ERC20 {
    constructor() ERC20("TestToken", "TEST"){
        _mint(msg.sender, 10000 * 10 ** 18);
    }
}

上記のコードでは、OpenZeppelinコントラクトテンプレートを使用してERC-20トークンを作成しています。これらのテンプレートは、ERC-20トークンに必要な最低限の機能を提供し、安全で最適化されているため、トークンのセキュリティを強化することを心配する必要はありません。コントラクトのコンストラクターでは、トークンの名前とシンボルを指定し、10,000個のトークンをコントラクト作成者のウォレットに作成しています。

ここで、 Ctrl + S(またはMacの場合はCmd + S )を押してコントラクトをコンパイルします。MetaMaskをPolygonMumbaiに接続していることを確認してください。サイドバーの[デプロイ]タブに移動し、環境をInjected Web3に設定して、RemixIDEがMetaMaskを使用してコントラクトをデプロイできるようにします。

他の設定が次のようになっていることを確認してください。

 

すべての設定を確認したら、[展開]をクリックします。これにより、MetaMask認証ポップアップが開きます。[確認]をクリックします。

Polygon Mumbaiウォレットに資金がない場合は、蛇口から資金を得ることができます。トランザクションを確認したら、左側のサイドバーにコントラクトがデプロイされていることを確認するまで数秒待ちます。

ベンダー契約を構成するときに必要になるため、契約アドレスをコピーします。

ベンダー契約の作成

TestTokenVendor.solフォルダの下にという新しいファイルを作成しcontractsます。契約には次のコードを使用します。

// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.4;
import "./TestToken.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract Vendor is Ownable {
  TestToken yourToken;
  uint256 public tokensPerMatic = 100;
  event BuyTokens(address buyer, uint256 amountOfMATIC, uint256 amountOfTokens);
  constructor(address tokenAddress) {
    yourToken = TestToken(tokenAddress);
  }

  function buyTokens() public payable returns (uint256 tokenAmount) {
    require(msg.value > 0, "You need to send some MATIC to proceed");
    uint256 amountToBuy = msg.value * tokensPerMatic;

    uint256 vendorBalance = yourToken.balanceOf(address(this));
    require(vendorBalance >= amountToBuy, "Vendor has insufficient tokens");

    (bool sent) = yourToken.transfer(msg.sender, amountToBuy);
    require(sent, "Failed to transfer token to user");

    emit BuyTokens(msg.sender, msg.value, amountToBuy);
    return amountToBuy;
  }
  function sellTokens(uint256 tokenAmountToSell) public {

    require(tokenAmountToSell > 0, "Specify an amount of token greater than zero");

    uint256 userBalance = yourToken.balanceOf(msg.sender);
    require(userBalance >= tokenAmountToSell, "You have insufficient tokens");

    uint256 amountOfMATICToTransfer = tokenAmountToSell / tokensPerMatic;
    uint256 ownerMATICBalance = address(this).balance;
    require(ownerMATICBalance >= amountOfMATICToTransfer, "Vendor has insufficient funds");
    (bool sent) = yourToken.transferFrom(msg.sender, address(this), tokenAmountToSell);
    require(sent, "Failed to transfer tokens from user to vendor");

    (sent,) = msg.sender.call{value: amountOfMATICToTransfer}("");
    require(sent, "Failed to send MATIC to the user");
  }

  function withdraw() public onlyOwner {
    uint256 ownerBalance = address(this).balance;
    require(ownerBalance > 0, "No MATIC present in Vendor");
    (bool sent,) = msg.sender.call{value: address(this).balance}("");
    require(sent, "Failed to withdraw");
  }
}

上記のコードでOwnableは、OpenZeppelinテンプレートであるコントラクトを作成しています。これは、必要に応じて、この契約の所有権を他のウォレットに譲渡できることを意味します。

トークンコントラクトをインポートし、コンストラクターを介してそれにアドレスを提供します(コントラクトをデプロイするときに引数としてアドレスを提供します)。tokensPerMaticまた、変数内にトークンの一般的な価格を設定しています。この値は、契約に送信されたMATICの量に基づいて購入するトークンの量を計算するために使用されます。

このbuyTokens()関数では、最初にMATICが送信されたかどうか、およびベンダー契約に十分なトークンのバランスが取れているかどうかを確認します。次にyourToken.transfer()、ベンダーコントラクトからコントラクトコールを送信したウォレットにトークンを送信します。最後に、イベントを発行し、購入したトークンの数を返します。

関数はsellTokens()関数ほど単純ではありませんbuyTokens()。ユーザーが指定された数のトークンを持っているかどうかをチェックし、ユーザーに返送されるMATICの量を計算し、ベンダー契約に十分な量のMATICがバランスしているかどうかをチェックします。

ただし、ここで楽しい部分があります。ユーザーのウォレットからベンダー契約の残高にトークンを転送するだけでは不十分です。まず、ユーザーのトークンを管理し、これらのトークンを返送するために、ユーザーの承認を求める必要があります。この方法は、承認を求めるトークンの数の制限を設定する必要があるため、安全です。承認はMetaMaskで行われ、ユーザーはコントラクトが処理できるトークンの量を明確に確認できます。

この承認手順は、(この場合はNext.jsアプリケーションの)フロントエンドで行われます。承認後、transferFrom()ユーザーの資金をベンダー契約のウォレットに転送する機能を実行します。そして最後に、MATICをユーザーに送信します。

このwithdraw()機能は、契約の所有者のみが実行できます。この機能を使用すると、スマートコントラクトに保存されているすべてのMATICを所有者のウォレットに送信できます。

最後に、コントラクトをデプロイし、トークンコントラクトのアドレスをパラメーターとして渡します。

ベンダースマートコントラクトの構成

ベンダースマートコントラクトをデプロイしたので、動作させるためにいくつかのトークンを送信する必要があります。

[デプロイ]タブでトークンコントラクトを開きます。転送セクションの下に、ベンダー契約のアドレスとトークンの合計量(をクリックして取得できます)を貼り付けますtotalSupply

トランザクションをクリックしてMetaMaskトランザクションを承認すると、すべてのトークンがウォレットからベンダー契約残高に送信されます。これで、ベンダー契約によりトークンの売買が容易になります。

次に、 [コンパイル]タブに移動し、2つのコントラクトのABI(およびトークンアドレスとベンダーアドレス)をコピーします。これは、Next.jsファイルで必要になるためです。

Next.jsアプリのセットアップ

安全なディレクトリに移動し、ターミナルで次のコマンドを実行してNext.jsアプリを作成します。

npx create-next-app erc20-exchange-platform

(最近デフォルトでyarnになっている)--use-npmを使用して依存関係をインストールする場合は、最後に使用します。プロジェクトフォルダーに移動し、次のコマンドを実行して、いくつかの必要な依存関係をインストールします。npmcreate-next-app

#npm
npm install @thirdweb-dev/sdk @thirdweb-dev/react web3
#yarn
yarn add @thirdweb-dev/sdk @thirdweb-dev/react web3

@thirdweb-dev/sdk認証を支援し、アプリケーションをMetaMaskに接続するためにインストールしています。このように、認証に多くの時間を費やすことはありません。

また、ユーザーの状態に関する有用な情報@thirdweb-dev/reactを提供するために連携するフックを提供するためにインストールしています。トークンとベンダーのスマートコントラクトとやり取りするため@thirdweb-dev/sdkにインストールしています。web3

このチュートリアルではスタイリングについては説明しないので、詳細にあまり焦点を当てずにここに残しておきます。globals.cssフォルダの下を開きstyles、次のスタイルを使用します。

html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
    Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a {
  color: inherit;
  text-decoration: none;
}
* {
  box-sizing: border-box;
}
.home__container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
}
.home__button {
  background-color: #2ecc71;
  border: none;
  border-radius: 5px;
  font-size: 20px;
  padding: 15px;
  margin: 10px;
  cursor: pointer;
}
.exchange__container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
}
.exchange__textBox {
  width: 300px;
  height: 50px;
  border: 1px solid #c4c4c4;
  border-radius: 5px;
  font-size: 20px;
  padding: 15px;
  margin: 10px;
  cursor: pointer;
}
.exchange__button {
  width: 300px;
  height: 50px;
  border: 1px solid #2ecc71;
  background-color: #2ecc71;
  border-radius: 5px;
  font-size: 20px;
  padding: 15px;
  margin: 10px;
  cursor: pointer;
}

環境変数およびその他のデータの設定

プロジェクトディレクトリに呼び出される新しいファイルを作成します.env.local。これにより、契約アドレスが保持されます。

NEXT_PUBLIC_TOKEN_CONTRACT_ADDRESS=(token address here)
NEXT_PUBLIC_VENDOR_CONTRACT_ADDRESS=(vendor address here)

contracts.js次に、プロジェクトディレクトリにという新しいファイルを作成します。このファイルでは、両方の契約の契約ABIを保存してエクスポートします。

export const tokenABI = [
  {
    inputs: [],
    stateMutability: "nonpayable",
    type: "constructor",
  },
  {
    anonymous: false,
    inputs: [
      {
        indexed: true,
        internalType: "address",
        name: "owner",
        type: "address",
      },
      {
        indexed: true,
        internalType: "address",
        name: "spender",
        type: "address",
      },
      {
        indexed: false,
        internalType: "uint256",
        name: "value",
        type: "uint256",
      },
    ],
    name: "Approval",
    type: "event",
  },
  {
    anonymous: false,
    inputs: [
      {
        indexed: true,
        internalType: "address",
        name: "from",
        type: "address",
      },
      {
        indexed: true,
        internalType: "address",
        name: "to",
        type: "address",
      },
      {
        indexed: false,
        internalType: "uint256",
        name: "value",
        type: "uint256",
      },
    ],
    name: "Transfer",
    type: "event",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "owner",
        type: "address",
      },
      {
        internalType: "address",
        name: "spender",
        type: "address",
      },
    ],
    name: "allowance",
    outputs: [
      {
        internalType: "uint256",
        name: "",
        type: "uint256",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "spender",
        type: "address",
      },
      {
        internalType: "uint256",
        name: "amount",
        type: "uint256",
      },
    ],
    name: "approve",
    outputs: [
      {
        internalType: "bool",
        name: "",
        type: "bool",
      },
    ],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "account",
        type: "address",
      },
    ],
    name: "balanceOf",
    outputs: [
      {
        internalType: "uint256",
        name: "",
        type: "uint256",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [],
    name: "decimals",
    outputs: [
      {
        internalType: "uint8",
        name: "",
        type: "uint8",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "spender",
        type: "address",
      },
      {
        internalType: "uint256",
        name: "subtractedValue",
        type: "uint256",
      },
    ],
    name: "decreaseAllowance",
    outputs: [
      {
        internalType: "bool",
        name: "",
        type: "bool",
      },
    ],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "spender",
        type: "address",
      },
      {
        internalType: "uint256",
        name: "addedValue",
        type: "uint256",
      },
    ],
    name: "increaseAllowance",
    outputs: [
      {
        internalType: "bool",
        name: "",
        type: "bool",
      },
    ],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [],
    name: "name",
    outputs: [
      {
        internalType: "string",
        name: "",
        type: "string",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [],
    name: "symbol",
    outputs: [
      {
        internalType: "string",
        name: "",
        type: "string",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [],
    name: "totalSupply",
    outputs: [
      {
        internalType: "uint256",
        name: "",
        type: "uint256",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "to",
        type: "address",
      },
      {
        internalType: "uint256",
        name: "amount",
        type: "uint256",
      },
    ],
    name: "transfer",
    outputs: [
      {
        internalType: "bool",
        name: "",
        type: "bool",
      },
    ],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "from",
        type: "address",
      },
      {
        internalType: "address",
        name: "to",
        type: "address",
      },
      {
        internalType: "uint256",
        name: "amount",
        type: "uint256",
      },
    ],
    name: "transferFrom",
    outputs: [
      {
        internalType: "bool",
        name: "",
        type: "bool",
      },
    ],
    stateMutability: "nonpayable",
    type: "function",
  },
];
export const vendorABI = [
  {
    inputs: [
      {
        internalType: "address",
        name: "tokenAddress",
        type: "address",
      },
    ],
    stateMutability: "nonpayable",
    type: "constructor",
  },
  {
    anonymous: false,
    inputs: [
      {
        indexed: false,
        internalType: "address",
        name: "buyer",
        type: "address",
      },
      {
        indexed: false,
        internalType: "uint256",
        name: "amountOfETH",
        type: "uint256",
      },
      {
        indexed: false,
        internalType: "uint256",
        name: "amountOfTokens",
        type: "uint256",
      },
    ],
    name: "BuyTokens",
    type: "event",
  },
  {
    anonymous: false,
    inputs: [
      {
        indexed: true,
        internalType: "address",
        name: "previousOwner",
        type: "address",
      },
      {
        indexed: true,
        internalType: "address",
        name: "newOwner",
        type: "address",
      },
    ],
    name: "OwnershipTransferred",
    type: "event",
  },
  {
    inputs: [],
    name: "buyTokens",
    outputs: [
      {
        internalType: "uint256",
        name: "tokenAmount",
        type: "uint256",
      },
    ],
    stateMutability: "payable",
    type: "function",
  },
  {
    inputs: [],
    name: "owner",
    outputs: [
      {
        internalType: "address",
        name: "",
        type: "address",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [],
    name: "renounceOwnership",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "uint256",
        name: "tokenAmountToSell",
        type: "uint256",
      },
    ],
    name: "sellTokens",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [],
    name: "tokensPerEth",
    outputs: [
      {
        internalType: "uint256",
        name: "",
        type: "uint256",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "newOwner",
        type: "address",
      },
    ],
    name: "transferOwnership",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [],
    name: "withdraw",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
];

認証に取り組んでいます

_app.jsフォルダの下のファイルに移動しpagesます。ここでは、アプリをで囲みますThirdwebProvider。これは、認証の実装に役立ちます。

次の_app.jsようになります。

import { ThirdwebProvider, ChainId } from "@thirdweb-dev/react";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
  return (
    <ThirdwebProvider desiredChainId={ChainId.Mumbai}>
      <Component {...pageProps} />
    </ThirdwebProvider>
  );
}
export default MyApp;

ここでは、desiredChainIdPolygonMumbaiのチェーンIDとしてを提供しています。次に、に移動してindex.js、次のコードをコピーします。

import { useAddress, useMetamask } from "@thirdweb-dev/react";
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";
import { useEffect } from "react";
import { useRouter } from "next/router";
export default function Home() {
  const connectWithMetamask = useMetamask();
  const router = useRouter();
  const address = useAddress();
  useEffect(() => {
    if (address) router.replace("/exchange");
  }, [address]);
  return (
    <div>
      <Head>
        <title>Exchange TEST tokens</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div className="home__container">
        <h1>Sign in to exchange</h1>
        <button className="home__button" onClick={connectWithMetamask}>
          Sign in using MetaMask
        </button>
      </div>
    </div>
  );
}

上記では、Thirdwebのフックを使用して、ボタンを使用して認証を実行し、ユーザーのウォレットのアドレスを追跡しています。また、ユーザーがで認証されているかどうかを確認し、useEffect()それに応じてユーザーをリダイレクトしています。

exchange.js次に、ディレクトリに呼び出される新しいファイルを作成しますpages。ファイルには次のレイアウトがあります。

import { useAddress } from "@thirdweb-dev/react";
import Head from "next/head";
import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import Web3 from "web3";
import { tokenABI, vendorABI } from "../contracts";
const web3 = new Web3(Web3.givenProvider);
function Exchange() {
  const [tokens, setTokens] = useState();
  const [matic, setMatic] = useState(0);
  const address = useAddress();
  const router = useRouter();
  const purchase = async () => {};
  const sell = async () => {};
  useEffect(() => {
    if (!address) router.replace("/");
  }, [address]);
  useEffect(() => {
    setMatic(tokens / 100);
  }, [tokens]);
  return (
    <div>
      <Head>
        <title>Exchange TEST tokens</title>
      </Head>
      <div className="exchange__container">
        <h1>Purchase TEST Tokens</h1>
        <input
          type="number"
          placeholder="Amount of tokens"
          className="exchange__textBox"
          value={tokens}
          onChange={(e) => setTokens(e.target.value)}
        />
        <div>MATIC equivalent: {matic}</div>
        <button className="exchange__button" onClick={purchase}>
          Purchase
        </button>
        <button className="exchange__button" onClick={sell}>
          Sell
        </button>
      </div>
    </div>
  );
}
export default Exchange;

上記のコードは、ユーザーが認証されているかどうかを確認し、トークンを購入するために必要なMATICの数を計算しています。コードは次のような結果を提供する必要があります。

 

トークンの数を入力すると、必要なMATICの量が更新されます。また、コンポーネントの前にパッケージを初期化したweb3ので、コントラクトベースの操作を使用できるようになりました。

購入および販売機能の実装

それでは、売買の実際の機能に取り組みましょう。上記のコードには、purchase()関数のコードはありません。そのためのコードは次のとおりです。

const purchase = async () => {
  try {
    const accounts = await web3.eth.getAccounts();
    const vendor = new web3.eth.Contract(
      vendorABI,
      process.env.NEXT_PUBLIC_VENDOR_CONTRACT_ADDRESS
    );
    const request = await vendor.methods.buyTokens().send({
      from: accounts[0],
      value: web3.utils.toWei(matic.toString(), "ether"),
    });
    alert("You have successfully purchased TEST tokens!");
    console.log(request);
  } catch (err) {
    console.error(err);
    alert("Error purchasing tokens");
  }
};

上記のコードでは、最初にMetaMaskに接続されたアカウントを取得します。次に、コンストラクターのパラメーターにコントラクトABIとアドレスを指定して、ベンダーコントラクトを初期化します。

次に、コントラクトの関数を呼び出し、buyTokens()要求とともに必要な量のMATICを送信します。これで、トークンを購入しようとすると、MetaMask認証がポップアップ表示され、それを受け入れると、すぐにMetaMaskアセットにトークンが表示されます。

のコードsell()は次のとおりです。

const sell = async () => {
  try {
    const accounts = await web3.eth.getAccounts();
    const tokenContract = new web3.eth.Contract(
      tokenABI,
      process.env.NEXT_PUBLIC_TOKEN_CONTRACT_ADDRESS
    );
    // Approve the contract to spend the tokens
    let request = await tokenContract.methods
      .approve(
        process.env.NEXT_PUBLIC_VENDOR_CONTRACT_ADDRESS,
        web3.utils.toWei(tokens, "ether")
      )
      .send({
        from: accounts[0],
      });
    // Trigger the selling of tokens
    const vendor = new web3.eth.Contract(
      vendorABI,
      process.env.NEXT_PUBLIC_VENDOR_CONTRACT_ADDRESS
    );
    request = await vendor.methods
      .sellTokens(web3.utils.toWei(tokens, "ether"))
      .send({
        from: accounts[0],
      });
    alert("You have successfully sold TEST tokens!");
    console.log(request);
  } catch (err) {
    console.error(err);
    alert("Error selling tokens");
  }
};

上記のコードでは、トークン契約に連絡して、ベンダー契約を承認し、必要なトークンを私たちに代わって使用しています。次に、販売プロセスを開始するためにベンダー契約に連絡しています。その後、トークンはベンダーに転送され、MATICがユーザーのウォレットに送信されます。

結論

おめでとう!これで、独自のERC-20トークン交換プラットフォームが正常に作成されました。チュートリアルのどこかで行き詰まっていると感じた場合は、GitHubリポジトリをご覧ください。

新しいことを試して、いくつかの機能を追加し、プラットフォームをよりインタラクティブにすることをお勧めします。これにより、Solidityの概念をさらによく学ぶことができます。

 ソース:https ://blog.logrocket.com/build-erc-20-exchange-platform/

  #erc #token #blockchain 

ERC-20交換プラットフォームの構築方法
Saul  Alaniz

Saul Alaniz

1651285320

Cómo Construir Una Plataforma De Intercambio ERC-20

Las aplicaciones de tokens y NFT aumentan cada día. Traen muchas nuevas posibilidades a la mesa. Desde usarlos en juegos hasta crear su propia moneda virtual para un evento, puede hacer muchas cosas usando las tecnologías Web3 .

Hoy, vamos a crear una plataforma de intercambio de tokens, donde los usuarios pueden comprar y vender un token específico. El tipo de token que vamos a usar aquí es ERC-20 y los vamos a implementar en la red de Polygon Mumbai.

El enfoque que vamos a seguir es primero crear un contrato inteligente, que acuñará nuestros tokens, y luego otro contrato inteligente para facilitar su compra y venta. Luego usaremos ambos contratos inteligentes en nuestra aplicación Next.js para que los usuarios puedan comprar y vender.

requisitos previos

  • Conocimiento práctico de React
  • Conocimiento práctico de Next.js
  • Conocimiento práctico de Solidity
  • Node.js instalado
  • Un editor de código: prefiero Visual Studio Code
  • Extensión MetaMask instalada con al menos una billetera
  • MetaMask conectado a la red de prueba Polygon Mumbai

Si siente que está atascado en el tutorial, no dude en consultar el repositorio de GitHub .

Crear el contrato inteligente de token

El contrato inteligente de tokens nos ayudará a acuñar tokens. Abra Remix IDE y cree un nuevo archivo llamado TestToken.soldebajo de la contractscarpeta. Use el siguiente código en el archivo:

// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract TestToken is ERC20 {
    constructor() ERC20("TestToken", "TEST"){
        _mint(msg.sender, 10000 * 10 ** 18);
    }
}

En el código anterior, usamos plantillas de contrato de OpenZeppelin para crear un token ERC-20. Estas plantillas nos brindan las funciones mínimas requeridas por los tokens ERC-20, y son seguras y optimizadas, por lo que no debe preocuparse por reforzar la seguridad de su token. En el constructor de nuestro contrato, especificamos el nombre y el símbolo de nuestro token y acuñamos 10,000 tokens en la billetera del creador del contrato.

Ahora, compile el contrato presionando Ctrl + S (o Cmd + S para Mac). Asegúrese de haber conectado MetaMask a Polygon Mumbai. Vaya a la pestaña Implementar en la barra lateral y configure el entorno en Inyectado Web3 , para que Remix IDE use MetaMask para implementar su contrato.

Asegúrate de que tus otras configuraciones sean similares a esta:

 

Después de verificar todas las configuraciones, haga clic en Implementar . Esto debería abrir la ventana emergente de autorización de MetaMask. Haga clic en Confirmar .

Si no tiene fondos en su billetera de Polygon Mumbai, puede obtener algunos a través de un faucet . Después de confirmar la transacción, espere unos segundos hasta que vea su contrato desplegado en la barra lateral izquierda.

Copie la dirección del contrato, ya que la necesitaremos al configurar el contrato del proveedor.

Crear el contrato de proveedor

Cree un nuevo archivo llamado TestTokenVendor.soldebajo de la contractscarpeta. Utilice el siguiente código para el contrato:

// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.4;
import "./TestToken.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract Vendor is Ownable {
  TestToken yourToken;
  uint256 public tokensPerMatic = 100;
  event BuyTokens(address buyer, uint256 amountOfMATIC, uint256 amountOfTokens);
  constructor(address tokenAddress) {
    yourToken = TestToken(tokenAddress);
  }

  function buyTokens() public payable returns (uint256 tokenAmount) {
    require(msg.value > 0, "You need to send some MATIC to proceed");
    uint256 amountToBuy = msg.value * tokensPerMatic;

    uint256 vendorBalance = yourToken.balanceOf(address(this));
    require(vendorBalance >= amountToBuy, "Vendor has insufficient tokens");

    (bool sent) = yourToken.transfer(msg.sender, amountToBuy);
    require(sent, "Failed to transfer token to user");

    emit BuyTokens(msg.sender, msg.value, amountToBuy);
    return amountToBuy;
  }
  function sellTokens(uint256 tokenAmountToSell) public {

    require(tokenAmountToSell > 0, "Specify an amount of token greater than zero");

    uint256 userBalance = yourToken.balanceOf(msg.sender);
    require(userBalance >= tokenAmountToSell, "You have insufficient tokens");

    uint256 amountOfMATICToTransfer = tokenAmountToSell / tokensPerMatic;
    uint256 ownerMATICBalance = address(this).balance;
    require(ownerMATICBalance >= amountOfMATICToTransfer, "Vendor has insufficient funds");
    (bool sent) = yourToken.transferFrom(msg.sender, address(this), tokenAmountToSell);
    require(sent, "Failed to transfer tokens from user to vendor");

    (sent,) = msg.sender.call{value: amountOfMATICToTransfer}("");
    require(sent, "Failed to send MATIC to the user");
  }

  function withdraw() public onlyOwner {
    uint256 ownerBalance = address(this).balance;
    require(ownerBalance > 0, "No MATIC present in Vendor");
    (bool sent,) = msg.sender.call{value: address(this).balance}("");
    require(sent, "Failed to withdraw");
  }
}

En el código anterior, estamos creando un Ownablecontrato, que es una plantilla de OpenZeppelin. Esto significa que podemos transferir la propiedad de este contrato a alguna otra billetera, si así lo deseamos.

Estamos importando nuestro contrato de token y brindándole la dirección a través del constructor (proporcionaremos la dirección como argumento al implementar el contrato). También estamos estableciendo un precio general del token dentro de la tokensPerMaticvariable. Este valor se utilizará para calcular la cantidad de tokens a comprar en función de la cantidad de MATIC enviada al contrato.

En la buyTokens()función, primero verificamos si se envió algún MATIC y si el contrato del proveedor tiene suficientes tokens en el saldo o no. Luego, estamos yourToken.transfer()enviando nuestros tokens desde el contrato del proveedor a la billetera que envió la llamada del contrato. Finalmente, estamos emitiendo un evento y devolviendo la cantidad de tokens comprados.

La sellTokens()función no es tan sencilla como la buyTokens()función. Verificamos si el usuario tiene la cantidad especificada de tokens o no, calculamos la cantidad de MATIC que se devolverá al usuario y verificamos si el contrato del proveedor tiene una cantidad suficiente de MATIC en el saldo.

Sin embargo, aquí viene la parte divertida: no podemos simplemente transferir tokens de la billetera del usuario al saldo de nuestro contrato de proveedor. Primero debemos solicitar la aprobación del usuario para permitirnos administrar sus tokens y enviarnos estos tokens. Este método es seguro porque necesitamos establecer el límite de la cantidad de tokens para los que solicitamos aprobación. La autorización está en MetaMask y el usuario puede ver claramente qué cantidad de tokens puede manejar el contrato.

Este procedimiento de aprobación tiene lugar en el front-end (de una aplicación Next.js, en este caso). Una vez que se otorga la aprobación, realizamos la transferFrom()función de transferir los fondos del usuario a la billetera del contrato del proveedor. Y finalmente, enviamos MATIC al usuario.

La withdraw()función sólo puede ser ejecutada por el titular del contrato. Esta función le permite enviar todo el MATIC almacenado en el contrato inteligente a la billetera del propietario.

Finalmente, implemente el contrato y pase la dirección del contrato del token como parámetro.

Configuración del contrato inteligente del proveedor

Ahora que hemos implementado el contrato inteligente del proveedor, debemos enviarle algunos tokens para que funcione.

Abra el contrato de token en la pestaña Implementar . En la sección de transferencia , pegue la dirección del contrato del proveedor y la cantidad total de tokens (que se pueden obtener haciendo clic en totalSupply.

Haga clic en transacción para aprobar la transacción de MetaMask y todos los tokens se enviarán desde su billetera al saldo del contrato del proveedor. Ahora su contrato de proveedor puede facilitar la compra y venta de tokens.

Ahora, vaya a la pestaña de compilación y copie el ABI de los dos contratos (así como las direcciones del token y del proveedor), ya que los necesitaremos en nuestro archivo Next.js.

Configurando nuestra aplicación Next.js

Navegue a un directorio seguro y ejecute el siguiente comando en la terminal para crear su aplicación Next.js:

npx create-next-app erc20-exchange-plataforma

Úselo --use-npmal final si desea instalar dependencias usando npm( create-next-apprecientemente se ha predeterminado a yarn). Navegue a la carpeta del proyecto y ejecute el siguiente comando para instalar algunas dependencias requeridas:

#npm
npm install @thirdweb-dev/sdk @thirdweb-dev/react web3
#yarn
yarn add @thirdweb-dev/sdk @thirdweb-dev/react web3

Estamos instalando @thirdweb-dev/sdkpara ayudar con la autenticación y conectando nuestra aplicación a MetaMask. De esta forma, no dedicamos mucho tiempo a la autenticación.

También estamos instalando @thirdweb-dev/reactpara proporcionarnos Hooks que funcionarán @thirdweb-dev/sdkpara brindarnos información útil sobre el estado del usuario. Estamos instalando web3para interactuar con nuestros tokens y contratos inteligentes de proveedores.

Como no cubriremos el estilo en este tutorial, lo dejaré aquí sin centrarme demasiado en los detalles. Abra globals.cssdebajo de la stylescarpeta y use los siguientes estilos:

html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
    Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a {
  color: inherit;
  text-decoration: none;
}
* {
  box-sizing: border-box;
}
.home__container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
}
.home__button {
  background-color: #2ecc71;
  border: none;
  border-radius: 5px;
  font-size: 20px;
  padding: 15px;
  margin: 10px;
  cursor: pointer;
}
.exchange__container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
}
.exchange__textBox {
  width: 300px;
  height: 50px;
  border: 1px solid #c4c4c4;
  border-radius: 5px;
  font-size: 20px;
  padding: 15px;
  margin: 10px;
  cursor: pointer;
}
.exchange__button {
  width: 300px;
  height: 50px;
  border: 1px solid #2ecc71;
  background-color: #2ecc71;
  border-radius: 5px;
  font-size: 20px;
  padding: 15px;
  margin: 10px;
  cursor: pointer;
}

Configuración de variables de entorno y otros datos

Cree un nuevo archivo llamado .env.localen el directorio del proyecto, este contendrá nuestras direcciones de contrato:

NEXT_PUBLIC_TOKEN_CONTRACT_ADDRESS=(token address here)
NEXT_PUBLIC_VENDOR_CONTRACT_ADDRESS=(vendor address here)

Ahora, cree un nuevo archivo llamado contracts.jsen el directorio del proyecto. En este archivo, guardaremos y exportaremos las ABI del contrato para ambos contratos:

export const tokenABI = [
  {
    inputs: [],
    stateMutability: "nonpayable",
    type: "constructor",
  },
  {
    anonymous: false,
    inputs: [
      {
        indexed: true,
        internalType: "address",
        name: "owner",
        type: "address",
      },
      {
        indexed: true,
        internalType: "address",
        name: "spender",
        type: "address",
      },
      {
        indexed: false,
        internalType: "uint256",
        name: "value",
        type: "uint256",
      },
    ],
    name: "Approval",
    type: "event",
  },
  {
    anonymous: false,
    inputs: [
      {
        indexed: true,
        internalType: "address",
        name: "from",
        type: "address",
      },
      {
        indexed: true,
        internalType: "address",
        name: "to",
        type: "address",
      },
      {
        indexed: false,
        internalType: "uint256",
        name: "value",
        type: "uint256",
      },
    ],
    name: "Transfer",
    type: "event",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "owner",
        type: "address",
      },
      {
        internalType: "address",
        name: "spender",
        type: "address",
      },
    ],
    name: "allowance",
    outputs: [
      {
        internalType: "uint256",
        name: "",
        type: "uint256",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "spender",
        type: "address",
      },
      {
        internalType: "uint256",
        name: "amount",
        type: "uint256",
      },
    ],
    name: "approve",
    outputs: [
      {
        internalType: "bool",
        name: "",
        type: "bool",
      },
    ],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "account",
        type: "address",
      },
    ],
    name: "balanceOf",
    outputs: [
      {
        internalType: "uint256",
        name: "",
        type: "uint256",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [],
    name: "decimals",
    outputs: [
      {
        internalType: "uint8",
        name: "",
        type: "uint8",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "spender",
        type: "address",
      },
      {
        internalType: "uint256",
        name: "subtractedValue",
        type: "uint256",
      },
    ],
    name: "decreaseAllowance",
    outputs: [
      {
        internalType: "bool",
        name: "",
        type: "bool",
      },
    ],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "spender",
        type: "address",
      },
      {
        internalType: "uint256",
        name: "addedValue",
        type: "uint256",
      },
    ],
    name: "increaseAllowance",
    outputs: [
      {
        internalType: "bool",
        name: "",
        type: "bool",
      },
    ],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [],
    name: "name",
    outputs: [
      {
        internalType: "string",
        name: "",
        type: "string",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [],
    name: "symbol",
    outputs: [
      {
        internalType: "string",
        name: "",
        type: "string",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [],
    name: "totalSupply",
    outputs: [
      {
        internalType: "uint256",
        name: "",
        type: "uint256",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "to",
        type: "address",
      },
      {
        internalType: "uint256",
        name: "amount",
        type: "uint256",
      },
    ],
    name: "transfer",
    outputs: [
      {
        internalType: "bool",
        name: "",
        type: "bool",
      },
    ],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "from",
        type: "address",
      },
      {
        internalType: "address",
        name: "to",
        type: "address",
      },
      {
        internalType: "uint256",
        name: "amount",
        type: "uint256",
      },
    ],
    name: "transferFrom",
    outputs: [
      {
        internalType: "bool",
        name: "",
        type: "bool",
      },
    ],
    stateMutability: "nonpayable",
    type: "function",
  },
];
export const vendorABI = [
  {
    inputs: [
      {
        internalType: "address",
        name: "tokenAddress",
        type: "address",
      },
    ],
    stateMutability: "nonpayable",
    type: "constructor",
  },
  {
    anonymous: false,
    inputs: [
      {
        indexed: false,
        internalType: "address",
        name: "buyer",
        type: "address",
      },
      {
        indexed: false,
        internalType: "uint256",
        name: "amountOfETH",
        type: "uint256",
      },
      {
        indexed: false,
        internalType: "uint256",
        name: "amountOfTokens",
        type: "uint256",
      },
    ],
    name: "BuyTokens",
    type: "event",
  },
  {
    anonymous: false,
    inputs: [
      {
        indexed: true,
        internalType: "address",
        name: "previousOwner",
        type: "address",
      },
      {
        indexed: true,
        internalType: "address",
        name: "newOwner",
        type: "address",
      },
    ],
    name: "OwnershipTransferred",
    type: "event",
  },
  {
    inputs: [],
    name: "buyTokens",
    outputs: [
      {
        internalType: "uint256",
        name: "tokenAmount",
        type: "uint256",
      },
    ],
    stateMutability: "payable",
    type: "function",
  },
  {
    inputs: [],
    name: "owner",
    outputs: [
      {
        internalType: "address",
        name: "",
        type: "address",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [],
    name: "renounceOwnership",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "uint256",
        name: "tokenAmountToSell",
        type: "uint256",
      },
    ],
    name: "sellTokens",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [],
    name: "tokensPerEth",
    outputs: [
      {
        internalType: "uint256",
        name: "",
        type: "uint256",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "newOwner",
        type: "address",
      },
    ],
    name: "transferOwnership",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [],
    name: "withdraw",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
];

Trabajando en la autenticación

Ir al _app.jsarchivo debajo de la pagescarpeta. Aquí, incluiremos nuestra aplicación con ThirdwebProvider, lo que nos ayudará a implementar la autenticación.

Tu _app.jsdebería verse así:

import { ThirdwebProvider, ChainId } from "@thirdweb-dev/react";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
  return (
    <ThirdwebProvider desiredChainId={ChainId.Mumbai}>
      <Component {...pageProps} />
    </ThirdwebProvider>
  );
}
export default MyApp;

Aquí, proporcionamos el desiredChainIdID de cadena de Polygon Mumbai. Ahora ve a index.jsy copia el siguiente código en él:

import { useAddress, useMetamask } from "@thirdweb-dev/react";
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";
import { useEffect } from "react";
import { useRouter } from "next/router";
export default function Home() {
  const connectWithMetamask = useMetamask();
  const router = useRouter();
  const address = useAddress();
  useEffect(() => {
    if (address) router.replace("/exchange");
  }, [address]);
  return (
    <div>
      <Head>
        <title>Exchange TEST tokens</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div className="home__container">
        <h1>Sign in to exchange</h1>
        <button className="home__button" onClick={connectWithMetamask}>
          Sign in using MetaMask
        </button>
      </div>
    </div>
  );
}

Arriba, estamos usando Hooks de Thirdweb para realizar la autenticación mediante un botón y realizar un seguimiento de la dirección de la billetera del usuario. También estamos comprobando si el usuario está autenticado en un useEffect()correo electrónico y redireccionando al usuario en consecuencia.

Ahora cree un nuevo archivo llamado exchange.jsen el pagesdirectorio. Tener el siguiente diseño en el archivo:

import { useAddress } from "@thirdweb-dev/react";
import Head from "next/head";
import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import Web3 from "web3";
import { tokenABI, vendorABI } from "../contracts";
const web3 = new Web3(Web3.givenProvider);
function Exchange() {
  const [tokens, setTokens] = useState();
  const [matic, setMatic] = useState(0);
  const address = useAddress();
  const router = useRouter();
  const purchase = async () => {};
  const sell = async () => {};
  useEffect(() => {
    if (!address) router.replace("/");
  }, [address]);
  useEffect(() => {
    setMatic(tokens / 100);
  }, [tokens]);
  return (
    <div>
      <Head>
        <title>Exchange TEST tokens</title>
      </Head>
      <div className="exchange__container">
        <h1>Purchase TEST Tokens</h1>
        <input
          type="number"
          placeholder="Amount of tokens"
          className="exchange__textBox"
          value={tokens}
          onChange={(e) => setTokens(e.target.value)}
        />
        <div>MATIC equivalent: {matic}</div>
        <button className="exchange__button" onClick={purchase}>
          Purchase
        </button>
        <button className="exchange__button" onClick={sell}>
          Sell
        </button>
      </div>
    </div>
  );
}
export default Exchange;

El código anterior verifica si el usuario está autenticado o no, y calcula cuántos MATIC se requieren para comprar tokens. El código debe proporcionar un resultado de la siguiente manera:

 

A medida que ingresa la cantidad de tokens, se actualizará la cantidad de MATIC requerida. También inicializamos el web3paquete antes que el componente, por lo que ahora podemos usar operaciones basadas en contratos.

Implementación de la funcionalidad de compra y venta.

Ahora, trabajemos en la funcionalidad real de compra y venta. En el código anterior, no tenemos código para la purchase()función. Aquí está el código para ello:

const purchase = async () => {
  try {
    const accounts = await web3.eth.getAccounts();
    const vendor = new web3.eth.Contract(
      vendorABI,
      process.env.NEXT_PUBLIC_VENDOR_CONTRACT_ADDRESS
    );
    const request = await vendor.methods.buyTokens().send({
      from: accounts[0],
      value: web3.utils.toWei(matic.toString(), "ether"),
    });
    alert("You have successfully purchased TEST tokens!");
    console.log(request);
  } catch (err) {
    console.error(err);
    alert("Error purchasing tokens");
  }
};

En el código anterior, primero conectamos las cuentas con MetaMask. Luego, inicializamos nuestro contrato de proveedor proporcionando el contrato ABI y la dirección en los parámetros del constructor.

Luego, llamamos a la buyTokens()función de nuestro contrato y enviamos la cantidad requerida de MATIC junto con la solicitud. Ahora, cuando intente comprar tokens, debería aparecer la autorización de MetaMask y, al aceptarla, debería ver los tokens en sus activos de MetaMask en breve:

El código para sell()es el siguiente:

const sell = async () => {
  try {
    const accounts = await web3.eth.getAccounts();
    const tokenContract = new web3.eth.Contract(
      tokenABI,
      process.env.NEXT_PUBLIC_TOKEN_CONTRACT_ADDRESS
    );
    // Approve the contract to spend the tokens
    let request = await tokenContract.methods
      .approve(
        process.env.NEXT_PUBLIC_VENDOR_CONTRACT_ADDRESS,
        web3.utils.toWei(tokens, "ether")
      )
      .send({
        from: accounts[0],
      });
    // Trigger the selling of tokens
    const vendor = new web3.eth.Contract(
      vendorABI,
      process.env.NEXT_PUBLIC_VENDOR_CONTRACT_ADDRESS
    );
    request = await vendor.methods
      .sellTokens(web3.utils.toWei(tokens, "ether"))
      .send({
        from: accounts[0],
      });
    alert("You have successfully sold TEST tokens!");
    console.log(request);
  } catch (err) {
    console.error(err);
    alert("Error selling tokens");
  }
};

En el código anterior, nos comunicamos con el contrato de token para aprobar el contrato del proveedor para gastar los tokens requeridos en nuestro nombre. Luego, nos ponemos en contacto con nuestro contrato de proveedor para iniciar el proceso de venta. Luego, los tokens se transferirán al proveedor y MATIC se enviará a la billetera del usuario.

Conclusión

¡Felicidades! Ha creado con éxito su propia plataforma de intercambio de tokens ERC-20. Aquí está el repositorio de GitHub si siente que está atascado en alguna parte del tutorial.

Sugiero probar algo nuevo, agregar algunas funciones y hacer que la plataforma sea más interactiva, esto lo ayudará a aprender aún mejor los conceptos de Solidity.

 Fuente: https://blog.logrocket.com/build-erc-20-exchange-platform/

 #erc #token #blockchain 

Cómo Construir Una Plataforma De Intercambio ERC-20
坂本  健一

坂本 健一

1651240800

ERC-1404トークン契約とは何ですか?

Solidity ERC-1404トークン契約標準は、参加者が証券業界のコンプライアンス要件を満たすことができるように設計されています。この契約基準は、コーポレートガバナンス、銀行、および証券法を考慮するのに十分な柔軟性があります。その結果、企業は、さまざまな法域のさまざまな規制に準拠できるトークンを設計および開発できます。

ERC-1404トークン契約とは何ですか?

ERC-1404トークン契約には、 ERC-20トークン契約と同じ機能と利点がすべて備わっていますが、発行者が規制上の制限を適用できるようにいくつかの機能が強化されています。新しい契約標準では、転送の検出制限と制限の理由を示すメッセージが追加されています。この標準契約により、作成者は、管轄区域で準拠を維持するために必要なルールを実装できます。

ERC-20トークン標準は、以下にリストされている基本機能を提供します。これらの機能は標準であり、すべてのERC-20トークンに存在します。

contract ERC20 {
  function totalSupply() public view returns (uint256);
  function balanceOf(address who) public view returns (uint256);
  function transfer(address to, uint256 value) public returns (bool);
  function allowance(address owner, address spender) public view returns (uint256);
  function transferFrom(address from, address to, uint256 value) public returns (bool);
  function approve(address spender, uint256 value) public returns (bool);
  event Approval(address indexed owner, address indexed spender, uint256 value);
  event Transfer(address indexed from, address indexed to, uint256 value);
}

 

設計上、ERC-1404トークン標準はERC-20トークン標準と完全な下位互換性があります。ERC-1404標準は、2つの機能を追加することにより、ERC-20のインターフェースに基づいて構築されています。

  • detectTransferRestriction –制限コードを返します(0は成功のために予約されています)
  • messageForTransferRestriction –制限コードの詳細を示す人間が読めるメッセージ文字列を返します
contract ERC1404 is ERC20 {
  function detectTransferRestriction (address from, address to, uint256 value) public view returns (uint8);
  function messageForTransferRestriction (uint8 restrictionCode) public view returns (string);
}

 

これらの2つの機能により、発行者はスマートコントラクト内で転送制限を適用できます。トークンの発行者は、これら2つの関数の動作を実装して、柔軟性を高めることができます。detectTransferRestrictionは、トークンのtransferおよびtransferFrom関数内で評価する必要があります。0detectTransferRestrictionが戻り値を返す場合は、トランザクション以外の値 を元に戻す必要があります。

これらの2つの関数は、証券トークンに必要なコンプライアンスを追加し、多くの状況で役立つ可能性があります。例として、ERC-1404コントラクトは決定するロジックを持つことができます。

  • ホワイトリストに登録されたアカウント
  • ロックアップ期間

ERC-1404契約の制限の例については、このGithubページにアクセスしてください。さらに、以下の一般的な転送制限パターンを確認してください。

  1. アカウント所有率
  2. アカウントの数
  3. アカウントのホワイトリスト
  4. トークンの分割可能性

実世界の例

INX Crypto Currency Exchangeは、ERC-1404トークン標準を使用してイーサリアムブロックチェーン上に証券トークンを作成しました。以下またはEtherscan.ioで彼らの契約を読んでください。制限がどのように評価および実装されるかを学ぶために、detectTransferRestriction関数に焦点を当てます。

/**
 *Submitted for verification at Etherscan.io on 2020-02-03
*/

// File: contracts/1404/IERC1404.sol

pragma solidity 0.5.8;

interface IERC1404 {
    /// @notice Detects if a transfer will be reverted and if so returns an appropriate reference code
    /// @param from Sending address
    /// @param to Receiving address
    /// @param value Amount of tokens being transferred
    /// @return Code by which to reference message for rejection reasoning
    /// @dev Overwrite with your custom transfer restriction logic
    function detectTransferRestriction (address from, address to, uint256 value) external view returns (uint8);

    /// @notice Detects if a transferFrom will be reverted and if so returns an appropriate reference code
    /// @param sender Transaction sending address
    /// @param from Source of funds address
    /// @param to Receiving address
    /// @param value Amount of tokens being transferred
    /// @return Code by which to reference message for rejection reasoning
    /// @dev Overwrite with your custom transfer restriction logic
    function detectTransferFromRestriction (address sender, address from, address to, uint256 value) external view returns (uint8);

    /// @notice Returns a human-readable message for a given restriction code
    /// @param restrictionCode Identifier for looking up a message
    /// @return Text showing the restriction's reasoning
    /// @dev Overwrite with your custom message and restrictionCode handling
    function messageForTransferRestriction (uint8 restrictionCode) external view returns (string memory);
}

interface IERC1404getSuccessCode {
    /// @notice Return the uint256 that represents the SUCCESS_CODE
    /// @return uint256 SUCCESS_CODE
    function getSuccessCode () external view returns (uint256);
}

/**
 * @title IERC1404Success
 * @dev Combines IERC1404 and IERC1404getSuccessCode interfaces, to be implemented by the TransferRestrictions contract
 */
contract IERC1404Success is IERC1404getSuccessCode, IERC1404 {
}

// File: contracts/1404/IERC1404Validators.sol

pragma solidity 0.5.8;

/**
 * @title IERC1404Validators
 * @dev Interfaces implemented by the token contract to be called by the TransferRestrictions contract
 */
interface IERC1404Validators {
    /// @notice Returns the token balance for an account
    /// @param account The address to get the token balance of
    /// @return uint256 representing the token balance for the account
    function balanceOf (address account) external view returns (uint256);

    /// @notice Returns a boolean indicating the paused state of the contract
    /// @return true if contract is paused, false if unpaused
    function paused () external view returns (bool);

    /// @notice Determine if sender and receiver are whitelisted, return true if both accounts are whitelisted
    /// @param from The address sending tokens.
    /// @param to The address receiving tokens.
    /// @return true if both accounts are whitelisted, false if not
    function checkWhitelists (address from, address to) external view returns (bool);

    /// @notice Determine if a users tokens are locked preventing a transfer
    /// @param _address the address to retrieve the data from
    /// @param amount the amount to send
    /// @param balance the token balance of the sending account
    /// @return true if user has sufficient unlocked token to transfer the requested amount, false if not
    function checkTimelock (address _address, uint256 amount, uint256 balance) external view returns (bool);
}

// File: @openzeppelin/contracts/access/Roles.sol

pragma solidity ^0.5.0;

/**
 * @title Roles
 * @dev Library for managing addresses assigned to a Role.
 */
library Roles {
    struct Role {
        mapping (address => bool) bearer;
    }

    /**
     * @dev Give an account access to this role.
     */
    function add(Role storage role, address account) internal {
        require(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;
    }

    /**
     * @dev Remove an account's access to this role.
     */
    function remove(Role storage role, address account) internal {
        require(has(role, account), "Roles: account does not have role");
        role.bearer[account] = false;
    }

    /**
     * @dev Check if an account has this role.
     * @return bool
     */
    function has(Role storage role, address account) internal view returns (bool) {
        require(account != address(0), "Roles: account is the zero address");
        return role.bearer[account];
    }
}

// File: contracts/roles/OwnerRole.sol

pragma solidity 0.5.8;


contract OwnerRole {
    using Roles for Roles.Role;

    event OwnerAdded(address indexed addedOwner, address indexed addedBy);
    event OwnerRemoved(address indexed removedOwner, address indexed removedBy);

    Roles.Role private _owners;

    modifier onlyOwner() {
        require(isOwner(msg.sender), "OwnerRole: caller does not have the Owner role");
        _;
    }

    function isOwner(address account) public view returns (bool) {
        return _owners.has(account);
    }

    function addOwner(address account) public onlyOwner {
        _addOwner(account);
    }

    function removeOwner(address account) public onlyOwner {
        require(msg.sender != account, "Owners cannot remove themselves as owner");
        _removeOwner(account);
    }

    function _addOwner(address account) internal {
        _owners.add(account);
        emit OwnerAdded(account, msg.sender);
    }

    function _removeOwner(address account) internal {
        _owners.remove(account);
        emit OwnerRemoved(account, msg.sender);
    }
}

// File: @openzeppelin/contracts/GSN/Context.sol

pragma solidity ^0.5.0;

/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with GSN meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
contract Context {
    // Empty internal constructor, to prevent people from mistakenly deploying
    // an instance of this contract, which should be used via inheritance.
    constructor () internal { }
    // solhint-disable-previous-line no-empty-blocks

    function _msgSender() internal view returns (address payable) {
        return msg.sender;
    }

    function _msgData() internal view returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

// File: @openzeppelin/contracts/token/ERC20/IERC20.sol

pragma solidity ^0.5.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
 * the optional functions; to access them see {ERC20Detailed}.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

// File: @openzeppelin/contracts/math/SafeMath.sol

pragma solidity ^0.5.0;

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     *
     * _Available since v2.4.0._
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     *
     * _Available since v2.4.0._
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     *
     * _Available since v2.4.0._
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}

// File: @openzeppelin/contracts/token/ERC20/ERC20.sol

pragma solidity ^0.5.0;




/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20Mintable}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of ERC20 applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20 {
    using SafeMath for uint256;

    mapping (address => uint256) private _balances;

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

    uint256 private _totalSupply;

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20};
     *
     * Requirements:
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for `sender`'s tokens of at least
     * `amount`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal {
        require(account != address(0), "ERC20: mint to the zero address");

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

     /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal {
        require(account != address(0), "ERC20: burn from the zero address");

        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
     *
     * This is internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal {
        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);
    }

    /**
     * @dev Destroys `amount` tokens from `account`.`amount` is then deducted
     * from the caller's allowance.
     *
     * See {_burn} and {_approve}.
     */
    function _burnFrom(address account, uint256 amount) internal {
        _burn(account, amount);
        _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
    }
}

// File: contracts/roles/RevokerRole.sol

pragma solidity 0.5.8;


contract RevokerRole is OwnerRole {

    event RevokerAdded(address indexed addedRevoker, address indexed addedBy);
    event RevokerRemoved(address indexed removedRevoker, address indexed removedBy);

    Roles.Role private _revokers;

    modifier onlyRevoker() {
        require(isRevoker(msg.sender), "RevokerRole: caller does not have the Revoker role");
        _;
    }

    function isRevoker(address account) public view returns (bool) {
        return _revokers.has(account);
    }

    function addRevoker(address account) public onlyOwner {
        _addRevoker(account);
    }

    function removeRevoker(address account) public onlyOwner {
        _removeRevoker(account);
    }

    function _addRevoker(address account) internal {
        _revokers.add(account);
        emit RevokerAdded(account, msg.sender);
    }

    function _removeRevoker(address account) internal {
        _revokers.remove(account);
        emit RevokerRemoved(account, msg.sender);
    }
}

// File: contracts/capabilities/Revocable.sol

pragma solidity 0.5.8;



/**
 * Allows an administrator to move tokens from a target account to their own.
 */
contract Revocable is ERC20, RevokerRole {

  event Revoke(address indexed revoker, address indexed from, uint256 amount);

  function revoke(
    address _from,
    uint256 _amount
  )
    public
    onlyRevoker
    returns (bool)
  {
    ERC20._transfer(_from, msg.sender, _amount);
    emit Revoke(msg.sender, _from, _amount);
    return true;
  }
}

// File: contracts/roles/WhitelisterRole.sol

pragma solidity 0.5.8;


contract WhitelisterRole is OwnerRole {

    event WhitelisterAdded(address indexed addedWhitelister, address indexed addedBy);
    event WhitelisterRemoved(address indexed removedWhitelister, address indexed removedBy);

    Roles.Role private _whitelisters;

    modifier onlyWhitelister() {
        require(isWhitelister(msg.sender), "WhitelisterRole: caller does not have the Whitelister role");
        _;
    }

    function isWhitelister(address account) public view returns (bool) {
        return _whitelisters.has(account);
    }

    function addWhitelister(address account) public onlyOwner {
        _addWhitelister(account);
    }

    function removeWhitelister(address account) public onlyOwner {
        _removeWhitelister(account);
    }

    function _addWhitelister(address account) internal {
        _whitelisters.add(account);
        emit WhitelisterAdded(account, msg.sender);
    }

    function _removeWhitelister(address account) internal {
        _whitelisters.remove(account);
        emit WhitelisterRemoved(account, msg.sender);
    }
}

// File: contracts/capabilities/Whitelistable.sol

pragma solidity 0.5.8;


/**
 * @title Whitelistable
 * @dev Allows tracking whether addressess are allowed to hold tokens.
 */
contract Whitelistable is WhitelisterRole {

    event WhitelistUpdate(address _address, bool status, string data);

    // Tracks whether an address is whitelisted
    // data field can track any external field (like a hash of personal details)
    struct whiteListItem {
        bool status;
        string data;
    }

    // white list status
    mapping (address => whiteListItem) public whitelist;

    /**
    * @dev Set a white list address
    * @param to the address to be set
    * @param status the whitelisting status (true for yes, false for no)
    * @param data a string with data about the whitelisted address
    */
    function setWhitelist(address to, bool status, string memory data)  public onlyWhitelister returns(bool){
        whitelist[to] = whiteListItem(status, data);
        emit WhitelistUpdate(to, status, data);
        return true;
    }

    /**
    * @dev Get the status of the whitelist
    * @param _address the address to be check
    */
    function getWhitelistStatus(address _address) public view returns(bool){
        return whitelist[_address].status;
    }

    /**
    * @dev Get the data of and address in the whitelist
    * @param _address the address to retrieve the data from
    */
    function getWhitelistData(address _address) public view returns(string memory){
        return whitelist[_address].data;
    }

    /**
    * @dev Determine if sender and receiver are whitelisted, return true if both accounts are whitelisted
    * @param from The address sending tokens.
    * @param to The address receiving tokens.
    */
    function checkWhitelists(address from, address to) external view returns (bool) {
        return whitelist[from].status && whitelist[to].status;
    }
}

// File: contracts/roles/TimelockerRole.sol

pragma solidity 0.5.8;


contract TimelockerRole is OwnerRole {

    event TimelockerAdded(address indexed addedTimelocker, address indexed addedBy);
    event TimelockerRemoved(address indexed removedTimelocker, address indexed removedBy);

    Roles.Role private _timelockers;

    modifier onlyTimelocker() {
        require(isTimelocker(msg.sender), "TimelockerRole: caller does not have the Timelocker role");
        _;
    }

    function isTimelocker(address account) public view returns (bool) {
        return _timelockers.has(account);
    }

    function addTimelocker(address account) public onlyOwner {
        _addTimelocker(account);
    }

    function removeTimelocker(address account) public onlyOwner {
        _removeTimelocker(account);
    }

    function _addTimelocker(address account) internal {
        _timelockers.add(account);
        emit TimelockerAdded(account, msg.sender);
    }

    function _removeTimelocker(address account) internal {
        _timelockers.remove(account);
        emit TimelockerRemoved(account, msg.sender);
    }
}

// File: contracts/capabilities/Timelockable.sol

pragma solidity 0.5.8;



/**
 * @title INX Timelockable
 * @dev Lockup all or a portion of an accounts tokens until an expiration date
 */
contract Timelockable is TimelockerRole {

    using SafeMath for uint256;

    struct lockupItem {
        uint256 amount;
        uint256 releaseTime;
    }

    mapping (address => lockupItem) lockups;

    event AccountLock(address _address, uint256 amount, uint256 releaseTime);
    event AccountRelease(address _address, uint256 amount);


    /**
    * @dev lock address and amount and lock it, set the release time
    * @param _address the address to lock
    * @param amount the amount to lock
    * @param releaseTime of the locked amount (in seconds since the epoch)
    */
    function lock( address _address, uint256 amount, uint256 releaseTime) public onlyTimelocker returns (bool) {
        require(releaseTime > block.timestamp, "Release time needs to be in the future");
        require(_address != address(0), "Address must be valid for lockup");

        lockupItem memory _lockupItem = lockupItem(amount, releaseTime);
        lockups[_address] = _lockupItem;
        emit AccountLock(_address, amount, releaseTime);
        return true;
    }

    /**
    * @dev release locked amount
    * @param _address the address to retrieve the data from
    * @param amountToRelease the amount to check
    */
    function release( address _address, uint256 amountToRelease) public onlyTimelocker returns(bool) {
        require(_address != address(0), "Address must be valid for release");

        uint256 _lockedAmount = lockups[_address].amount;

        // nothing to release
        if(_lockedAmount == 0){
            emit AccountRelease(_address, 0);
            return true;
        }

        // extract release time for re-locking
        uint256 _releaseTime = lockups[_address].releaseTime;

        // delete the lock entry
        delete lockups[_address];

        if(_lockedAmount >= amountToRelease){
           uint256 newLockedAmount = _lockedAmount.sub(amountToRelease);

           // re-lock the new locked balance
           lock(_address, newLockedAmount, _releaseTime);
           emit AccountRelease(_address, amountToRelease);
           return true;
        } else {
            // if they requested to release more than the locked amount emit the event with the locked amount that has been released
            emit AccountRelease(_address, _lockedAmount);
            return true;
        }
    }

    /**
    * @dev return true if the given account has enough unlocked tokens to send the requested amount
    * @param _address the address to retrieve the data from
    * @param amount the amount to send
    * @param balance the token balance of the sending account
    */
    function checkTimelock(address _address, uint256 amount, uint256 balance) external view returns (bool) {
        // if the user does not have enough tokens to send regardless of lock return true here
        // the failure will still fail but this should make it explicit that the transfer failure is not
        // due to locked tokens but because of too low token balance
        if (balance < amount) {
            return true;
        }

        // get the sending addresses token balance that is not locked
        uint256 nonLockedAmount = balance.sub(lockups[_address].amount);

        // determine if the sending address has enough free tokens to send the entire amount
        bool notLocked = amount <= nonLockedAmount;

        // if the timelock is greater then the release time the time lock is expired
        bool timeLockExpired = block.timestamp > lockups[_address].releaseTime;

        // if the timelock is expired OR the requested amount is available the transfer is not locked
        if(timeLockExpired || notLocked){
            return true;

        // if the timelocked is not expired AND the requested amount is not available the tranfer is locked
        } else {
            return false;
        }
    }

    /**
    * @dev get address lockup info
    * @param _address the address to retrieve the data from
    * @return array of 2 uint256, release time (in seconds since the epoch) and amount (in INX)
    */
    function checkLockup(address _address) public view returns(uint256, uint256) {
        // copy lockup data into memory
        lockupItem memory _lockupItem = lockups[_address];

        return (_lockupItem.releaseTime, _lockupItem.amount);
    }
}

// File: contracts/roles/PauserRole.sol

pragma solidity 0.5.8;


contract PauserRole is OwnerRole {

    event PauserAdded(address indexed addedPauser, address indexed addedBy);
    event PauserRemoved(address indexed removedPauser, address indexed removedBy);

    Roles.Role private _pausers;

    modifier onlyPauser() {
        require(isPauser(msg.sender), "PauserRole: caller does not have the Pauser role");
        _;
    }

    function isPauser(address account) public view returns (bool) {
        return _pausers.has(account);
    }

    function addPauser(address account) public onlyOwner {
        _addPauser(account);
    }

    function removePauser(address account) public onlyOwner {
        _removePauser(account);
    }

    function _addPauser(address account) internal {
        _pausers.add(account);
        emit PauserAdded(account, msg.sender);
    }

    function _removePauser(address account) internal {
        _pausers.remove(account);
        emit PauserRemoved(account, msg.sender);
    }
}

// File: contracts/capabilities/Pausable.sol

pragma solidity 0.5.8;


/**
 * Allows transfers on a token contract to be paused by an administrator.
 */
contract Pausable is PauserRole {
    event Paused();
    event Unpaused();

    bool private _paused;

    /**
     * @return true if the contract is paused, false otherwise.
     */
    function paused() external view returns (bool) {
        return _paused;
    }

    /**
     * @dev internal function, triggers paused state
     */
    function _pause() internal {
        _paused = true;
        emit Paused();
    }

    /**
     * @dev internal function, returns to unpaused state
     */
    function _unpause() internal {
        _paused = false;
        emit Unpaused();
    }

     /**
     * @dev called by pauser role to pause, triggers stopped state
     */
    function pause() public onlyPauser {
        _pause();
    }

    /**
     * @dev called by pauer role to unpause, returns to normal state
     */
    function unpause() public onlyPauser {
        _unpause();
    }
}

// File: @openzeppelin/contracts/token/ERC20/ERC20Detailed.sol

pragma solidity ^0.5.0;


/**
 * @dev Optional functions from the ERC20 standard.
 */
contract ERC20Detailed is IERC20 {
    string private _name;
    string private _symbol;
    uint8 private _decimals;

    /**
     * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of
     * these values are immutable: they can only be set once during
     * construction.
     */
    constructor (string memory name, string memory symbol, uint8 decimals) public {
        _name = name;
        _symbol = symbol;
        _decimals = decimals;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5,05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view returns (uint8) {
        return _decimals;
    }
}

// File: contracts/InxToken.sol

pragma solidity 0.5.8;

contract InxToken is IERC1404, IERC1404Validators, IERC20, ERC20Detailed, OwnerRole, Revocable, Whitelistable, Timelockable, Pausable {

    // Token Details
    string constant TOKEN_NAME = "INX Token";
    string constant TOKEN_SYMBOL = "INX";
    uint8 constant TOKEN_DECIMALS = 18;

    // Token supply - 2 Hundred Million Tokens, with 18 decimal precision
    uint256 constant HUNDRED_MILLION = 100000000;
    uint256 constant TOKEN_SUPPLY = 2 * HUNDRED_MILLION * (10 ** uint256(TOKEN_DECIMALS));

    // This tracks the external contract where restriction logic is executed
    IERC1404Success private transferRestrictions;

    // Event tracking when restriction logic contract is updated
    event RestrictionsUpdated (address newRestrictionsAddress, address updatedBy);

    /**
    Constructor for the token to set readable details and mint all tokens
    to the specified owner.
    */
    constructor(address owner) public
        ERC20Detailed(TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS)
    {
        _mint(owner, TOKEN_SUPPLY);
        _addOwner(owner);
    }

    /**
    Function that can only be called by an owner that updates the address
    with the ERC1404 Transfer Restrictions defined
    */
    function updateTransferRestrictions(address _newRestrictionsAddress)
        public
        onlyOwner
        returns (bool)
    {
        transferRestrictions = IERC1404Success(_newRestrictionsAddress);
        emit RestrictionsUpdated(address(transferRestrictions), msg.sender);
        return true;
    }

    /**
    The address with the Transfer Restrictions contract
    */
    function getRestrictionsAddress () public view returns (address) {
        return address(transferRestrictions);
    }


    /**
    This function detects whether a transfer should be restricted and not allowed.
    If the function returns SUCCESS_CODE (0) then it should be allowed.
    */
    function detectTransferRestriction (address from, address to, uint256 amount)
        public
        view
        returns (uint8)
    {
        // Verify the external contract is valid
        require(address(transferRestrictions) != address(0), 'TransferRestrictions contract must be set');

        // call detectTransferRestriction on the current transferRestrictions contract
        return transferRestrictions.detectTransferRestriction(from, to, amount);
    }

    /**
    This function detects whether a transferFrom should be restricted and not allowed.
    If the function returns SUCCESS_CODE (0) then it should be allowed.
    */
    function detectTransferFromRestriction (address sender, address from, address to, uint256 amount)
        public
        view
        returns (uint8)
    {
        // Verify the external contract is valid
        require(address(transferRestrictions) != address(0), 'TransferRestrictions contract must be set');

        // call detectTransferFromRestriction on the current transferRestrictions contract
        return  transferRestrictions.detectTransferFromRestriction(sender, from, to, amount);
    }

    /**
    This function allows a wallet or other client to get a human readable string to show
    a user if a transfer was restricted.  It should return enough information for the user
    to know why it failed.
    */
    function messageForTransferRestriction (uint8 restrictionCode)
        external
        view
        returns (string memory)
    {
        // call messageForTransferRestriction on the current transferRestrictions contract
        return transferRestrictions.messageForTransferRestriction(restrictionCode);
    }

    /**
    Evaluates whether a transfer should be allowed or not.
    */
    modifier notRestricted (address from, address to, uint256 value) {
        uint8 restrictionCode = transferRestrictions.detectTransferRestriction(from, to, value);
        require(restrictionCode == transferRestrictions.getSuccessCode(), transferRestrictions.messageForTransferRestriction(restrictionCode));
        _;
    }

    /**
    Evaluates whether a transferFrom should be allowed or not.
    */
    modifier notRestrictedTransferFrom (address sender, address from, address to, uint256 value) {
        uint8 transferFromRestrictionCode = transferRestrictions.detectTransferFromRestriction(sender, from, to, value);
        require(transferFromRestrictionCode == transferRestrictions.getSuccessCode(), transferRestrictions.messageForTransferRestriction(transferFromRestrictionCode));
        _;
    }

    /**
    Overrides the parent class token transfer function to enforce restrictions.
    */
    function transfer (address to, uint256 value)
        public
        notRestricted(msg.sender, to, value)
        returns (bool success)
    {
        success = ERC20.transfer(to, value);
    }

    /**
    Overrides the parent class token transferFrom function to enforce restrictions.
    */
    function transferFrom (address from, address to, uint256 value)
        public
        notRestrictedTransferFrom(msg.sender, from, to, value)
        returns (bool success)
    {
        success = ERC20.transferFrom(from, to, value);
    }
}

Remixで試してみてください 

ソース:https ://cryptomarketpool.com/what-is-an-erc-1404-token-contract/

#token #erc 

ERC-1404トークン契約とは何ですか?
Joshua Yates

Joshua Yates

1639553078

End-To-End NFT/ERC-721/Blockchain on Opensea, Host Metadata on IPFS

NFT/ERC-721/Collectible END-TO-END TUTORIAL | Deploy, List on Opensea, Host Metadata on IPFS

End-To-End NFT/ERC-721/Blockchain Collectible tutorial. We deploy some adorable puppies.

Deploy your smart contract to Opensea, end-to-end. The Ultimate/end-to-end tutorial.

UPDATES:
🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺
1. ValueError: The private key must be exactly 32 bytes long, instead of 0 bytes.

2. requestRandomness no longer requires userProvidedSeed. However, if you use version 1.0.2 of the chainlink-brownie-contracts in your brownie-config.yaml as done in the video, you can ignore this.

3. Skip running `source .env` and instead just make sure in your `brownie-config.yaml` you have `dotenv: .env` and all your environment variables in your `.env` file. 
🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺

0:00 | Intro
1:02 | Quickstart 
16:02 | Deploy an NFT from scratch setup
18:30 | AdvancedCollectible.sol - Importing from github
26:45 | AdvancedCollectible.sol - Contract Constructor
30:20 | AdvancedCollectible.sol - CreateCollectible
42:28 | AdvancedCollectible.sol - fulfillRandomness
52:08 | AdvancedCollectible.sol - setTokenURI
55:40 | Deploy Script
1:05:39 | Link Fund Script
1:12:00 | Deploy Script (continued)
1:16:28 | create_collectible.py
1:25:11 | create_metadata.py
1:36:55 | create_metadata.py - IPFS
1:56:18 | set_tokeuri.py
2:09:55 | View on Opensea
2:11:29 | End

Code for this tutorial: https://github.com/PatrickAlphaC/nft-mix 
Download IPFS: https://ipfs.io/ 

#nft #blockchain #erc 

End-To-End NFT/ERC-721/Blockchain on Opensea, Host Metadata on IPFS

BEP20 Token Generator | Create BEP20 Token For Free

How to Create BEP20 Token? BEP20 Token Create Tool
If you would like to create BEP20 token under Binance Smart Chain without any coding skills, you are invited to use the BEP20 Token Generator which is free and easy to use with its user friendly interface.

BEP20 Token Generator: https://createmytoken.net/

ERC20 Token Generator: https://createmytoken.net/

BEP20 Token Generator is a free DApp which allows you to create your own BEP20 token in less than a minute.

How to use the BEP20 Token Generator
It is super easy to use the tool

Install Metamask and login.
Enter your token details such as name, symbol, decimals and supply.
Create your token.

#bsc  #bep20  #token  #bep20  #tokenization  create #solidity  #metamask  #erc20  #erc 20

BEP20 Token Generator | Create BEP20 Token For Free

Next-gen NFT token Development | ERC 1155 | ERC 721 - Brugu Software Solutions - Blog

Next gen NFT token Development | ERC 1155 | ERC 721

Non-fungible tokens or NFTs are now being used all over the world to represent ownership of various assets like digital artwork, music and even real estate. They have helped investors, brands, crypto enthusiasts and developers explore myriad new avenues in the blockchain space.

In fact, several popular brands have now jumped on the NFT bandwagon. For example, Taco Bell has recently started offering NFTs of their own by creating digital collectibles for sale. If you would like to get a better idea about what NFTs are and how they work, you can refer to our article ‘What are non-fungible tokens or NFTs?’.

If you are already aware of NFTs and the various benefits that they offer, then creating an NFT of your own is definitely something that would interest you. In this article, we will take a look at how to create non-fungible tokens with Ethereum 1155 standard.

Here, we’ll help you gain better insights into the below topics:
What is a token standard?
Limitations of ERC-721 token
What is ERC-1155 and how does it compare to ERC-721?
Features of ERC-1155 tokens
Creating an NFT token with ERC-1155 standard
What is a token standard?
A token standard is a collection of guidelines that describe the data and as a result, of the functions that each token can perform. Currently, the most popular NFT standards in use are ERC-721 and ERC-1155. ERC-721 is the foremost standard that enables us to develop unique non-fungible tokens for digital collectibles.

NFT token standard
If you’re looking to get a better idea about how to create an NFT with Ethereum 721 standard, you can have a look at our article on ‘How to create an NFT or Non-Fungible Token? Part 1 ERC-721 Standard’
Limitations of ERC-721 token
Despite the popularity of Ethereum 721, there are a few major limitations associated with this standard. One of the primary examples would be the difficulty level of transactions with such tokens. While using ERC-721, acquiring a token identifier directly is not possible, which makes transactions much harder. Imagine a situation where you would like to send 20 NFTs to someone. You will need to execute twenty different transactions, along with an individual additional charge for each transaction. This means that the transaction costs associated with this standard are extremely high. The load operations of the network often get compromised as well, which greatly affects the usability of the Ethereum network.

Another major limitation that we can see is traversing ERC-721 tokens. In order to do this efficiently, every single token in the contract will need to be traversed so that a response can be given to the dApp and the user. So, if an ERC-721 contract has 1 million tokens under its registration, a user will have to send a transaction to the network in order to know the status of their tokens. This will involve sifting through millions of tokens to match them with the user’s address and deliver a response, which is an extremely time consuming and inefficient process.
What is ERC-1155 and how does it compare to ERC-721?
Although the ERC-721 standard works well for tokenization and creation of unique assets that can be transferred between to users, a collection of such tokens is often slow and inefficient. This is why many have now turned towards ERC-1155, as it takes care of many of the limitations that are seen with the use of ERC-721. ERC-1155 tokens have often been lauded for its multi-token approach. They support batch transfers of multiple tokens at once at much higher costs than an ERC-721 token.

We can compare ERC-1155 tokens to a vending machine. Each developer is able to deploy a single smart contract that is often used to mint unlimited fungible and non-fungible tokens. On the other hand, ERC-721 tokens standard only produces non-fungible tokens, which means that developers need to deploy a new smart contract for each new token.

Developers will use the ERC-1155 token standard to deploy a single smart contract and then mint new tokens in seconds. They will keep using the one smart contract indefinitely, minting an unlimited number of fungible and non-fungible tokens.

Users can also send batch transfers of multiple fungible and non-fungible tokens using ERC 1155 in one go:

Additionally, users can also swap any amount of tokens easily and efficiently by following these steps-

There are various advantages associated with using ERC-1155 when compared to other token standards like ERC-20 and ERC-721. It allows developers to consolidate the logic in one contract and build an ecosystem around a single address. It also enables users to execute batch transfers in the same contract, and all of this is possible at much lower costs. The primary idea behind ERC-1155 is that a single smart contract can govern an infinite number of tokens.
Need help with NFT token development?
Reach out to us today and get started!
Features of ERC-1155 tokens
Batch transfers
ERC-1155 allows users to send several tokens in one single transaction. This allows them to save up on costs and time. By using this standard, you will be able to build Atomic Swaps with the same design as well. This can help you exchange one kind of token for another with absolute security from fraud and no intermediate party involved.

Strict rules
This standard follows a very strict set of rules. By sending a token to an address, the tokens will perform a deterministic smart contract function. For example, if you send a token to a DEX exchange address, the exchange could immediately return another token back to the sender’s address. Similarly, if a blockchain game receives an ERC-1155 token from a user it will execute a game feature. Without needing to access ABIs or communicate with the smart contract directly, tokens could be wrapped, transformed, designed, or escrowed.

ID substitution
A user can now point to an infinite number of token URIs without storing any additional data on-chain by using the substitution string “{id},”. This string can also be used inside the JSON itself to automatically link to imagery for each token. As a result of this, the Developers’ overhead for displaying metadata for large sets of tokens is greatly reduced.

Localization
Since token information is specified in JSON format, localization for multiple languages is now possible using {locale}. Alternate versions of token names, images, and any other data can be displayed in wallets and applications that support multiple languages.
Creating an NFT token with ERC-1155 standard
The multi token support option offered by ERC-1155 is one of its prime features. Imagine a scenario where every single airline possesses a unique token. If you’re looking to book a flight with three different connecting flights from various airlines, you would need to pay for each airline ticket individually. There could be many problems associated with this. In order to overcome these issues, can you create a single multi-token contract which holds the individual tokens for every airline? That’s exactly what ERC-1155 allows you to do. Instead of just one token class, you can add as many as we want.
https://brugu.io/blog/next-gen-nft-token-development-erc-1155-erc-721/

#blockchain #nft #erc #erc

Next-gen NFT token Development | ERC 1155 | ERC 721 - Brugu Software Solutions - Blog