Alert Source Discuss

ERC Review Standards Track

ERC-7837: Diffusive Tokens

A fungible token that mints new tokens on transfer, charges a per-token native fee, and enforces a capped supply.

Authors
James Savechives ()
Created 2024-12-07
Discussion Link https://ethereum-magicians.org/t/erc-7837-diffusive-tokens/21989

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:

  • Real-World Asset Backing: A manufacturer can issue DIFF tokens representing a batch of products (e.g., iPhones). Each token can be redeemed (burned) for one physical item.

  • Fee-Driven Incentives: The transfer fee ensures that infinite minting by constant transferring is economically disincentivized. The fee also supports the token issuer or provides a funding mechanism.

Specification

Terminology

  • Diffusive Token: A fungible token unit that is minted on transfers.
  • Max Supply: The maximum total supply the token can reach.
  • Transfer Fee: A fee in native blockchain currency (e.g., ETH) that must be paid by the sender for each token transferred. The total fee = transferFee * amount.
  • Burn: The action of destroying tokens, reducing both the holder’s balance and the total supply.

Data Structures

  • Total Supply and Max Supply:

    uint256 public totalSupply;
    uint256 public maxSupply;
    
  • Transfer Fee:

    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:

    • A does not lose any tokens.
    • B receives newly minted tokens (increasing their balance and totalSupply).
    • The totalSupply increases by the transferred amount, but must not exceed maxSupply.
  2. 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.

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

  4. Burning Tokens Token holders can burn tokens to:

    • Reduce their balance by the burned amount.
    • 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:

  • function balanceOf(address account) external view returns (uint256);

  • function transfer(address to, uint256 amount) external payable returns (bool);

    • Modified behavior: Mints amount tokens to to, requires msg.value >= transferFee * amount.
  • function burn(uint256 amount) external;

    • Reduces sender’s balance and totalSupply.

Administration Functions (Owner Only):

  • function setMaxSupply(uint256 newMax) external;

  • function setTransferFee(uint256 newFee) external;

  • function withdrawFees(address payable recipient) external;

    • Withdraws accumulated native currency fees.

Optional Approval Interface (For Compatibility):

  • function approve(address spender, uint256 amount) external returns (bool);

  • function transferFrom(address from, address to, uint256 amount) external payable returns (bool);

    • Modified behavior: Similar to transfer, but uses allowance and still mints tokens to to rather than redistributing from from.

Events

  • event Transfer(address indexed from, address indexed to, uint256 amount);

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

  • event Burn(address indexed burner, uint256 amount);

    Emitted when amount of tokens are burned from an address.

  • event FeeUpdated(uint256 newFee);

    Emitted when the owner updates the transferFee.

  • event MaxSupplyUpdated(uint256 newMaxSupply);

    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:

  • Fungibility: Each token unit is identical and divisible as in ERC-20.
  • Balances and Transfers: The balanceOf function works as normal. However, transfer and transferFrom no longer redistribute tokens. Instead, they mint new tokens (up to maxSupply).
  • Approvals: The approve and transferFrom functions remain, but their logic is unconventional since the sender’s balance is never reduced by transfers.

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

Rationale

Design Decisions:

  • Unlimited Minting vs. Max Supply: Allowing minting on every transfer provides a “diffusive” spread of tokens. The maxSupply prevents uncontrolled inflation.

  • Burn Mechanism: Enables redemption or deflation as tokens are taken out of circulation.

  • Owner Controls: The owner (e.g., issuer) can adjust fees and max supply, maintaining flexibility as market conditions change.

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.

  • Wallets and Exchanges: Most ERC-20 compatible tools can display balances and initiate transfers. However, the unusual economics (mint on transfer) may confuse users and pricing mechanisms.
  • Allowances and TransferFrom: Still implemented for interoperability, but the expected logic (debiting from balance) does not apply.

Test Cases

  1. Initial Conditions:

    • Deploy contract with maxSupply = 1,000,000 DIFF, transferFee = 0.001 ETH.
    • totalSupply = 0.
    • Owner sets parameters and verifies via maxSupply() and transferFee() getters.
  2. Minting on Transfer:

    • User A calls transfer(B, 100) with msg.value = 0.1 ETH (assuming transferFee = 0.001 ETH).
    • Check balances[B] == 100, totalSupply == 100.
    • Check that the contract now holds 0.1 ETH from the fee.
  3. Exceeding Max Supply:

    • If totalSupply = 999,950 and someone tries to transfer 100 tokens, causing totalSupply to exceed 1,000,000, the transaction reverts.
  4. Burning Tokens:

    • User B calls burn(50).
    • Check balances[B] == 50, totalSupply == 50 less than before.
    • Burn event emitted.
  5. Updating Fee and Withdrawing Funds:

    • Owner calls setTransferFee(0.002 ETH).
    • FeeUpdated event emitted.
    • Owner calls withdrawFees(ownerAddress).
    • Check that ownerAddress receives accumulated fees.

Reference Implementation

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

  • A basic contract implementing the DIFF standard.
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 {}
}
  • Interfaces and helper contracts for testing and demonstration purposes.

Security Considerations

  • Reentrancy: Handle fee transfers using the Checks-Effects-Interactions pattern. Consider ReentrancyGuard from OpenZeppelin to prevent reentrant calls.
  • Overflow/Underflow: Solidity 0.8.x guards against this by default.
  • Contract Balance Management: Ensure enough native currency is sent to cover fees. Revert on insufficient fees.
  • Access Control: Only the owner can update transferFee and maxSupply. Use proper onlyOwner modifiers.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

James Savechives, "Diffusive Tokens," Ethereum Improvement Proposals, no. 7837, early access, December 2024. [Online serial]. Available: https://eips-wg.github.io/EIPs/7837/.