HomeEIPsNewsletter
EIPsERC-7837
ERC-7837

Diffusive Tokens

A fungible token that mints new tokens on transfer, charges a per-token native fee, and enforces a capped supply.
DraftStandards Track: ERC
Created: 2024-12-07
James Savechives (@jamesavechives)
Discussions ForumOriginal Proposal LinkEdit
1 min read
Anyone may contribute to propose contents.
Go propose
Video
Anyone may contribute to propose contents.
Go propose
Original

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.

Further reading
Anyone may contribute to propose contents.
Go propose
Adopted by projects
Anyone may contribute to propose contents.
Go propose

Not miss a beat of EIPs' update?

Subscribe EIPs Fun to receive the latest updates of EIPs Good for Buidlers to follow up.

View all
Serve Ethereum Builders, Scale the Community.
Resources
GitHub