Skip to main content

Universal Receiver

The Universal Receiver is a powerful tool that enables any smart contract or dApp to identify specific incoming transactions and automatically initiate customized responses.

Advantages

  • Flexibility: The Universal Receiver offers flexibility in assigning customized responses to external contracts.
  • Customization: These responses can be assigned to external contracts, known as Universal Receiver Delegate, which can have their own unique mechanisms for various purposes.

A Universal Profile contract can host multiple Universal Receiver Delegate (URD) smart contracts. This enables an active digital presence on the blockchain, where interactions are managed and responded to based on your own predefined rules.

To delegate incoming Universal Receiver notifications to a specific smart contract, simply set a ERC725Y data key on your Universal Profile. This will instruct your profile to forward notifications to the designated contract.

You can a Universal Receiver Delegate contracts using one of the following data keys:

  • LSP1UniversalReceiverDelegate to set the default one.
  • LSP1UniversalReceiverDelegate:<bytes32> to set a specific one related to the mapped <bytes32> typeId.

This way, different Universal Receiver Delegate contracts can be assigned for certain type IDs (bytes32) that are sent along the Universal Receiver call from a notifying smart contract. This allows to host multiple Universal Receiver Delegate contracts.

warning

Setting a new default Universal Receiver Delegate (URD) contract will replace the default delegate contract, which can be found here.

Getting Started

This guide will teach you how to deploy and set the default implementation of the Universal Receiver Delegate (URD) used by the Universal Profile. This contract will register the addresses of the received assets and vaults and will remove them on a balance equal to 0. This contract requires the SUPER_SETDATA Permission to interact with the profile through the KeyManager.

UniversalReceiverDelegate setting data keys on profile

Setup

Make sure you have the following dependencies installed before beginning this tutorial:

Install the dependencies
npm install web3 @lukso/lsp-smart-contracts @erc725/erc725.js

Step 1 - Imports, constants and EOA

We need to;

  1. Get the ABIs of the contracts that we will use and the bytecode of the LSP1UniversalReceiverDelegateUP.
  2. Store the address of our Universal Profile.
  3. Initialize the EOA that we will further use.
Imports, Constants & EOA
import LSP1UniversalReceiverDelegateUP from '@lukso/lsp-smart-contracts/artifacts/LSP1UniversalReceiverDelegateUP.json';
import UniversalProfile from '@lukso/lsp-smart-contracts/artifacts/UniversalProfile.json';
import {
ERC725YDataKeys,
PERMISSIONS,
} from '@lukso/lsp-smart-contracts/constants.js';
import Web3 from 'web3';

// constants
const web3 = new Web3('https://rpc.testnet.lukso.network');
const URD_DATA_KEY = ERC725YDataKeys.LSP1.LSP1UniversalReceiverDelegate;
const universalProfileAddress = '0x...';

// setup your EOA
const privateKey = '0x...';
const EOA = web3.eth.accounts.wallet.add(privateKey);

Step 2 - Deploy the default Universal Receiver Delegate contract

info

The Universal Profile and the Vault don't use the same implementation of the Universal Receiver Delegate.

Create a contract instance

At this step we will create an instance of the Universal profile URD that we will further be used to deploy one.

Contract instance of the Universal Profile URD
// create an instance of the LSP1UniversalReceiverDelegateUP
let universalProfileURD = new web3.eth.Contract(
LSP1UniversalReceiverDelegateUP.abi,
);

Send the contract deployment transaction

Send the deployment transaction to get a newly deployed URD.

Send the transaction for deploying a new Universal Profile URD
// deploy the Universal Receiver Delegate UP contract
await universalProfileURD
.deploy({
data: LSP1UniversalReceiverDelegateUP.bytecode,
})
.send({
from: EOA.address,
gas: 5_000_000,
gasPrice: '1000000000',
});

Final code

Deploy a Universal Receiver Delegate for the Universal Profile
const deployUniversalProfileURD = async () => {
// create an instance of the LSP1UniversalReceiverDelegateUP
const universalProfileURD = new web3.eth.Contract(
LSP1UniversalReceiverDelegateUP.abi,
);
let universalProfileURDAddress;

// deploy the Universal Receiver Delegate UP contract
await universalProfileURD
.deploy({
data: LSP1UniversalReceiverDelegateUP.bytecode,
})
.send({
from: EOA.address,
gas: 5_000_000,
gasPrice: '1000000000',
})
.on('receipt', (receipt) => {
universalProfileURDAddress = receipt.contractAddress;
});

return universalProfileURDAddress;
};

// deploy a new Universal Profile URD and retrieve its address
const universalProfileURDAddress = await deployUniversalProfileURD();

Step 3 - Set the address of the URD in the storage

After deploying the contract, we need to set its address under the LSP1-UniversalReceiverDelegate Data Key and grant it the SUPER_SETDATA permission.

Create an UP contract instance

Firstly we need to create an instance for the Universal Profile contract.

Contract instance for the Universal Profile
// create an instance of the Universal Profile
const universalProfile = new web3.eth.Contract(
UniversalProfile.abi,
universalProfileAddress,
);

Register URD on the UP + set the URD permissions

Generate Data Keys & Values for adding a URD to the Universal Profile and for granting SUPER_SETDATA permission to the URD.

Encode Data Keys & Values for updating the URD and its permissions
import { ERC725 } from '@erc725/erc725.js';

const addressPermissionsOldArrayLengthHex = await universalProfile.methods[
'getData(bytes32)'
](ERC725YDataKeys.LSP6['AddressPermissions[]'].length).call();

const addressPermissionsNewArrayLength =
web3.utils.hexToNumber(addressPermissionsOldArrayLengthHex) + 1;

const addressPermissionsNewArrayLengthHex = web3.utils.padLeft(
web3.utils.numberToHex(addressPermissionsNewArrayLength),
32,
);

// create bytes32 permission value for the LSP1 Delegate
const lsp1DelegatePermissions = ERC725.encodePermissions({
SUPER_SETDATA: true,
REENTRANCY: true,
});

// bytes16 index `addressPermissionsOldArrayLengthHex` will serve as index
const newElementIndexInArrayHex =
addressPermissionsOldArrayLengthHex.substring(2);

const dataKeys = [
URD_DATA_KEY,
ERC725YDataKeys.LSP6['AddressPermissions[]'].length,
ERC725YDataKeys.LSP6['AddressPermissions[]'].index +
newElementIndexInArrayHex,
ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] +
universalProfileURDAddress.substring(2),
];
const dataValues = [
universalProfileURDAddress,
addressPermissionsNewArrayLengthHex,
universalProfileURDAddress,
lsp1DelegatePermissions,
];

Set the URD and its permissions

Lastly, we need to send the transaction that will update the URD and its permissions on the Universal Profile.

Update the data on the Universal Profile
// update the Universal Profile data
await universalProfile.methods.setDataBatch(dataKeys, dataValues).send({
from: EOA.address,
gasLimit: 600_000,
});

Final code

Update the URD of the Universal Profile and its permissions
import { ERC725 } from '@erc725/erc725.js';

const updateUniversalProfileURD = async (universalProfileURDAddress) => {
// create an instance of the Universal Profile
const universalProfile = new web3.eth.Contract(
UniversalProfile.abi,
universalProfileAddress,
);

const addressPermissionsOldArrayLengthHex = await universalProfile.methods[
'getData(bytes32)'
](ERC725YDataKeys.LSP6['AddressPermissions[]'].length).call();

const addressPermissionsNewArrayLength =
web3.utils.hexToNumber(addressPermissionsOldArrayLengthHex) + 1;

const addressPermissionsNewArrayLengthHex = web3.utils.padLeft(
web3.utils.numberToHex(addressPermissionsNewArrayLength),
32,
);

// create bytes32 permission value for the LSP1 Delegate
const lsp1DelegatePermissions = ERC725.encodePermissions({
SUPER_SETDATA: true,
REENTRANCY: true,
});

// bytes16 index `addressPermissionsOldArrayLengthHex` will serve as index
const newElementIndexInArrayHex =
addressPermissionsOldArrayLengthHex.substring(2);

const dataKeys = [
URD_DATA_KEY,
ERC725YDataKeys.LSP6['AddressPermissions[]'].length,
ERC725YDataKeys.LSP6['AddressPermissions[]'].index +
newElementIndexInArrayHex,
ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] +
universalProfileURDAddress.substring(2),
];
const dataValues = [
universalProfileURDAddress,
addressPermissionsNewArrayLengthHex,
universalProfileURDAddress,
lsp1DelegatePermissions,
];

// update the Universal Profile data
await universalProfile.methods.setDataBatch(dataKeys, dataValues).send({
from: EOA.address,
gasLimit: 600_000,
});
};

// update the URD of the Universal profile
await updateUniversalProfileURD(universalProfileURDAddress);

Final code - Deploy & Update

Deploy a Universal Profile URD, update its permissions and add it to the Universal Profile
import LSP1UniversalReceiverDelegateUP from '@lukso/lsp-smart-contracts/artifacts/LSP1UniversalReceiverDelegateUP.json';
import UniversalProfile from '@lukso/lsp-smart-contracts/artifacts/UniversalProfile.json';
import {
ERC725YDataKeys,
PERMISSIONS,
} from '@lukso/lsp-smart-contracts/constants.js';
import Web3 from 'web3';
import { ERC725 } from '@erc725/erc725.js';

// constants
const web3 = new Web3('https://rpc.testnet.lukso.network');
const URD_DATA_KEY = ERC725YDataKeys.LSP1.LSP1UniversalReceiverDelegate;
const universalProfileAddress = '0x...';

// setup your EOA
const privateKey = '0x...';
const EOA = web3.eth.accounts.wallet.add(privateKey);

const deployUniversalProfileURD = async () => {
// create an instance of the LSP1UniversalReceiverDelegateUP
const universalProfileURD = new web3.eth.Contract(
LSP1UniversalReceiverDelegateUP.abi,
);
let universalProfileURDAddress;

// deploy the Universal Receiver Delegate UP contract
await universalProfileURD
.deploy({
data: LSP1UniversalReceiverDelegateUP.bytecode,
})
.send({
from: EOA.address,
gas: 5_000_000,
gasPrice: '1000000000',
})
.on('receipt', (receipt) => {
universalProfileURDAddress = receipt.contractAddress;
});

return universalProfileURDAddress;
};

const updateUniversalProfileURD = async (universalProfileURDAddress) => {
// create an instance of the Universal Profile
const universalProfile = new web3.eth.Contract(
UniversalProfile.abi,
universalProfileAddress,
);

const addressPermissionsOldArrayLengthHex = await universalProfile.methods[
'getData(bytes32)'
](ERC725YDataKeys.LSP6['AddressPermissions[]'].length).call();

const addressPermissionsNewArrayLength =
web3.utils.hexToNumber(addressPermissionsOldArrayLengthHex) + 1;

const addressPermissionsNewArrayLengthHex = web3.utils.padLeft(
web3.utils.numberToHex(addressPermissionsNewArrayLength),
32,
);

// create bytes32 permission value for the LSP1 Delegate
const lsp1DelegatePermissions = ERC725.encodePermissions({
SUPER_SETDATA: true,
REENTRANCY: true,
});

// bytes16 index `addressPermissionsOldArrayLengthHex` will serve as index
const newElementIndexInArrayHex =
addressPermissionsOldArrayLengthHex.substring(2);

const dataKeys = [
URD_DATA_KEY,
ERC725YDataKeys.LSP6['AddressPermissions[]'].length,
ERC725YDataKeys.LSP6['AddressPermissions[]'].index +
newElementIndexInArrayHex,
ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] +
universalProfileURDAddress.substring(2),
];
const dataValues = [
universalProfileURDAddress,
addressPermissionsNewArrayLengthHex,
universalProfileURDAddress,
lsp1DelegatePermissions,
];

// update the Universal Profile data
await universalProfile.methods.setDataBatch(dataKeys, dataValues).send({
from: EOA.address,
gasLimit: 600_000,
});
};

// deploy a new Universal Profile URD and retrieve its address
const universalProfileURDAddress = await deployUniversalProfileURD();

// update the URD of the Universal profile
await updateUniversalProfileURD(universalProfileURDAddress);