Alert Source Discuss
⚠️ Draft Standards Track: ERC

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 (@jamesavechives)
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 (@jamesavechives), "ERC-7837: Diffusive Tokens [DRAFT]," Ethereum Improvement Proposals, no. 7837, December 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7837.