-
Notifications
You must be signed in to change notification settings - Fork 812
Add ERC: Scaled UI Amount Extension for ERC-20 Tokens #1283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
cridmann
wants to merge
9
commits into
ethereum:master
Choose a base branch
from
superstateinc:add/scaled-ui
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 4 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
a747edc
feat: ERC-8043 - Scaled UI Amount Extension for ERC-20 Tokens
cridmann 94281c1
chore: formatting
cridmann 552f6b6
chore: formatting
cridmann 25d0cf0
chore: formatting
cridmann 8dc7cd3
fix: rename to EIP-8056
cridmann 7f49013
Update ERCS/erc-8056.md
cridmann d0dd90c
fix: link to images
cridmann bd17628
fix: link to images
cridmann 7696bcf
fix: link to images
cridmann File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,313 @@ | ||
| --- | ||
| eip: 8043 | ||
| title: Scaled UI Amount Extension for ERC-20 Tokens | ||
| description: Equity Token support for Stock Splits | ||
| author: Chris Ridmann (@cridmann) <[email protected]>, Daniel Gretzke (@gretzke) | ||
| discussions-to: | ||
|
Check failure on line 6 in ERCS/erc-8043.md
|
||
| status: Draft | ||
| type: Standards Track | ||
| category: ERC | ||
| created: 2025-10-20 | ||
| requires: 20 | ||
| --- | ||
|
|
||
| ## Abstract | ||
|
|
||
| This EIP proposes a standard extension to [ERC-20](./eip-20.md) 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. | ||
|
|
||
| ### Interface: | ||
|
|
||
| ```solidity | ||
|
|
||
| interface IScaledUIAmount { | ||
| // Emitted when the UI multiplier is updated | ||
| event UIMultiplierUpdated(uint256 oldMultiplier, uint256 newMultiplier, uint256 setAtTimestamp, uint256 effectiveAtTimestamp); | ||
|
|
||
| // Returns the current UI multiplier | ||
| // Multiplier is represented with 18 decimals (1e18 = 1.0) | ||
| function uiMultiplier() external view returns (uint256); | ||
|
|
||
| // Converts a raw token amount to UI amount | ||
| function toUIAmount(uint256 rawAmount) external view returns (uint256); | ||
|
|
||
| // Converts a UI amount to raw token amount | ||
| function fromUIAmount(uint256 uiAmount) external view returns (uint256); | ||
|
|
||
| // Returns the UI-adjusted balance of an account | ||
| function balanceOfUI(address account) external view returns (uint256); | ||
|
|
||
| // Updates the UI multiplier (only callable by authorized role) | ||
| function setUIMultiplier(uint256 newMultiplier, uint256 effectiveAtTimestamp) external; | ||
| } | ||
|
|
||
| ``` | ||
|
|
||
| ### Implementation Requirements: | ||
|
|
||
| 1. Multiplier Precision: The UI multiplier MUST use 18 decimal places for precision (1e18 represents a multiplier of 1.0). | ||
|
|
||
| 2. Backwards Compatibility: The standard ERC-20 functions (balanceOf, transfer, transferFrom, etc.) MUST continue to work with raw amounts. | ||
|
|
||
| 3. Event Emission: The UIMultiplierUpdated event MUST be emitted whenever the multiplier is changed. | ||
|
|
||
| ### Reference Implementation | ||
|
|
||
|
|
||
| ```solidity | ||
| contract ScaledUIToken is ERC20, IScaledUIAmount, Ownable { | ||
| uint256 private constant MULTIPLIER_DECIMALS = 1e18; | ||
| uint256 private _uiMultiplier = MULTIPLIER_DECIMALS; // Initially 1.0 | ||
| uint256 public _nextUiMultiplier = MULTIPLIER_DECIMALS; | ||
| uint256 public _nextUiMultiplierEffectiveAt = 0; | ||
|
|
||
| constructor(string memory name, string memory symbol) ERC20(name, symbol) {} | ||
|
|
||
| function uiMultiplier() public view override returns (uint256) { | ||
| uint256 currentTime = block.timestamp; | ||
| if (currentTime >= _nextUiMultiplierEffectiveAt) { | ||
| return _nextUiMultiplier; | ||
| } else { | ||
| return _uiMultiplier; | ||
| } | ||
| } | ||
|
|
||
| function toUIAmount(uint256 rawAmount) public view override returns (uint256) { | ||
| uint256 currentTime = block.timestamp; | ||
| if (currentTime >= _nextUiMultiplierEffectiveAt) { | ||
| return (rawAmount * _nextUiMultiplier) / MULTIPLIER_DECIMALS; | ||
| } else { | ||
| return (rawAmount * _uiMultiplier) / MULTIPLIER_DECIMALS; | ||
| } | ||
| } | ||
|
|
||
| function fromUIAmount(uint256 uiAmount) public view override returns (uint256) { | ||
| if (currentTime >= _nextUiMultiplierEffectiveAt) { | ||
| return (uiAmount * MULTIPLIER_DECIMALS) / _nextUiMultiplier; | ||
| } else { | ||
| return (uiAmount * MULTIPLIER_DECIMALS) / _uiMultiplier; | ||
| } | ||
|
|
||
| } | ||
|
|
||
| function balanceOfUI(address account) public view override returns (uint256) { | ||
| return toUIAmount(balanceOf(account)); | ||
| } | ||
|
|
||
| function setUIMultiplier(uint256 newMultiplier, uint256 effectiveAtTimestamp) external override onlyOwner { | ||
| require(newMultiplier > 0, "Multiplier must be positive"); | ||
|
|
||
| uint256 currentTime = block.timestamp; | ||
| require(effectiveAtTimestamp > currentTime, "Effective At must be in the future"); | ||
|
|
||
| if (currentTime > _nextUiMultiplierEffectiveAt) { | ||
| uint256 oldMultiplier = _nextUiMultiplier; | ||
| _uiMultiplier = oldMultiplier; | ||
| _nextUiMultiplier = newMultiplier; | ||
| _nextUiMultiplierEffectiveAt = effectiveAtTimestamp; | ||
| emit UIMultiplierUpdated(oldMultiplier, newMultiplier, block.timestamp, effectiveAtTimestamp); | ||
| } else { | ||
| uint256 oldMultiplier = _uiMultiplier; | ||
| _nextUiMultiplier = newMultiplier; | ||
| _nextUiMultiplierEffectiveAt = effectiveAtTimestamp; | ||
| emit UIMultiplierUpdated(oldMultiplier, newMultiplier, block.timestamp, effectiveAtTimestamp); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| ``` | ||
| ## Rationale | ||
|
|
||
|
|
||
| Design Decisions: | ||
|
|
||
| 1. 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. | ||
|
|
||
| 2. 18 Decimal Precision: Using 18 decimals for the multiplier provides sufficient precision for most use cases while aligning with Ethereum's standard decimal representation. | ||
|
|
||
| 3. No Automatic Updates: The multiplier must be explicitly set by authorized parties, giving issuers full control over when and how adjustments are made. | ||
|
|
||
| 4. 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. | ||
|
|
||
| Alternative Approaches Considered: | ||
|
|
||
| 1. Rebasing Tokens: While rebasing tokens adjust supply automatically, they create complexity for integrators and can break composability with DeFi protocols. | ||
|
|
||
| 2. Wrapper Tokens: Creating wrapper tokens for each adjustment event adds unnecessary complexity and gas costs. | ||
|
|
||
| 3. 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. | ||
|
|
||
| 4. 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: | ||
|
|
||
| 1. Initial Multiplier Test: | ||
|
|
||
| - Verify that initial multiplier is 1.0 (1e18) | ||
|
|
||
| - Confirm balanceOf equals balanceOfUI initially | ||
|
|
||
| 2. 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 | ||
|
|
||
|
|
||
| 1. 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). | ||
|
|
||
| 2. Integer Overflow | ||
|
|
||
| - Risk of overflow when applying the multiplier | ||
|
|
||
| - Use SafeMath or Solidity 0.8.0+ automatic overflow protection | ||
|
|
||
| 3. User Confusion | ||
|
|
||
| - Clear communication is essential when UI amounts differ from raw amounts | ||
|
|
||
| - Integrators MUST clearly indicate when displaying UI-adjusted balances | ||
|
|
||
| 4. Oracle Dependency | ||
|
|
||
| - For automated multiplier updates, the system may depend on oracles | ||
|
|
||
| - Oracle failures or manipulations could affect displayed balances | ||
|
|
||
| 5. Overflow Protection: Implementations MUST handle potential overflow when applying the multiplier. | ||
|
|
||
| ### Implementation Guide for Integrators | ||
|
|
||
| #### Wallet Integration | ||
|
|
||
| Wallets supporting this standard should: | ||
|
|
||
| 1. Check if a token implements IScaledUIAmount interface | ||
|
|
||
| 2. Display both raw and UI amounts, clearly labeled | ||
|
|
||
| 3. Use balanceOfUI() for primary balance display | ||
|
|
||
| 4. Handle transfers using raw amounts (standard ERC-20 functions) | ||
|
|
||
| **Example JavaScript integration:** | ||
|
|
||
| ```javascript | ||
| async function displayBalance(tokenAddress, userAddress) { | ||
| const token = new ethers.Contract(tokenAddress, ScaledUIAmountABI, provider); | ||
|
|
||
| // Check if scaled UI is supported | ||
| const supportsScaledUI = await supportsInterface(tokenAddress, SCALED_UI_INTERFACE_ID); | ||
|
|
||
| if (supportsScaledUI) { | ||
| const uiBalance = await token.balanceOfUI(userAddress); | ||
| const rawBalance = await token.balanceOf(userAddress); | ||
| const multiplier = await token.uiMultiplier(); | ||
|
|
||
| return { | ||
| display: formatUnits(uiBalance, decimals), | ||
| raw: formatUnits(rawBalance, decimals), | ||
| multiplier: formatUnits(multiplier, 18) | ||
| }; | ||
| } else { | ||
| // Fall back to standard ERC-20 | ||
| const balance = await token.balanceOf(userAddress); | ||
| return { | ||
| display: formatUnits(balance, decimals), | ||
| raw: formatUnits(balance, decimals), | ||
| multiplier: "1.0" | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| ``` | ||
|
|
||
| #### Exchange Integration | ||
|
|
||
| Exchanges should: | ||
|
|
||
| 1. Store and track the multiplier for each supported token | ||
|
|
||
| 2. Display UI amounts in user interfaces | ||
|
|
||
| 3. Use raw amounts for all internal accounting | ||
|
|
||
| 4. Provide clear documentation about the scaling mechanism | ||
|
|
||
| Example implementation: | ||
|
|
||
| ```javascript | ||
| class ScaledTokenHandler { | ||
| async processDeposit(tokenAddress, amount, isUIAmount) { | ||
| const token = new ethers.Contract(tokenAddress, ScaledUIAmountABI, provider); | ||
|
|
||
| let rawAmount; | ||
| if (isUIAmount && await this.supportsScaledUI(tokenAddress)) { | ||
| rawAmount = await token.fromUIAmount(amount); | ||
| } else { | ||
| rawAmount = amount; | ||
| } | ||
|
|
||
| // Process deposit with raw amount | ||
| return this.recordDeposit(tokenAddress, rawAmount); | ||
| } | ||
|
|
||
| async getDisplayBalance(tokenAddress, userAddress) { | ||
| const token = new ethers.Contract(tokenAddress, ScaledUIAmountABI, provider); | ||
| const rawBalance = await this.getInternalBalance(userAddress, tokenAddress); | ||
|
|
||
| if (await this.supportsScaledUI(tokenAddress)) { | ||
| return await token.toUIAmount(rawBalance); | ||
| } | ||
| return rawBalance; | ||
| } | ||
| } | ||
|
|
||
| ``` | ||
|
|
||
| #### DeFi Protocol Integration | ||
|
|
||
| DeFi protocols should: | ||
|
|
||
| 1. Continue using raw amounts for all protocol operations | ||
|
|
||
| 2. Provide UI helpers for displaying adjusted amounts | ||
|
|
||
| 3. Emit events with both raw and UI amounts where relevant | ||
|
|
||
| 4. Document clearly which amounts are used in calculations | ||
|
|
||
|
|
||
| ## Copyright | ||
|
|
||
| Copyright and related rights waived via [CC0](../LICENSE.md). | ||
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.