| id | TIP-1026 |
|---|---|
| title | Token Logo URI |
| description | Adds a logoURI string field to TIP-20 tokens for on-chain token icon metadata. |
| authors | Dan Robinson |
| status | Approved |
| related | TIP-20 |
| protocolVersion | T5 |
Adds a logoURI field to TIP-20 tokens — a string capped at 256 bytes, mutable by the token admin and validated against a small allowlist of URI schemes. This allows wallets and explorers to read a token's icon URI directly from the contract for metadata distribution; curation/verification of trusted tokens remains an off-chain concern (see Out of Scope).
Token icons are currently distributed through an off-chain token list registry (tokenlist.tempo.xyz). Wallets, explorers, and other apps must query this external service to display token icons, and issuers must submit a PR to a separate repository after deploying their token.
Adding logoURI as a first-class on-chain field makes token metadata self-describing: any token deployed with TIP-1026 metadata gets discoverability without an off-chain registry round-trip. The 256-byte cap prevents abuse (e.g., storing excessively large strings that could degrade indexer or explorer performance).
This TIP is limited to metadata distribution. It does not define which tokens are trusted, verified, or suitable for integration by wallets, DEXes, or explorers. Decisions around trust and curation, such as gas tokens or swappable assets remain offchain and at the discretion of integrators.
The following functions are added to ITIP20:
/// @notice Returns the logo URI for this token
/// @return The logo URI string (max 256 bytes)
function logoURI() external view returns (string memory);
/// @notice Sets the logo URI for this token (requires DEFAULT_ADMIN_ROLE)
/// @param newLogoURI The new logo URI (must be <= 256 bytes and, if non-empty,
/// a valid URI with an allowed scheme — see Behavior).
/// @dev Reverts with LogoURITooLong if the URI exceeds 256 bytes.
/// Reverts with InvalidLogoURI if the URI is non-empty and either not
/// syntactically a URI or its scheme is not in the allowlist.
/// An empty string is valid and clears the logo URI.
function setLogoURI(string calldata newLogoURI) external;/// @notice Emitted when the logo URI is updated.
/// @param updater The account that performed the update.
/// @param newLogoURI The new logo URI.
event LogoURIUpdated(address indexed updater, string newLogoURI);/// @notice The provided logo URI exceeds the maximum length of 256 bytes
error LogoURITooLong();
/// @notice The provided logo URI is non-empty and is either not a syntactically
/// valid URI or its scheme is not in the allowlist (see Behavior).
error InvalidLogoURI();logoURI()returns the current logo URI. Returns an empty string if not set.setLogoURI(string)updates the logo URI. Restricted toDEFAULT_ADMIN_ROLE.-
Reverts with
LogoURITooLongifbytes(newLogoURI).length > 256. -
Reverts with
InvalidLogoURIifnewLogoURIis non-empty and either does not have a scheme parseable per RFC 3986 §3.1 (scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )followed by:) or the scheme is not in the allowlist:Scheme Notes httpsRecommended for general web-hosted assets. httpAllowed for completeness; integrators should prefer https.ipfsRecommended for content-addressed assets. dataUseful for small inline images; subject to the 256-byte cap. Scheme matching is ASCII-case-insensitive (e.g.
HTTPSandhttpsare equivalent). -
Empty strings (
"") are explicitly valid and clear the logo URI; no scheme check is performed in that case.
-
A new Solidity overload of TIP20Factory.createToken is added that accepts an additional logoURI argument:
function createToken(
string calldata name,
string calldata symbol,
string calldata currency,
address quoteToken,
address admin,
bytes32 salt,
string calldata logoURI
) external returns (address);The new overload validates logoURI with the same rules as setLogoURI. Validation MAY occur before or after deployment, but a rejected URI MUST cause the entire transaction to revert so no partially-created token is observable. If logoURI is non-empty, the overload writes it and emits LogoURIUpdated from the new token's address with updater = msg.sender. An empty logoURI skips both the slot write and the event.
This TIP is non-breaking. The existing createToken selector and TokenCreated event remain unchanged, new functionality is additive and gated behind the activating hardfork, and existing integrations continue to work without modification.
- HTTPS:
https://example.com/icon.png(~40–120 bytes) - IPFS:
ipfs://QmXfzKRvjZz3u5JRgC4v5mGVbm9ahrUiB4DgzHBsnWbTMM(~53 bytes)
Square aspect ratio is recommended. Rasterized formats (PNG / WebP / static, single-frame) are recommended over SVG; integrators that accept SVG must follow the SVG-handling guidance in Security Considerations.
The protocol enforces only structural checks: a 256-byte length cap and a scheme allowlist (https, http, ipfs, data). The resolved endpoint and its content must be treated as untrusted by all consumers.
The protocol only validates URI structure. Integrators are responsible for accounting for tracking, as direct logo fetches expose user metadata such as IP address to the endpoint operator, for executable SVG content which should not be rendered inline, for image decoder vulnerabilities since all fetched images are untrusted input, and for impersonation since tokens may claim arbitrary branding and trust must come from offchain curation.
- Length cap.
bytes(logoURI()).length <= 256must always hold. - Backwards compatibility. The legacy 6-argument
createTokenselector and theTokenCreatedevent signature are unchanged by this TIP. - Admin-only mutation.
setLogoURIMUST revert withUnauthorizedfor any caller other than the token admin; only the admin can mutatelogoURI. - URI validation.
setLogoURIand the 7-argumentcreateTokenoverload MUST reject any non-empty URI that has no parseable scheme (RFC 3986 §3.1) or whose scheme is not in the allowlist (https,http,ipfs,data, ASCII-case-insensitive), reverting withInvalidLogoURI. Oversized URIs MUST revert withLogoURITooLong. - Factory atomicity. The 7-argument
createTokenoverload is atomic: a rejectedlogoURIMUST revert the entire transaction and leave the address returned bygetTokenAddress(sender, salt)undeployed.