ERC-7837 Diffusive token (minted on transfer) (Diffusive Tokens)
Final · Tokens · created 2024-12-07
A fungible token standard in which sending tokens does not reduce the sender's balance and new tokens are minted to the recipient. Each transfer pays a per-token fee in the native currency such as ETH, and total issuance has a cap.
Why it is needed
A conventional ERC-20 token reduces the sender's balance and increases the recipient's balance on transfer, so a fixed supply is divided among holders. However, in some cases, such as a voucher for a bundle of goods, it is natural for the spread of the token itself to increase supply. The existing standard has no way to express minting a new token on every transfer, and if each team builds such contracts differently, wallets and exchanges cannot predict their behavior. A common mechanism is also needed to prevent the problem of unlimited tokens being created just by repeating transfers. This standard bundles the rules of minting on transfer, a fixed fee per token, a maximum supply cap, and burning, so that such diffusive tokens can be built in a consistent way.
What to implement
function balanceOf(address account) external view returns (uint256);: Returns the token balance held by the address. It behaves the same as ERC-20.function transfer(address to, uint256 amount) external payable returns (bool);: Mints new tokens to `to` in the amount of `amount`. The sender's balance does not decrease, and the sender must also send a fee of at least transferFee * amount via msg.value.function burn(uint256 amount) external;: Reduces the caller's balance and totalSupply by amount. Used for physical redemption or supply reduction.function setMaxSupply(uint256 newMax) external;: Callable only by the owner. Changes the maximum supply.function setTransferFee(uint256 newFee) external;: Callable only by the owner. Changes the per-token transfer fee.function withdrawFees(address payable recipient) external;: Callable only by the owner. Sends the native currency fees accumulated in the contract to recipient.function approve(address spender, uint256 amount) external returns (bool);: Approves spender to execute transfers up to amount on the caller's behalf. Provided for ERC-20 compatibility.function transferFrom(address from, address to, uint256 amount) external payable returns (bool);: Mints new tokens to `to` while deducting from the allowance. The balance of from does not decrease.event Transfer(address indexed from, address indexed to, uint256 amount);: Emitted when a transfer call mints tokens to `to`.event Burn(address indexed burner, uint256 amount);: Emitted when amount tokens are burned from an address.event FeeUpdated(uint256 newFee);: Emitted when the owner changes the transfer fee.event MaxSupplyUpdated(uint256 newMaxSupply);: Emitted when the owner changes the maximum supply.
In depth
A transfer is minting, not a balance movement
When A transfers amount to B, A's balance stays the same, and B's balance and totalSupply both increase by amount. If totalSupply + amount exceeds maxSupply, the call must revert. The reference implementation makes the call fail if to is the zero address or amount is 0. The Transfer event is emitted when a transfer call mints new tokens to `to`, not when tokens actually move, so do not assume from's balance decreased based on the event alone.
A fixed per-token fee is paid in the native currency
On each transfer, the sender must send transferFee * amount of the native currency (ETH, in wei) via msg.value, and the transaction reverts if the amount is insufficient. This fee accumulates in the contract and the owner collects it with withdrawFees. Because every transfer carries a real cost, repeated transfers to mint without limit are economically discouraged, and the fee also becomes a revenue source for the issuer.
Maximum supply and burning control the supply
maxSupply is the upper bound totalSupply can reach, and any transfer that would exceed it fails. A holder can reduce their own balance and totalSupply together with burn(amount), and a Burn event is emitted. Burning can map to redeeming tokens for physical goods or services, or can simply be used to reduce supply. In the reference implementation, setMaxSupply requires that the new cap be at least the current totalSupply.
approve and transferFrom are for compatibility and have different semantics
approve and transferFrom remain for interoperability with ERC-20 tooling, but their logic differs. transferFrom deducts the allowance that from granted to msg.sender by amount, but does not reduce from's balance at all, and mints new tokens to `to`. The fee transferFee * amount is paid via msg.value by the party that calls transferFrom. The 'deduct from's balance' behavior expected in ERC-20 does not apply, so integration code written on that assumption cannot be used as is.
The owner manages the fee and the cap
The owner can update the per-token fee with setTransferFee and the maximum supply with setMaxSupply, which emit the FeeUpdated and MaxSupplyUpdated events respectively. Accumulated fees are collected with withdrawFees(recipient), and the reference implementation sends the contract's entire balance to recipient. The design lets the issuer adjust conditions flexibly to market conditions, but the owner's authority directly affects the token economy to that extent.
Only the interface is compatible with ERC-20
The function set, including balanceOf, transfer, approve, and transferFrom, matches ERC-20, so most ERC-20 compatible tools can display balances and initiate transfers. However, behavior differs significantly because a transfer is minting rather than redistribution, and the source text states explicitly that this unusual economic structure can confuse users and pricing mechanisms. Systems that integrate the DIFF token must understand this difference.
Things to watch out for
- Reentrancy attack prevention: Handling that sends native currency, such as fees, should follow the Checks-Effects-Interactions pattern, and you should consider using OpenZeppelin's ReentrancyGuard to block reentrant calls.
- Overflow and underflow: Solidity 0.8.x prevents arithmetic overflow and underflow by default.
- Fee validation: The contract must check that a sufficient transfer fee was sent and revert the transaction if it falls short.
- Access control: transferFee and maxSupply must be changeable only by the owner, and access control such as onlyOwner must be applied correctly.