Finalizable Service Token (FST) what do you think?

I thought a particular token designed for companies and generally service selling.
The token can be admitted and transferred only n-times and can be finalized to an address with data (like a receipt)

discussion on github

Simple Summary

A standard interface for Finalizable Service Token enables companies or professionals to emit a token for its services.

Abstract

This improvemnt wnats to propose a service token transferable n-times and mintable.

The main focus of this token Is about a distributed service that can be used with this token.

Motivation

I will try to explain this initial idea by examples.

Application Example 1 (On Demand Service)

A company wants to emit a license for software that it develops the machine with this software wants to send a paid token with its custom data to a user wallet to enable software X on machine Y for 1 yr.
The token can’t be transferred after finalization with or without data or after a certain amount of transfer. The multi transfer can be useful in case of using other contracts before or using different company wallets.

Application Example 2 (Validation service)

let Actor1 Actor2 buy 2 different Token and wants to make a Deal with a Validation using a contract Validator.

  • Actor1 finalize a token with data given by Actor2 and sent it to a Validator
  • Actor2 finalize a token with data given by Actor1 and sent it to a Validator
  • Validator mint 2 different tokens as a Deal form Actor1 and Actor2 and finalize 2 tokens that send to actors.

Specification

I just thought an initial basic interface for this EIP and a possible implementation

pragma solidity ^0.7.1;
// SPDX-License-Identifier: MIT

interface IERCXXX{

    /**
     * @dev Emitted when `tokenId` token is transfered from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
        /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @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 Mint a given `tokenId` to `to`
     *
     *Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must not exist.
     * - `allowedMaxTransfer` maximum transfers for the minted token
     * Emits a {Transfer} event.
     */
    function mint(address to, uint256 tokenId, uint256 allowedMaxTransfer) external;

    /**
     * @dev Finalizes the token with given `tokenId` and send it from `from` to `to` adding `data` to token.
     * after calling this function token can't be anymore transfered and data can't be modified anymore.
     *
     *Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist.
     * Emits a {Transfer} event.
     */
    function finalizeAndTransferFrom(address from,address to, uint256 tokenId, string memory data) external;

     /**
     * @dev Transfer token from `from` to `to` and decrement the internal transfer counter.
     *
     *Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist.
     * Emits a {Transfer} event.
     */
    function transferFrom(address from,address to, uint256 tokenId) external;

    /**
     * @dev Gets the token by giveing the `tokenId`.
     *
     *Requirements:
     *
     * - `tokenId` token must exist.
     */
    function getTokenData(uint256 tokenID) external view returns(string memory);

    /**
     * @dev Gets the remaining transfer for the given `tokenId`.
     *
     *Requirements:
     *
     * - `tokenId` token must exist.
     */    
    function getResidualTransfer(uint256 tokenId) external view returns(uint256) ;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address[] memory operators);


}
contract ERCxxx is IERCXXX{

    string private _name;
    string private _symbol;
    mapping(address=>bool) private _approvedMinters;
    mapping(uint256 => uint256) private _residualTransfer;
    mapping(uint256 => address[]) private _tokenOwners;
    mapping(uint256 => string) private _tokenData;
    mapping(address => uint256) private _balances;

    uint256[] private _tokens;



    constructor(string memory tokenName, string memory tokenSymbol)
    {
        _name = tokenName;
        _symbol = tokenSymbol;
       
        _approvedMinters[msg.sender] = true;
    }
    function Symbol()public view returns(string memory)
    {
        return _symbol;
    }
    function _exists(uint256 tokenId) private view returns(bool)
    {
        if(_tokenOwners[tokenId].length == 0)
            return false;
        return true;
    }

    function _isOwnerOf(address owner,uint256 tokenId) private view returns(bool)
    {
        address[] memory addr = _tokenOwners[tokenId];
        for(uint256 i =0; i< addr.length; i++)
        {
            if(addr[i]==owner)
                return true;
        }
        return false;
    }
    function mint(address to, uint256 tokenId, uint256 allowedMaxTransfer) public override
    {
        require(_approvedMinters[msg.sender],"ERCXXX: you can't mint this token");
        require(to != address(0), "ERCXXX: mint require a not null address");
        require(!_exists(tokenId), "ERCXXX: Token id already exists");
        _tokens.push(tokenId);
        _tokenOwners[tokenId].push(to);
        _residualTransfer[tokenId] = allowedMaxTransfer;
        _balances[to]++;
    }
    function totalSupply() public view override returns (uint256) {
        return _tokens.length;
    }

    function _transfer(address from,address to, uint256 tokenId, string memory data) private {
        
        _residualTransfer[tokenId]--;
        delete _tokenOwners[tokenId];
        _tokenOwners[tokenId].push(to);
        _tokenData[tokenId] = data;
        _balances[from]--;
        _balances[to]++;
    }

    function _transferCheck(address from,uint256 tokenId) private view
    {
        require(_residualTransfer[tokenId] > 0, "ERCXXX: transfer limit reached for this token");
        require(_isOwnerOf(from,tokenId), "ERCXXX: only token owner can finalize and/or transfer");
    }
    function finalizeAndTransfer(address to, uint256 tokenId, string memory data) public
    {
        _transferCheck(msg.sender,tokenId);
        _residualTransfer[tokenId]=1;
        _transfer(msg.sender,to,tokenId,data);
    }

    function transfer(address to, uint256 tokenId) public
    {
        _transferCheck(msg.sender,tokenId);
        _transfer(msg.sender,to,tokenId,"");
    }
    
    function finalizeAndTransferFrom(address from,address to, uint256 tokenId, string memory data) public override
    {
        _transferCheck(from,tokenId);
        _residualTransfer[tokenId]=1;
        _transfer(from,to,tokenId,data);
    }

    function transferFrom(address from,address to, uint256 tokenId) public override
    {
        _transferCheck(from,tokenId);
        _transfer(from,to,tokenId,"");
    }


    
    function getResidualTransfer(uint256 tokenId) public view override returns(uint256)
    {
        return _residualTransfer[tokenId];
    }
    
    function balanceOf(address wallet) public view override returns(uint256)
    {
        return _balances[wallet];
    }

    function approve(address to, uint256 tokenId) public override
    {
        _tokenOwners[tokenId].push(to);
    }


    function getApproved(uint256 tokenId) public view override returns (address[] memory operators)
    {
        return _tokenOwners[tokenId];
    }
    
    function getTokenData(uint256 tokenID) public view override returns(string memory) 
    {
        return _tokenData[tokenID];
    }
}