---
eip: 7837
title: Diffusive Tokens
description: A fungible token that mints new tokens on transfer, charges a per-token native fee, and enforces a capped supply.
author: Cheng Qian (@jamesavechives) <james.walstonn@gmail.com>
discussions-to: https://ethereum-magicians.org/t/erc-7837-diffusive-tokens/21989
status: Final
type: Standards Track
category: ERC
created: 2024-12-07
---

## Abstract

This ERC proposes a standard for a new type of fungible token, called **Diffusive Tokens (DIFF)**. Unlike traditional [ERC-20](./eip-20.md) 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](./eip-20.md) 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**:
  
  ```solidity
  uint256 public totalSupply;
  uint256 public maxSupply;
  ```

- **Transfer Fee**:
  
  ```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`:
   - `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](./eip-20.md), 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.
```solidity
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

Copyright and related rights waived via [CC0](../LICENSE.md).
