--- 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) 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).