TIP-1022: Virtual Addresses for TIP-20 Deposit Forwarding
Abstract
This TIP introduces virtual addresses: a reserved 20-byte address format that, when detected in TIP-20 recipient-bearing operations, causes the precompile to auto-credit a registered master wallet instead of the literal target address. This eliminates sweep transactions entirely for entities such as exchanges, ramps, and payment processors that generate per-user deposit addresses. Master registration is a one-time onchain call; deposit address derivation is fully offchain.
Motivation
-
Eliminate sweep transactions. Entities such as exchanges, ramps, and payment processors need to offer each customer a unique deposit address. Today, funds arriving at each address must be swept back to a central wallet in separate transactions, which is a large operational cost and burden at scale. Virtual addresses auto-credit the master wallet at the protocol level, making sweeps unnecessary.
-
Avoid the 250,000 gas new-account cost. Tempo charges 250,000 gas to create state for a new address on first use. With virtual addresses, deposit addresses never create onchain state, so the first transfer to a new deposit address costs the same as any other transfer.
-
Prevent state bloat. Without virtual addresses, each customer deposit address creates a new account in the state trie. At enterprise scale (millions of deposit addresses), this is significant and permanent state growth. Virtual addresses avoid this entirely: no accounts are created, regardless of how many deposit addresses a business generates.
Specification
Address Layout
Virtual addresses are standard 20-byte EVM addresses with the following reserved format:
[4-byte masterId] [10-byte MAGIC] [6-byte userTag]
= 20 bytes total
| Field | Bytes | Description |
|---|---|---|
| masterId | 4 | Deterministic identifier derived from (masterAddress, salt) via the registration hash. This is the registry lookup key. |
| VIRTUAL_MAGIC | 10 | Fixed magic value 0xFDFDFDFDFDFDFDFDFDFD. Identifies the address as virtual. |
| userTag | 6 | Opaque per-user identifier derived offchain by the operator. 48 bits support ~2.8×10^14 unique deposit addresses per master. |
Why This Layout?
TIP-1022 intentionally places the 10-byte magic sequence in the middle of the address instead of at the beginning. This preserves more visually useful bytes at the front and back of the address for operators and users comparing deposit addresses in wallets, explorers, etc.
The 4-byte masterId is kept short to preserve room for a large userTag, while the 10-byte magic keeps the format highly unlikely to appear accidentally. The security implications of this tradeoff are discussed in Security Considerations.
Conformance and Scope
TIP-1022 applies only to TIP-20 precompile recipient resolution for the entrypoints listed in Transfer Path Modification.
TIP-1022 does not alter TIP-20 methods that do not carry a recipient in the TIP-20 transfer path (e.g. approve, burn, permit) and does not alter non-TIP-20 protocol behavior.
Non-TIP-20 token transfers (e.g. ERC-20 contracts deployed on Tempo) to virtual addresses are not subject to TIP-1022 forwarding. Such transfers behave as standard EVM transfers to the literal address. Tokens sent this way may be irrecoverable — see Risks and Limitations.
setRewardRecipient is not a TIP-20 transfer-path operation and is therefore not subject to TIP-1022 recipient resolution. Implementations MUST reject virtual addresses when setting reward recipients so that rewards remain tied to canonical accounts rather than aliases.
Reserved Virtual Address Format
Any address whose bytes [4:14] equal VIRTUAL_MAGIC is treated as a virtual address by the TIP-20 precompile.
If a TIP-20 transfer targets such an address:
- the precompile extracts the
masterIdfrom bytes[0:4] - looks up the registered master
- credits the resolved master if registered
- otherwise reverts with
VirtualAddressUnregistered
The literal virtual address never accumulates TIP-20 balance through standard TIP-20 transfer paths.
Reserved Address Space
Addresses matching the virtual-address format occupy a reserved TIP-20 recipient namespace. A user who happens to control an EOA or contract whose address matches this format can still exist on Tempo and can still originate ordinary EVM transactions. However, TIP-20 transfers to such an address will follow TIP-1022 recipient resolution semantics rather than crediting the literal address.
Users who control such an address SHOULD NOT use it as a normal account on Tempo.
Master ID Derivation
The masterId is deterministic and derived from the registration hash computed during registerVirtualMaster():
registrationHash = keccak256(abi.encodePacked(msg.sender, salt))
masterId = bytes4(registrationHash[4:8])
The first 4 bytes of registrationHash are consumed by the proof-of-work check (see Registration Proof of Work); the masterId is extracted from bytes [4:8] of the same hash.
The salt is a bytes32 value chosen by the caller. Callers MUST grind the salt to satisfy the 32-bit proof-of-work requirement. The resulting masterId is permanently bound to the registration address.
Why masterId Registrations Are Immutable
TIP-1022 intentionally does not provide a mechanism to rotate or update the master address bound to a masterId. Allowing rotation would interact poorly with TIP-403 policies: a blacklisted master could rotate to a fresh address and resume receiving deposits, requiring policy enforcement to track masterIds in addition to addresses. Operators who need to change their underlying key material can register their masterId to an upgradeable proxy contract or multisig, allowing the controlling keys to be rotated at the contract layer without any protocol-level change. Finally, any rotation mechanism would require a timelock or similar delay to prevent an attacker who compromises a master key from silently redirecting deposits before the legitimate owner can respond — complexity that is better handled by the operator's own key management infrastructure.
In the event of a masterId collision (two (address, salt) pairs mapping to the same 4-byte masterId), the second registration reverts with MasterIdCollision. The caller can retry with a different valid salt. The probability of such a collision (and the resulting need to regrind another salt) is less than 0.1% even if 4 million masterId's have already been registered.
Registration Proof of Work
Registration requires a 32-bit proof of work to make targeted collisions against a chosen masterId computationally expensive.
The registration hash is computed as:
registrationHash = keccak256(abi.encodePacked(msg.sender, salt))
The first 4 bytes of registrationHash MUST be zero:
require(bytes4(registrationHash[0:4]) == 0x00000000) // 32-bit PoW
masterId = bytes4(registrationHash[4:8])
This requires the caller to grind ~2^32 salt values to find a valid registration. If the first 4 bytes are not zero, the call reverts with ProofOfWorkFailed.
This proof of work is intended to make it expensive for an attacker who sees a pending registration transaction to compute a different (attackerAddress, salt) pair that lands on the same masterId and gets mined first. With a 4-byte masterId and a 32-bit proof-of-work requirement, that targeted attack costs ~2^64 work.
User Tag Derivation (Offchain)
The userTag is an opaque 6-byte value generated offchain by the operator. The protocol does not interpret or validate it — all values including 0x000000000000 are valid. It exists solely so the operator can attribute deposits to specific users via the two-hop Transfer events described below.
Operators maintain their own internal mapping {internalUserId -> virtualAddress}. No onchain transaction is needed to create a new deposit address.
Worked Example
An exchange with master address 0xABCD...1234 registers with a salt that satisfies the 32-bit PoW:
registrationHash = keccak256(abi.encodePacked(0xABCD...1234, salt))registrationHash[0:4] == 0x00000000(PoW satisfied)masterId = bytes4(registrationHash[4:8])-> e.g.0x07A3B1C2- For customer #103048, the exchange derives a
userTag-> e.g.0xD4E5A7C3F19E
Virtual address = 0x07A3B1C2 FDFDFDFDFDFDFDFDFDFD D4E5A7C3F19E
^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^
masterId magic (10) userTag (6)
Registry Precompile
Virtual address resolution requires a registry that maps masterId -> masterAddress. This is managed through a new precompile deployed at 0xFDC0000000000000000000000000000000000000.
The registry MUST maintain the following mapping constraints:
- each
masterIdmaps to at most one registered master address (one-to-one frommasterId) - multiple
masterIds MAY map to the same master address (many-to-one)
This many-to-one design allows a single underlying wallet to register multiple masterIds (e.g. with different salts).
A valid master address MUST satisfy TIP-20 recipient safety constraints:
- MUST NOT be
address(0) - MUST NOT itself match the virtual-address format (
VIRTUAL_MAGICat bytes[4:14]) - MUST NOT be a TIP-20 token address (
0x20c000....at bytes[0:12])
Registry Storage Layout
Each masterId maps to a single 32-byte storage slot:
slot = keccak256(abi.encode(masterId, REGISTRY_SLOT))
value = masterType | reserved | masterAddress
^^^^^^^^^^ ^^^^^^^^ ^^^^^^^^^^^
1 byte 11 bytes 20 bytes
Here REGISTRY_SLOT means the storage slot of the mapping(bytes4 => bytes32) used to store registry entries, following standard Solidity mapping layout.
| Field | Bytes | Description |
|---|---|---|
masterType | 1 | Type discriminator for future extensibility. MUST be 0x00 in this version. |
reserved | 11 | Reserved for future use. MUST be zeroed. |
masterAddress | 20 | The registered master address for this masterId. address(0) if unregistered. |
This layout packs all metadata for a masterId into a single storage slot, enabling one SLOAD during transfer-path resolution.
Interface
interface IAddressRegistry {
// ──────────────────── Events ────────────────────
/// @notice Emitted when a new master is registered.
event MasterRegistered(
bytes4 indexed masterId,
address indexed masterAddress
);
// ──────────────────── Errors ────────────────────
/// @notice The computed masterId is already registered to a different address.
error MasterIdCollision();
/// @notice The caller/new master address is invalid for virtual forwarding.
error InvalidMasterAddress();
/// @notice The registration hash does not satisfy the 32-bit proof-of-work requirement.
error ProofOfWorkFailed();
/// @notice The virtual address has a valid format but its masterId is not registered.
error VirtualAddressUnregistered();
// ──────────────── Registration ──────────────────
/// @notice Registers msg.sender as a virtual address master.
/// @dev The registration hash is keccak256(abi.encodePacked(msg.sender, salt)).
/// The first 4 bytes of the hash MUST be zero (32-bit proof of work).
/// masterId is derived from bytes [4:8] of the registration hash.
/// Reverts with ProofOfWorkFailed if the first 4 bytes are not zero.
/// Reverts with InvalidMasterAddress if msg.sender is not a valid master address.
/// Reverts with MasterIdCollision if the derived masterId is already taken
/// by a different address. On collision, the caller can retry with a different salt.
/// The same address MAY register multiple masterIds using different salts.
/// @param salt Caller-chosen salt for masterId derivation. Must satisfy 32-bit PoW.
/// @return masterId The derived master identifier.
function registerVirtualMaster(bytes32 salt) external returns (bytes4 masterId);
// ────────────────── Queries ─────────────────────
/// @notice Returns the registered master address for a given masterId, or address(0) if unregistered.
function getMaster(bytes4 masterId) external view returns (address);
/// @notice Resolves a transfer recipient using TIP-1022 execution semantics.
/// For non-virtual addresses, returns `to` unchanged.
/// For virtual addresses, returns the registered master or reverts with
/// VirtualAddressUnregistered.
function resolveRecipient(address to) external view returns (address effectiveRecipient);
/// @notice Resolves a virtual address to its registered master.
/// Returns address(0) if the address does not match the virtual-address format.
/// Returns address(0) if the masterId is not registered.
function resolveVirtualAddress(address virtualAddr) external view returns (address master);
/// @notice Returns true if the address matches the virtual-address format.
function isVirtualAddress(address addr) external pure returns (bool);
/// @notice Decodes a virtual address into its components.
/// @return isVirtual True if the address matches the virtual-address format.
/// @return masterId The 4-byte master identifier (zero if not virtual).
/// @return userTag The 6-byte user tag (zero if not virtual).
function decodeVirtualAddress(address addr)
external pure returns (bool isVirtual, bytes4 masterId, bytes6 userTag);
}Constants
| Name | Value | Description |
|---|---|---|
VIRTUAL_MAGIC | 0xFDFDFDFDFDFDFDFDFDFD | 10-byte magic value identifying virtual addresses |
REGISTRY_ADDRESS | 0xFDC0000000000000000000000000000000000000 | Precompile address for the virtual-address registry |
Transfer Path Modification
The following existing TIP-20 entrypoints are modified to resolve the to (recipient) address before crediting:
transfertransferFromtransferWithMemotransferFromWithMemomintmintWithMemosystemTransferFrom
The from address on transferFrom, transferFromWithMemo, and systemTransferFrom is not affected by TIP-1022 resolution.
Resolution Logic
function resolveRecipient(to: address) -> address:
// Check bytes [4:14] against VIRTUAL_MAGIC
if to[4:14] != VIRTUAL_MAGIC:
return to
masterId = to[0:4]
master = registry.getMaster(masterId)
if master == address(0):
revert VirtualAddressUnregistered()
return masterStandard Transfer Entrypoints
For transfer, transferFrom, transferWithMemo, transferFromWithMemo, and systemTransferFrom:
- Resolve recipient: compute
effectiveRecipient = resolveRecipient(to). Iftois not virtual,effectiveRecipient = to. - Token-level sender check: apply the standard TIP-403 / TIP-1015 sender authorization rules.
- Token-level recipient check: apply the standard TIP-403 / TIP-1015 recipient authorization rules to
effectiveRecipient. - Apply balance changes: debit sender, credit
effectiveRecipient. - Emit events: per Event Emission (two-hop
Transferif virtual, singleTransferotherwise).
If any step reverts, the enclosing TIP-20 operation MUST revert atomically with no balance changes and no events.
Mint Entrypoints
For mint and mintWithMemo:
- Resolve recipient: compute
effectiveRecipient = resolveRecipient(to). Iftois not virtual,effectiveRecipient = to. - Token-level mint-recipient check: apply the standard TIP-1015 mint-recipient authorization rules to
effectiveRecipient. - Apply balance changes: credit
effectiveRecipient. - Emit events: per Event Emission.
If any step reverts, the enclosing TIP-20 operation MUST revert atomically with no balance changes and no events.
Authorization Semantics
TIP-1022 does not introduce new authorization logic in TIP-403 itself. Instead, TIP-20 transfer and mint logic MUST resolve virtual recipient addresses before invoking the existing TIP-403 / TIP-1015 checks.
Concretely, for any TIP-20 entrypoint covered by TIP-1022:
- Compute
effectiveRecipient = resolveRecipient(to). - Apply the existing sender / recipient / mint-recipient authorization rules to
effectiveRecipient, not the literal virtual address.
This preserves view/execution symmetry with the TIP-20 authorization path defined by TIP-1015: any internal TIP-20 helper such as isTransferAuthorized(from, to) MUST evaluate recipient authorization against the resolved master address when to is virtual.
balanceOf(virtualAddress) remains literal and MUST continue to return 0.
Contracts or integrators that need explicit resolution behavior outside the TIP-20 transfer path MAY call resolveRecipient on the registry.
Event Emission
TIP-1022 does not introduce new transfer-path events. The registry precompile emits MasterRegistered, but forwarding itself is represented using two-hop standard Transfer events: one hop showing funds arriving at the virtual address, and a second hop showing funds moving from the virtual address to the resolved master. Using standard Transfer events (rather than a new event type) preserves compatibility with existing indexers, block explorers, and wallets that already understand TIP-20 / ERC-20 Transfer events — no custom integration is required to track virtual address deposits.
For transfers where the recipient is not a virtual address, event emission is unchanged from standard TIP-20 behavior — a single Transfer(sender, to, amount).
Deposit Forwarding (Inbound)
When a transfer targets a virtual address (to is virtual), the precompile MUST emit two Transfer events in sequence:
Transfer(sender, virtualAddress, amount)— shows funds arriving at the virtual addressTransfer(virtualAddress, masterAddress, amount)— shows funds forwarding to the master
The actual balance change is applied only to masterAddress. The virtual address never holds a balance; the first Transfer event is a logical representation of deposit attribution, not a real balance credit.
Indexers that need deposit attribution SHOULD watch for pairs of Transfer events within the same transaction where the intermediate address matches the virtual-address format. The userTag can then be extracted from the virtual address to identify the depositor.
Entrypoint-Specific Event Ordering
-
transfer,transferFrom,systemTransferFrom:Transfer(sender, virtualAddress, amount)Transfer(virtualAddress, masterAddress, amount)
-
transferWithMemo,transferFromWithMemo:Transfer(sender, virtualAddress, amount)TransferWithMemo(sender, virtualAddress, amount, memo)Transfer(virtualAddress, masterAddress, amount)
-
mint:Transfer(address(0), virtualAddress, amount)Mint(virtualAddress, amount)Transfer(virtualAddress, masterAddress, amount)
-
mintWithMemo:Transfer(address(0), virtualAddress, amount)TransferWithMemo(address(0), virtualAddress, amount, memo)Mint(virtualAddress, amount)Transfer(virtualAddress, masterAddress, amount)
Self-Forwarding
If the registered master sends tokens to one of its own virtual addresses, the transfer resolves back to the master, effectively a transfer to self. The standard TIP-20 self-transfer semantics apply (no net balance change). The two-hop Transfer events are still emitted: Transfer(master, virtualAddress, amount) followed by Transfer(virtualAddress, master, amount). Indexers SHOULD NOT interpret this as net inflow when from == masterAddress in the first hop.
Interaction with TIP-403
Virtual address resolution happens before TIP-403 / TIP-1015 authorization checks. Policy evaluation uses the resolved masterAddress, not the literal virtual address.
- If the master address is not authorized to receive the token, transfers to any of its virtual addresses revert.
- If the sender is not authorized to send the token, the transfer reverts.
- Policies configured on individual virtual addresses are ignored by the TIP-20 transfer path because virtual addresses have no independent canonical TIP-20 balance.
Rejection of Virtual Addresses in Policy Operations
TIP-403 operations that accept addresses as policy members MUST reject virtual addresses rather than accepting them silently.
Implementations SHOULD use a clear, informative error indicating that virtual addresses are aliases for TIP-20 forwarding and are not valid literal policy subjects.
Rejecting these operations avoids the footgun where an operator configures policy on the virtual alias they see in logs or explorers instead of on the resolved master address that actually holds the funds.
Interaction with Account-Level Features
balanceOf(virtualAddress): Always returns 0. Virtual addresses do not hold balances.- Nonce / transaction origination: A contract or EOA whose address matches the virtual-address format can still exist and can still originate ordinary EVM transactions. TIP-1022 resolution applies only to the
tofield in TIP-20 precompile calls, not to transaction senders.
Security Considerations
4-Byte masterId and 32-Bit Registration PoW
A 4-byte masterId would be too small if its security relied only on raw namespace size. TIP-1022 does not rely on that. Instead, security comes from the combination of:
- a 4-byte
masterId, and - a 32-bit proof-of-work requirement on registration
An attacker who sees a pending registration transaction and wants to steal that masterId must compute a different (attackerAddress, salt) pair that:
- satisfies the 32-bit proof-of-work requirement, and
- lands on the same 4-byte
masterId
That targeted attack costs roughly 2^64 work. Further, because registration requires proof-of-work grinding, deployment will typically happen via dedicated tooling or a managed service that:
- performs the proof-of-work search,
- submits the registration transaction, and
- waits for confirmation or revert before the operator routes value through the resulting master ID.
This does not eliminate the residual collision-risk entirely, but it substantially reduces the practical chance that an operator incorrectly believes they control a master ID that was actually registered first by an attacker.
Why the Magic Bytes Are in the Middle
The middle VIRTUAL_MAGIC layout is a deliberate usability tradeoff:
- it leaves the first 4 bytes available for
masterId - it leaves the last 6 bytes available for
userTag - it avoids spending the most visually important bytes of the address on static marker data
This improves address comparison in UIs while still keeping a large reserved pattern that is highly unlikely to appear accidentally. We believe this layout is superior to the other permutations in terms of the prospect of address poisoning attacks (see below).
Contracts and EOAs Matching the Virtual Format
A sufficiently resourced adversary could, in principle, grind a CREATE2 deployment or private key so that a contract or EOA lands at an address matching the virtual-address format in a masterID controlled by the adversary. We view this as unlikely in practice because the address must match a 10-byte fixed magic value in the middle of the address, while targeted theft of a specific registered namespace also requires colliding the 4-byte masterId under the registration proof-of-work design (i.e., 14 bytes totally).
A stronger global reservation mechanism for problematic address ranges may still be desirable in the future.
Policy Configuration on Virtual Addresses
Virtual addresses are forwarding aliases, not canonical TIP-20 holders. Using them directly in policy configuration is misleading and dangerous because the TIP-20 transfer path evaluates policies against the resolved master address.
Accordingly, TIP-403 configuration operations SHOULD reject virtual addresses with explicit errors rather than accepting them.
Risks and Limitations
Address Poisoning and UI Confusion
TIP-1022 still introduces a recognizable structured address format. Wallets, block explorers, and operational tooling that truncate addresses SHOULD display enough of the address to distinguish both the masterId and the userTag; ideally they SHOULD show the full address.
Non-TIP-20 Token Loss
TIP-1022 forwarding applies exclusively to TIP-20 precompile operations. Non-TIP-20 tokens (e.g. ERC-20 contracts deployed on Tempo) transferred to a virtual address are credited to the literal virtual address by the ERC-20 contract and are irrecoverable: no recovery mechanism is defined here.
This risk is mitigated by the strong incentives for token issuers to use TIP-20 on Tempo (gas-payment eligibility, access to the payment lane, and policy support), but it remains a limitation of this design.
Non-TIP-20 Protocol Positions Minted to Virtual Addresses
TIP-1022 changes only the TIP-20 transfer and mint entrypoints listed in this document. It does not change other protocol logic that accepts an address parameter and records ownership against that literal address.
This creates an edge case for protocols that mint LP shares, receipt tokens, or other redeemable positions to a user-supplied to address. If such a protocol later requires the recorded holder address to burn, redeem, or withdraw, a position minted to a virtual address can become stranded even though the corresponding master account controls that virtual namespace. The Fee AMM is one example of this pattern: LP shares minted can be mited to a virtual address, but are then permanently unburnable since burn checks that msg_sender==lp_address.
In short, virtual-address forwarding is only defined for the TIP-20 paths enumerated by TIP-1022; other protocols remain literal-address systems unless they explicitly say otherwise.
Externally-Triggerable Revert on Unregistered Virtual Addresses
TIP-1022 introduces a recipient-dependent revert: if the to address matches the virtual-address format but its masterId is not registered, the transfer reverts with VirtualAddressUnregistered. This is the first TIP-20 revert condition that an untrusted recipient address can induce — prior to TIP-1022, transfers could only revert due to sender-side conditions (insufficient balance, authorization failure).
Contracts that perform batch transfers in a single transaction (e.g. payroll, airdrop, or distribution contracts) SHOULD validate recipient addresses before execution or wrap individual transfers in try/catch to prevent a single unregistered virtual address from reverting the entire batch.
Contracts and EOAs at virtual addresses
It is theoretically possible to deploy a contract or control an EOA whose address matches VIRTUAL_MAGIC, including by grinding CREATE2 salts or private keys. Such addresses can still exist and originate ordinary EVM transactions, but we consider this unlikely in practice because targeting the 10-byte VIRTUAL_MAGIC requires roughly 2^80 work, with additional cost for targeted collisions against registered virtual namespaces.
Invariants
Core Invariants
-
No fund loss: A TIP-20 transfer to a virtual address MUST either credit the registered master's balance by exactly the transfer amount, or revert. Funds MUST NOT be credited to the virtual address itself or lost.
-
Revert on unregistered: A transfer to an address matching the virtual-address format whose
masterIdis not registered MUST revert. It MUST NOT credit any account. -
Balance consistency: After a successful virtual-forwarded transfer of amount
X,balanceOf(master)MUST have increased by exactlyX. -
Zero-balance invariant: For every virtual address,
balanceOf(virtualAddress)MUST equal 0 from T3 activation onwards. Pre-T3, the TIP-20 precompile does not perform virtual-address resolution, so a transfer targeting an address that matches the virtual format will credit the literal address. Such pre-T3 balances are stranded (no party can claim them) and do not violate this invariant, which applies only to the T3-and-later transfer path. The probability of anyone controlling a private key for such an address is negligible. -
Event consistency: For virtual-forwarded entrypoints, the precompile MUST emit two
Transferevents:Transfer(sender, virtualAddress, amount)followed byTransfer(virtualAddress, masterAddress, amount).TransferWithMemoevents MUST immediately follow their matchingTransferand MUST usevirtualAddressas the recipient to preserve deposit attribution.Mintevents MUST usevirtualAddress. -
Non-virtual path unaffected: Transfers to addresses that do not match the virtual-address format MUST behave identically to pre-TIP-1022 semantics, with no registry lookup.
-
Deterministic masterId: Given
registrationHash = keccak256(abi.encodePacked(registrationAddress, salt)), the first 4 bytes ofregistrationHashMUST be zero, andmasterIdMUST equalbytes4(registrationHash[4:8]), whereregistrationAddressis the address that calledregisterVirtualMaster()andsaltis the caller-supplied salt. If the PoW check fails, registration MUST revert withProofOfWorkFailed.masterIdMUST NOT depend on registration order or transaction ordering. -
Master ID uniqueness: Each
masterIdMUST map to at most one registered master address. MultiplemasterIds MAY map to the same master address. -
Atomic revert behavior: If virtual resolution fails, the enclosing TIP-20 call MUST revert with no state changes and no events.
-
View/execution symmetry: TIP-20 authorization logic MUST evaluate recipient authorization against the resolved master address when
tois virtual, matching execution-time recipient resolution semantics. -
Policy on master: TIP-403 / TIP-1015 authorization for virtual-forwarded transfers and mints MUST check the resolved
masterAddress. Policies set on individual virtual addresses MUST be ignored by the TIP-20 transfer path. -
Policy-operation rejection: TIP-403 configuration operations that accept literal addresses as policy subjects or members MUST reject virtual addresses.
Was this helpful?