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.
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.
transferFee * amount
.solidity
uint256 public totalSupply;
uint256 public maxSupply;
solidity
uint256 public transferFee; // fee per token transferred in wei
address public owner;
The owner
sets and updates transferFee
and maxSupply
.
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:
totalSupply
by the burned amount.This can map to redeeming underlying goods or simply deflating the token.
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
.
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
.
The DIFF standard implements the ERC-20 interface but significantly alters the transfer
and transferFrom
semantics:
balanceOf
function works as normal. However, transfer
and transferFrom
no longer redistribute tokens. Instead, they mint new tokens (up to maxSupply
).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.
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.
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.
from
balance) does not apply.maxSupply = 1,000,000 DIFF
, transferFee = 0.001 ETH
.totalSupply = 0
.Owner sets parameters and verifies via maxSupply()
and transferFee()
getters.
Minting on Transfer:
transfer(B, 100)
with msg.value = 0.1 ETH
(assuming transferFee = 0.001 ETH
).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:
burn(50)
.balances[B] == 50
, totalSupply == 50
less than before.Burn
event emitted.
Updating Fee and Withdrawing Funds:
setTransferFee(0.002 ETH)
.FeeUpdated
event emitted.withdrawFees(ownerAddress)
.ownerAddress
receives accumulated fees.A reference implementation is provided under the asset folder in the EIPs repository. The implementation includes:
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 {}
}
ReentrancyGuard
from OpenZeppelin to prevent reentrant calls.transferFee
and maxSupply
. Use proper onlyOwner
modifiers.Copyright and related rights waived via CC0.