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:
uint256publictotalSupply;uint256publicmaxSupply;
Transfer Fee:
uint256publictransferFee;// fee per token transferred in wei
addresspublicowner;
The owner sets and updates transferFee and maxSupply.
Token Semantics
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.
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.
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 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.
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.
Owner sets parameters and verifies via maxSupply() and transferFee() getters.
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.
Exceeding Max Supply:
If totalSupply = 999,950 and someone tries to transfer 100 tokens, causing totalSupply to exceed 1,000,000, the transaction reverts.
Burning Tokens:
User B calls burn(50).
Check balances[B] == 50, totalSupply == 50 less than before.
Burn event emitted.
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.
contractDiffusiveToken{// -----------------------------------------
// State Variables
// -----------------------------------------
stringpublicname;stringpublicsymbol;uint8publicdecimals;uint256publictotalSupply;uint256publicmaxSupply;uint256publictransferFee;// Fee per token transferred in wei
addresspublicowner;// -----------------------------------------
// Events
// -----------------------------------------
eventTransfer(addressindexedfrom,addressindexedto,uint256amount);eventBurn(addressindexedburner,uint256amount);eventFeeUpdated(uint256newFee);eventMaxSupplyUpdated(uint256newMaxSupply);eventApproval(addressindexedowner,addressindexedspender,uint256value);// -----------------------------------------
// Modifiers
// -----------------------------------------
modifieronlyOwner(){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(stringmemory_name,stringmemory_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
*/functionbalanceOf(addressaccount)externalviewreturns(uint256){returnbalances[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
*/functiontransfer(addressto,uint256amount)externalpayablereturns(bool){require(to!=address(0),"DiffusiveToken: transfer to zero address");require(amount>0,"DiffusiveToken: amount must be greater than zero");uint256requiredFee=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;emitTransfer(msg.sender,to,amount);returntrue;}/**
* @notice Burns `amount` tokens from the caller's balance, decreasing total supply.
* @param amount The number of tokens to burn
*/functionburn(uint256amount)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;emitBurn(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
*/functionapprove(addressspender,uint256amount)externalreturns(bool){require(spender!=address(0),"DiffusiveToken: approve to zero address");allowances[msg.sender][spender]=amount;emitApproval(msg.sender,spender,amount);returntrue;}/**
* @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
*/functionallowance(address_owner,address_spender)externalviewreturns(uint256){returnallowances[_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)
*/functiontransferFrom(addressfrom,addressto,uint256amount)externalpayablereturns(bool){require(to!=address(0),"DiffusiveToken: transfer to zero address");require(amount>0,"DiffusiveToken: amount must be greater than zero");uint256allowed=allowances[from][msg.sender];require(allowed>=amount,"DiffusiveToken: allowance exceeded");// Deduct from allowance
allowances[from][msg.sender]=allowed-amount;uint256requiredFee=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;emitTransfer(from,to,amount);returntrue;}// -----------------------------------------
// Owner Functions
// -----------------------------------------
/**
* @notice Updates the maximum supply of tokens. Must be >= current totalSupply.
* @param newMaxSupply The new maximum supply
*/functionsetMaxSupply(uint256newMaxSupply)externalonlyOwner{require(newMaxSupply>=totalSupply,"DiffusiveToken: new max < current supply");maxSupply=newMaxSupply;emitMaxSupplyUpdated(newMaxSupply);}/**
* @notice Updates the per-token transfer fee.
* @param newFee The new fee in wei per token transferred
*/functionsetTransferFee(uint256newFee)externalonlyOwner{transferFee=newFee;emitFeeUpdated(newFee);}/**
* @notice Allows the owner to withdraw accumulated native currency fees.
* @param recipient The address that will receive the withdrawn fees
*/functionwithdrawFees(addresspayablerecipient)externalonlyOwner{require(recipient!=address(0),"DiffusiveToken: withdraw to zero address");uint256balance=address(this).balance;(boolsuccess,)=recipient.call{value:balance}("");require(success,"DiffusiveToken: withdrawal failed");}// -----------------------------------------
// Fallback and Receive
// -----------------------------------------
// Allows the contract to receive Ether.
receive()externalpayable{}}
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.