ERC-7837 - Diffusive Tokens

Created 2024-12-07
Status Draft
Category ERC
Type Standards Track
Authors

Abstract

This ERC proposes a standard for a new type of fungible token, called Diffusive Tokens (DIFF). Unlike traditional ERC-20 tokens, transferring DIFF tokens does not decrease the sender’s balance. Instead, it mints new tokens directly to the recipient, increasing the total supply on every transfer action. A fixed native currency fee is charged per token transferred, and this fee is paid by the sender to the contract owner. The supply growth is limited by a maximum supply set by the owner. Token holders can also burn their tokens to reduce the total supply. These features enable a controlled, incentivized token distribution model that merges fungibility with a built-in economic mechanism.

Motivation

Traditional ERC-20 tokens maintain a constant total supply and simply redistribute balances on transfers. While this model is widespread, certain use cases benefit from a token design that continuously expands supply during transfers, simulating a controlled "diffusion" of value. The Diffusive Token model may be suitable for representing claims on real-world goods (e.g., a product batch like iPhone 15 units), digital goods, or controlled asset distributions where initial token distribution and ongoing availability need to be managed differently.

This model also includes a native currency fee per token transferred, incentivizing careful, value-driven transfers and providing a revenue stream for the token’s issuer. The maximum supply cap prevents unbounded inflation, ensuring long-term scarcity. The ability for owners to burn tokens to redeem underlying goods or services directly maps on-chain assets to real-world redemptions.

Use Cases:

Specification

Terminology

Data Structures

solidity uint256 public totalSupply; uint256 public maxSupply;

solidity uint256 public transferFee; // fee per token transferred in wei address public owner;

The owner sets and updates transferFee and maxSupply.

Token Semantics

  1. Minting on Transfer When a transfer occurs from A to B:
  2. A does not lose any tokens.
  3. B receives newly minted tokens (increasing their balance and totalSupply).
  4. The totalSupply increases by the transferred amount, but must not exceed maxSupply.

  5. Fixed Transfer Fee in Native Currency Each transfer requires the sender to pay transferFee * amount in the native currency. If msg.value is insufficient, the transaction reverts.

  6. Maximum Supply If a transfer would cause totalSupply + amount > maxSupply, it must revert.

  7. Burning Tokens Token holders can burn tokens to:

  8. Reduce their balance by the burned amount.
  9. Decrease totalSupply by the burned amount.

This can map to redeeming underlying goods or simply deflating the token.

Interface

The DIFF standard aligns partially with ERC-20, but redefines certain behaviors:

Core Functions:

Administration Functions (Owner Only):

Optional Approval Interface (For Compatibility):

Events

Emitted when tokens are minted to to via a transfer call.

Emitted when amount of tokens are burned from an address.

Emitted when the owner updates the transferFee.

Emitted when the owner updates maxSupply.

Compliance with ERC-20

The DIFF standard implements the ERC-20 interface but significantly alters the transfer and transferFrom semantics:

While the DIFF standard can be seen as ERC-20 compatible at the interface level, the underlying economics differ substantially.

Rationale

Design Decisions:

Backwards Compatibility

The DIFF standard is interface-compatible with ERC-20 but not behaviorally identical. Any system integrating DIFF tokens should understand the difference in minting on transfer.

Test Cases

  1. Initial Conditions:
  2. Deploy contract with maxSupply = 1,000,000 DIFF, transferFee = 0.001 ETH.
  3. totalSupply = 0.
  4. Owner sets parameters and verifies via maxSupply() and transferFee() getters.

  5. Minting on Transfer:

  6. User A calls transfer(B, 100) with msg.value = 0.1 ETH (assuming transferFee = 0.001 ETH).
  7. Check balances[B] == 100, totalSupply == 100.
  8. Check that the contract now holds 0.1 ETH from the fee.

  9. Exceeding Max Supply:

  10. If totalSupply = 999,950 and someone tries to transfer 100 tokens, causing totalSupply to exceed 1,000,000, the transaction reverts.

  11. Burning Tokens:

  12. User B calls burn(50).
  13. Check balances[B] == 50, totalSupply == 50 less than before.
  14. Burn event emitted.

  15. Updating Fee and Withdrawing Funds:

  16. Owner calls setTransferFee(0.002 ETH).
  17. FeeUpdated event emitted.
  18. Owner calls withdrawFees(ownerAddress).
  19. Check that ownerAddress receives accumulated fees.

Reference Implementation

A reference implementation is provided under the asset folder in the EIPs repository. The implementation includes:

contract DiffusiveToken {
    // -----------------------------------------
    // State Variables
    // -----------------------------------------

    string public name;
    string public symbol;
    uint8 public decimals;

    uint256 public totalSupply;
    uint256 public maxSupply;
    uint256 public transferFee; // Fee per token transferred in wei

    address public owner;

    // -----------------------------------------
    // Events
    // -----------------------------------------

    event Transfer(address indexed from, address indexed to, uint256 amount);
    event Burn(address indexed burner, uint256 amount);
    event FeeUpdated(uint256 newFee);
    event MaxSupplyUpdated(uint256 newMaxSupply);
    event Approval(address indexed owner, address indexed spender, uint256 value);

    // -----------------------------------------
    // Modifiers
    // -----------------------------------------

    modifier onlyOwner() {
        require(msg.sender == owner, "DiffusiveToken: caller is not the owner");
        _;
    }

    // -----------------------------------------
    // Constructor
    // -----------------------------------------

    /**
     * @dev Constructor sets the initial parameters for the Diffusive Token.
     * @param _name Token name
     * @param _symbol Token symbol
     * @param _decimals Decimal places
     * @param _maxSupply The max supply of tokens that can ever exist
     * @param _transferFee Initial fee per token transferred in wei
     */
    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals,
        uint256 _maxSupply,
        uint256 _transferFee
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;
        maxSupply = _maxSupply;
        transferFee = _transferFee;
        owner = msg.sender;
        totalSupply = 0; // Initially, no tokens are minted
    }

    // -----------------------------------------
    // External and Public Functions
    // -----------------------------------------

    /**
     * @notice Returns the token balance of the given address.
     * @param account The address to query
     */
    function balanceOf(address account) external view returns (uint256) {
        return balances[account];
    }

    /**
     * @notice Transfers `amount` tokens to address `to`, minting new tokens in the process.
     * @dev Requires payment of native currency: transferFee * amount.
     * @param to Recipient address
     * @param amount Number of tokens to transfer
     * @return True if successful
     */
    function transfer(address to, uint256 amount) external payable returns (bool) {
        require(to != address(0), "DiffusiveToken: transfer to zero address");
        require(amount > 0, "DiffusiveToken: amount must be greater than zero");

        uint256 requiredFee = transferFee * amount;
        require(msg.value >= requiredFee, "DiffusiveToken: insufficient fee");

        // Check max supply limit
        require(totalSupply + amount <= maxSupply, "DiffusiveToken: would exceed max supply");

        // Mint new tokens to `to`
        balances[to] += amount;
        totalSupply += amount;

        emit Transfer(msg.sender, to, amount);
        return true;
    }

    /**
     * @notice Burns `amount` tokens from the caller's balance, decreasing total supply.
     * @param amount The number of tokens to burn
     */
    function burn(uint256 amount) external {
        require(amount > 0, "DiffusiveToken: burn amount must be greater than zero");
        require(balances[msg.sender] >= amount, "DiffusiveToken: insufficient balance");

        balances[msg.sender] -= amount;
        totalSupply -= amount;

        emit Burn(msg.sender, amount);
    }

    /**
     * @notice Approves `spender` to transfer up to `amount` tokens on behalf of `msg.sender`.
     * @param spender The address authorized to spend
     * @param amount The max amount they can spend
     */
    function approve(address spender, uint256 amount) external returns (bool) {
        require(spender != address(0), "DiffusiveToken: approve to zero address");
        allowances[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    /**
     * @notice Returns the current allowance of `spender` for `owner`.
     * @param _owner The owner of the tokens
     * @param _spender The address allowed to spend the tokens
     */
    function allowance(address _owner, address _spender) external view returns (uint256) {
        return allowances[_owner][_spender];
    }

    /**
     * @notice Transfers `amount` tokens from `from` to `to` using the allowance mechanism.
     * @dev The `from` account does not lose tokens; this still mints to `to`.
     * @param from The address from which the allowance has been given
     * @param to The recipient address
     * @param amount The number of tokens to transfer (mint)
     */
    function transferFrom(address from, address to, uint256 amount) external payable returns (bool) {
        require(to != address(0), "DiffusiveToken: transfer to zero address");
        require(amount > 0, "DiffusiveToken: amount must be greater than zero");

        uint256 allowed = allowances[from][msg.sender];
        require(allowed >= amount, "DiffusiveToken: allowance exceeded");

        // Deduct from allowance
        allowances[from][msg.sender] = allowed - amount;

        uint256 requiredFee = transferFee * amount;
        require(msg.value >= requiredFee, "DiffusiveToken: insufficient fee");

        // Check max supply
        require(totalSupply + amount <= maxSupply, "DiffusiveToken: would exceed max supply");

        // Mint tokens to `to`
        balances[to] += amount;
        totalSupply += amount;

        emit Transfer(from, to, amount);
        return true;
    }

    // -----------------------------------------
    // Owner Functions
    // -----------------------------------------

    /**
     * @notice Updates the maximum supply of tokens. Must be >= current totalSupply.
     * @param newMaxSupply The new maximum supply
     */
    function setMaxSupply(uint256 newMaxSupply) external onlyOwner {
        require(newMaxSupply >= totalSupply, "DiffusiveToken: new max < current supply");
        maxSupply = newMaxSupply;
        emit MaxSupplyUpdated(newMaxSupply);
    }

    /**
     * @notice Updates the per-token transfer fee.
     * @param newFee The new fee in wei per token transferred
     */
    function setTransferFee(uint256 newFee) external onlyOwner {
        transferFee = newFee;
        emit FeeUpdated(newFee);
    }

    /**
     * @notice Allows the owner to withdraw accumulated native currency fees.
     * @param recipient The address that will receive the withdrawn fees
     */
    function withdrawFees(address payable recipient) external onlyOwner {
        require(recipient != address(0), "DiffusiveToken: withdraw to zero address");
        uint256 balance = address(this).balance;
        (bool success, ) = recipient.call{value: balance}("");
        require(success, "DiffusiveToken: withdrawal failed");
    }

    // -----------------------------------------
    // Fallback and Receive
    // -----------------------------------------

    // Allows the contract to receive Ether.
    receive() external payable {}
}

Security Considerations

Copyright

Copyright and related rights waived via CC0.