Diffusive Tokens
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 updatestransferFee
andmaxSupply
.
Token Semantics
- Minting on Transfer
When a transfer occurs from
A
toB
: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 exceedmaxSupply
.
-
Fixed Transfer Fee in Native Currency Each transfer requires the sender to pay
transferFee * amount
in the native currency. Ifmsg.value
is insufficient, the transaction reverts. -
Maximum Supply If a transfer would cause
totalSupply + amount > maxSupply
, it must revert. - 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 toto
, requiresmsg.value >= transferFee * amount
.
- Modified behavior: Mints
-
function burn(uint256 amount) external;
- Reduces sender’s balance and
totalSupply
.
- Reduces sender’s balance and
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 toto
rather than redistributing fromfrom
.
- Modified behavior: Similar to
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
andtransferFrom
no longer redistribute tokens. Instead, they mint new tokens (up tomaxSupply
). - Approvals: The
approve
andtransferFrom
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
- Initial Conditions:
- Deploy contract with
maxSupply = 1,000,000 DIFF
,transferFee = 0.001 ETH
. totalSupply = 0
.- Owner sets parameters and verifies via
maxSupply()
andtransferFee()
getters.
- Deploy contract with
- Minting on Transfer:
- User A calls
transfer(B, 100)
withmsg.value = 0.1 ETH
(assumingtransferFee = 0.001 ETH
). - Check
balances[B] == 100
,totalSupply == 100
. - Check that the contract now holds 0.1 ETH from the fee.
- User A calls
- Exceeding Max Supply:
- If
totalSupply = 999,950
and someone tries to transfer 100 tokens, causingtotalSupply
to exceed1,000,000
, the transaction reverts.
- If
- Burning Tokens:
- User B calls
burn(50)
. - Check
balances[B] == 50
,totalSupply == 50
less than before. Burn
event emitted.
- User B calls
- Updating Fee and Withdrawing Funds:
- Owner calls
setTransferFee(0.002 ETH)
. FeeUpdated
event emitted.- Owner calls
withdrawFees(ownerAddress)
. - Check that
ownerAddress
receives accumulated fees.
- Owner calls
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
andmaxSupply
. Use properonlyOwner
modifiers.
Copyright
Copyright and related rights waived via CC0.