This EIP proposes a standard extension to ERC-20 tokens that enables issuers to apply an updatable multiplier to the UI (user interface) amount of tokens. This allows for efficient representation of stock splits, without requiring actual token minting or transfers. The extension provides a cosmetic layer that modifies how token balances are displayed to users while maintaining the underlying token economics.
Motivation
Current ERC-20 implementations lack an efficient mechanism to handle real-world asset scenarios such as stock splits: When a company performs a 2-for-1 stock split, all shareholders should see their holdings double. Currently, this requires minting new tokens to all holders, which is gas-intensive and operationally complex. Moreover, the internal accounting in DeFi protocols would break from such a split.
The inability to efficiently handle this scenario limits the adoption of tokenized real-world assets (RWAs) on Ethereum. This EIP addresses these limitations by introducing a multiplier mechanism that adjusts the displayed balance without altering the actual token supply.
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
Core Interface
Compliant contracts MUST implement the IScaledUIAmount interface:
interfaceIScaledUIAmount{// Emitted when the UI multiplier is updated
eventUIMultiplierUpdated(uint256oldMultiplier,uint256newMultiplier,uint256effectiveAtTimestamp);// OPTIONAL: Emitted during a token transfer with the UI-adjusted amount
eventTransferWithUIAmount(addressindexedfrom,addressindexedto,uint256amount,uint256uiAmount);// Returns the current UI multiplier
// Multiplier is represented with 18 decimals (1e18 = 1.0)
functionuiMultiplier()externalviewreturns(uint256);}
Note: How the multiplier is updated is an implementation detail left to the token issuer. The reference implementation below shows one approach using a setUIMultiplier function with delayed effectiveness.
Optional Extension: Conversion
Contracts MAY implement the IScaledUIAmountConversion extension for on-chain conversion helpers:
interfaceIScaledUIAmountConversion{// Converts a raw token amount to UI amount
functiontoUIAmount(uint256rawAmount)externalviewreturns(uint256);// Converts a UI amount to raw token amount
functionfromUIAmount(uint256uiAmount)externalviewreturns(uint256);}
Optional Extension: Balances
Contracts MAY implement the IScaledUIAmountBalances extension for on-chain UI balance queries:
interfaceIScaledUIAmountBalances{// Returns the UI-adjusted balance of an account
functionbalanceOfUI(addressaccount)externalviewreturns(uint256);// Returns the UI-adjusted total supply
functiontotalSupplyUI()externalviewreturns(uint256);}
Interface Detection
Compliant contracts MUST implement ERC-165 and return true when queried for the IScaledUIAmount interface ID.
Contracts implementing optional extensions MUST also return true for their respective interface IDs.
The interface identifiers are:
IScaledUIAmount: 0xa60bf13d
IScaledUIAmountConversion: 0xTBD
IScaledUIAmountBalances: 0xd890fd71
Implementation Requirements:
ERC-165 Support: Compliant contracts MUST implement ERC-165 interface detection.
Multiplier Precision: The UI multiplier MUST use 18 decimal places for precision (1e18 represents a multiplier of 1.0).
Backwards Compatibility: The standard ERC-20 functions (balanceOf, transfer, transferFrom, etc.) MUST continue to work with raw amounts.
Event Emission: The UIMultiplierUpdated event MUST be emitted whenever the multiplier is changed.
Reference Implementation
The following implementation includes the core interface and all optional extensions:
contractScaledUITokenisERC20,ERC165,IScaledUIAmount,IScaledUIAmountConversion,IScaledUIAmountBalances,Ownable{uint256privateconstantMULTIPLIER_DECIMALS=1e18;uint256private_uiMultiplier=MULTIPLIER_DECIMALS;// Initially 1.0
uint256public_nextUiMultiplier=MULTIPLIER_DECIMALS;uint256public_nextUiMultiplierEffectiveAt=0;constructor(stringmemoryname,stringmemorysymbol)ERC20(name,symbol){}// ERC-165 interface detection
functionsupportsInterface(bytes4interfaceId)publicviewvirtualoverridereturns(bool){returninterfaceId==type(IScaledUIAmount).interfaceId||interfaceId==type(IScaledUIAmountConversion).interfaceId||interfaceId==type(IScaledUIAmountBalances).interfaceId||super.supportsInterface(interfaceId);}// ============ Core Interface ============
functionuiMultiplier()publicviewoverridereturns(uint256){uint256currentTime=block.timestamp;if(currentTime>=_nextUiMultiplierEffectiveAt){return_nextUiMultiplier;}else{return_uiMultiplier;}}// Implementation-specific: How the multiplier is updated
functionsetUIMultiplier(uint256newMultiplier,uint256effectiveAtTimestamp)externalonlyOwner{require(newMultiplier>0,"Multiplier must be positive");uint256currentTime=block.timestamp;require(effectiveAtTimestamp>currentTime,"Effective At must be in the future");if(currentTime>_nextUiMultiplierEffectiveAt){uint256oldMultiplier=_nextUiMultiplier;_uiMultiplier=oldMultiplier;_nextUiMultiplier=newMultiplier;_nextUiMultiplierEffectiveAt=effectiveAtTimestamp;emitUIMultiplierUpdated(oldMultiplier,newMultiplier,effectiveAtTimestamp);}else{uint256oldMultiplier=_uiMultiplier;_nextUiMultiplier=newMultiplier;_nextUiMultiplierEffectiveAt=effectiveAtTimestamp;emitUIMultiplierUpdated(oldMultiplier,newMultiplier,effectiveAtTimestamp);}}// ============ Optional: Conversion Extension ============
functiontoUIAmount(uint256rawAmount)publicviewoverridereturns(uint256){uint256currentTime=block.timestamp;if(currentTime>=_nextUiMultiplierEffectiveAt){return(rawAmount*_nextUiMultiplier)/MULTIPLIER_DECIMALS;}else{return(rawAmount*_uiMultiplier)/MULTIPLIER_DECIMALS;}}functionfromUIAmount(uint256uiAmount)publicviewoverridereturns(uint256){uint256currentTime=block.timestamp;if(currentTime>=_nextUiMultiplierEffectiveAt){return(uiAmount*MULTIPLIER_DECIMALS)/_nextUiMultiplier;}else{return(uiAmount*MULTIPLIER_DECIMALS)/_uiMultiplier;}}// ============ Optional: Balances Extension ============
functionbalanceOfUI(addressaccount)publicviewoverridereturns(uint256){returntoUIAmount(balanceOf(account));}functiontotalSupplyUI()publicviewoverridereturns(uint256){returntoUIAmount(totalSupply());}// ============ Optional: TransferWithUIAmount Event ============
function_update(addressfrom,addressto,uint256amount)internalvirtualoverride{super._update(from,to,amount);uint256uiAmount=toUIAmount(amount);emitTransferWithUIAmount(from,to,amount,uiAmount);}}
Rationale
Design Decisions:
Separate UI Functions: Rather than modifying the core ERC-20 functions, we provide separate UI-specific functions. This ensures backward compatibility and allows integrators to opt-in to the UI scaling feature.
18 Decimal Precision: Using 18 decimals for the multiplier provides sufficient precision for most use cases while aligning with Ethereum’s standard decimal representation.
No Automatic Updates: The multiplier must be explicitly set by authorized parties, giving issuers full control over when and how adjustments are made.
Raw Amount Preservation: All actual token operations continue to use raw amounts, ensuring that the multiplier is purely a display feature and doesn’t affect the underlying token economics.
Optional Extensions: Following the pattern established by ERC-721 and ERC-1155, helper functions like toUIAmount, fromUIAmount, balanceOfUI, and totalSupplyUI are defined in separate optional interfaces. The TransferWithUIAmount event is defined in the core interface as optional since events do not affect interface IDs. This keeps the core interface minimal while allowing contracts to opt-in to additional on-chain functionality. Integrators can detect support for these extensions using ERC-165 interface detection.
Alternative Approaches Considered:
Rebasing Tokens: While rebasing tokens adjust supply automatically, they create complexity for integrators and can break composability with DeFi protocols.
Wrapper Tokens: Creating wrapper tokens for each adjustment event adds unnecessary complexity and gas costs.
Index/Exchange Rate Tokens confer similar advantages to the proposed Scaled UI approach, but is ultimately less intuitive and requires more calculations on the UI layers.
Off-chain Solutions: Purely off-chain solutions lack standardization and require trust in centralized providers.
Backwards Compatibility
This EIP is fully backwards compatible with ERC-20. Existing ERC-20 functions continue to work as expected, and the UI scaling features are opt-in through additional functions.
Test Cases
Example test scenarios:
Initial Multiplier Test:
Verify that initial multiplier is 1.0 (1e18)
Confirm balanceOf equals balanceOfUI initially
Stock Split Test:
Set multiplier to 2.0 (2e18) for 2-for-1 split
Verify UI balance is double the raw balance
Confirm conversion functions work correctly
Security Considerations
Multiplier Manipulation
Unauthorized changes to the UI multiplier could mislead users about their holdings
Implementations MUST use robust access control mechanisms
The setUIMultiplier function MUST be restricted to authorized addresses (e.g., contract owner or a designated role).
Integer Overflow
Risk of overflow when applying the multiplier
Use SafeMath or Solidity 0.8.0+ automatic overflow protection
User Confusion
Clear communication is essential when UI amounts differ from raw amounts
Integrators MUST clearly indicate when displaying UI-adjusted balances
Oracle Dependency
For automated multiplier updates, the system may depend on oracles
Oracle failures or manipulations could affect displayed balances
Overflow Protection: Implementations MUST handle potential overflow when applying the multiplier.
Implementation Guide for Integrators
Wallet Integration
Wallets supporting this standard should:
Check if a token implements IScaledUIAmount interface using ERC-165
Optionally check for IScaledUIAmountBalances extension for on-chain balance queries
Display both raw and UI amounts, clearly labeled
Compute UI balance off-chain using balanceOf() and uiMultiplier(), or use balanceOfUI() if the extension is supported
Handle transfers using raw amounts (standard ERC-20 functions)
Example JavaScript integration:
constMULTIPLIER_DECIMALS=BigInt(1e18);// Interface IDs for ERC-165 detectionconstISCALED_UI_AMOUNT_ID="0xa60bf13d";constISCALED_UI_BALANCES_ID="0xd890fd71";// Off-chain conversion functionsfunctiontoUIAmount(rawAmount,multiplier){return(BigInt(rawAmount)*BigInt(multiplier))/MULTIPLIER_DECIMALS;}functionfromUIAmount(uiAmount,multiplier){return(BigInt(uiAmount)*MULTIPLIER_DECIMALS)/BigInt(multiplier);}asyncfunctiondisplayBalance(tokenAddress,userAddress){consttoken=newethers.Contract(tokenAddress,ScaledUIAmountABI,provider);// Check if core scaled UI is supportedconstsupportsScaledUI=awaittoken.supportsInterface(ISCALED_UI_AMOUNT_ID);if(!supportsScaledUI){// Fall back to standard ERC-20constbalance=awaittoken.balanceOf(userAddress);return{display:formatUnits(balance,decimals),raw:formatUnits(balance,decimals),multiplier:"1.0"};}// Check if optional balances extension is supportedconstsupportsBalancesExt=awaittoken.supportsInterface(ISCALED_UI_BALANCES_ID);constrawBalance=awaittoken.balanceOf(userAddress);constmultiplier=awaittoken.uiMultiplier();// Use on-chain balanceOfUI if available, otherwise compute off-chainconstuiBalance=supportsBalancesExt?awaittoken.balanceOfUI(userAddress):toUIAmount(rawBalance,multiplier);return{display:formatUnits(uiBalance,decimals),raw:formatUnits(rawBalance,decimals),multiplier:formatUnits(multiplier,18)};}
Exchange Integration
Exchanges should:
Store and track the multiplier for each supported token
Display UI amounts in user interfaces
Use raw amounts for all internal accounting
Provide clear documentation about the scaling mechanism
Example implementation:
constMULTIPLIER_DECIMALS=BigInt(1e18);classScaledTokenHandler{// Off-chain conversion functionstoUIAmount(rawAmount,multiplier){return(BigInt(rawAmount)*BigInt(multiplier))/MULTIPLIER_DECIMALS;}fromUIAmount(uiAmount,multiplier){return(BigInt(uiAmount)*MULTIPLIER_DECIMALS)/BigInt(multiplier);}asyncprocessDeposit(tokenAddress,amount,isUIAmount){consttoken=newethers.Contract(tokenAddress,ScaledUIAmountABI,provider);letrawAmount;if(isUIAmount&&awaitthis.supportsScaledUI(tokenAddress)){constmultiplier=awaittoken.uiMultiplier();rawAmount=this.fromUIAmount(amount,multiplier);}else{rawAmount=amount;}// Process deposit with raw amountreturnthis.recordDeposit(tokenAddress,rawAmount);}asyncgetDisplayBalance(tokenAddress,userAddress){consttoken=newethers.Contract(tokenAddress,ScaledUIAmountABI,provider);constrawBalance=awaitthis.getInternalBalance(userAddress,tokenAddress);if(awaitthis.supportsScaledUI(tokenAddress)){constmultiplier=awaittoken.uiMultiplier();returnthis.toUIAmount(rawBalance,multiplier);}returnrawBalance;}}
DeFi Protocol Integration
DeFi protocols should:
Continue using raw amounts for all protocol operations
Provide UI helpers for displaying adjusted amounts
Emit events with both raw and UI amounts where relevant
Document clearly which amounts are used in calculations