Skip to main content

LSP1 - Universal Receiver

Standard Document

Introduction​

There is often the need for smart contracts to be aware of incoming transactions, especially receiving tokens.

A good example is ERC20 token transfers. When a smart contract receives a token, it has no generic way to be notified about it. During the token transfer, the sender's balance decreases, and the recipient's balance increases. There are no further interactions, and the ERC20 token contract just acts as a registry.

Smart contract recipient outside interaction

One solution to this problem could be to listen to all the ERC20 token transfer events on the network. However, this requires using a trusted third party to listen to the events. Such a method limits smart contracts autonomy and introduces a single point of failure.

Standards like ERC223, ERC721, and ERC1155 require having specific functions in order to receive and be notified about the token such as tokenReceived(..), onERC721Received(..), and onERC1155Received(..) respectively.

These functions have different contexts, parameters, and events that make the smart contract not interoperable. If later in the future, a token standard ERCXXXX gets adopted, the smart contract (account, DEXs) will not be able to receive these kinds of tokens because it was already deployed on the network and does not support onERCXXXXReceived(..) function.

On token received functions

One way to solve this problem is by creating a standard and unified function that any smart contract can implement. DEXs, Wallets, or profiles could use this function to be notified about an incoming asset, information, followers, etc.

Unified notification function to call

What does this standard represent?​

Specification​

recommendation

Smart contracts implementing the LSP1-UniversalReceiver standard SHOULD register the LSP1UniversalReceiver InterfaceId using ERC165. This way, other contracts can be aware that the contract supports the LSP1 standard.

This standard defines a single function named universalReceiver(...) that could receive any arbitrary information. It takes two parameters:

  • bytes32 typeId: Hash or Hook of a specific standard.
  • bytes data: Any arbitrary data.

Receiving contracts should consider the typeId parameter to decode the data correctly.

The universalReceiver(...) function emits an event with the data passed to it and some additional data.

universalReceiver function emits an event

For example, to notify the recipient that he is about to receive tokens, during a token transfer, the token contract can call the universalReceiver(..) function of the recipient with:

  • bytes32 typeId: Hash('ERCXXXXTokenReceived')
  • bytes data: packedData(amount of token sent, the sender address, the block timestamp)

In this way, instead of listening to all the events of the token contrats on the network, and checking which one of these transfers is relative to the recipient, users can listen to the UniversalReceiver event on the contract implementing the universalReceiver(..) and know the token transfer details.

As well as emitting an event, the universalReceiver(...) function can implement custom logic to make the contract behave differently based on the data received. Some ideas include:

  • Reverting on calls to completely disallow the smart contract from receiving assets, information, etc. ❌
  • Registering the received assets inside the contract storage (see LSP5 - Received Assets). ➕
  • Disallowing receiving specific tokens from specific token contract addresses, for instance (e.g: spam tokens).
  • Forwarding all the received assets to an external vault or a staking contract.
  • Forwarding specific tokens in a contract behind a protocol or dApp (e.g: liquidity or lending pool to earn interest).
  • Depending on the typeId, save a percentage % of tokens received (native tokens or not), by placing them in a vault for instance.

universalReceiver function execute custom logic

Extension​

info

See the LSP1-UniversalReceiverDelegate standard for more details.

Overriding and customizing the universalReceiver(..) function is an option for users to allow different behaviours depending on the data received. However, it's not advised to hardcode the logic of reacting to specific actions inside the function because this logic may need to change in the future depending on several factors (eg. the vault where the tokens are forwarded gets compromised, a new staking contract is deployed, decided to revert on specific tokens later).

LSP1-UniversalReceiverDelegate is an optional extension to the LSP1-UniversalReceiver standard. As well as notifying a contract about the incoming and outgoing transactions by emitting an event, it can delegate the call to an external contract that can handle and react to specific calls with its custom logic.

Universal Receiver Delegate contract

The address of the external contract can be stored and changed inside the contract storage. This way, users can customize such contracts to implement a specific logic that is changeable at any time.

Multiple Universal Receiver Delegate

References​