Skip to main content

Universal Receiver

This guide will teach you how to deploy and set the default implementation of the Universal Receiver Delegate (URD) used by the Universal Profile. The URD will register the addresses of the received assets and vaults and remove them on a balance equal to 0. It requires the SUPER_SETDATA and REENTRANCY permissions to interact with the πŸ†™.

warning

If you created your UP with my.universalprofile.cloud/create, the Universal Receiver Delegate contract is already set to our default LSP1UniversalReceiverDelegateUP.

Setting a new default Universal Receiver Delegate (URD) contract will replace this default one and might affect the behaviour of your UP (for instance, not register your newly received assets).

This guide is more intended for any generic ERC725Y contract that is looking to implement solely the LSP1 Delegate extension functionality.

Advantages​

The Universal Receiver is a powerful tool that enables any smart contract or dApp to identify specific incoming transactions and automatically initiate customized responses. This provides several 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 contract implementing LSP1 can host multiple Universal Receiver Delegate (URD) smart contracts. This enables one to manage how to respond to specific interactions based on your own predefined rules.

To delegate incoming Universal Receiver notifications to a specific smart contract, set an 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, you can assign your contract different Universal Receiver Delegate contracts, depending on which specific type IDs (bytes32) it received when its universalReceiver(...) function was called. This allows to host multiple Universal Receiver Delegate contracts.

UniversalReceiverDelegate setting data keys on profile

Setup​

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

Install the dependencies
npm install ethers @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 { ethers } from 'ethers';

// constants
const provider = new ethers.providers.JsonRpcProvider(
'https://rpc.testnet.lukso.network',
);
const URD_DATA_KEY = ERC725YDataKeys.LSP1.LSP1UniversalReceiverDelegate;
const universalProfileAddress = '0x...';

// setup your EOA
const privateKey = '0x...'; // your EOA private key (controller address)
const EOA = new ethers.Wallet(privateKey).connect(provider);

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 a LSP1UniversalReceiverDelegateUP Contract Factory
let universalProfileURDFactory = new ethers.ContractFactory(
LSP1UniversalReceiverDelegateUP.abi,
LSP1UniversalReceiverDelegateUP.bytecode,
);

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
const universalProfileURD = await universalProfileURDFactory
.connect(EOA)
.deploy();

Final code​

Deploy a Universal Receiver Delegate for the Universal Profile
const deployUniversalProfileURD = async () => {
// create a LSP1UniversalReceiverDelegateUP Contract Factory
let universalProfileURDFactory = new ethers.ContractFactory(
LSP1UniversalReceiverDelegateUP.abi,
LSP1UniversalReceiverDelegateUP.bytecode,
);

// deploy the Universal Receiver Delegate UP contract
const universalProfileURD = await universalProfileURDFactory
.connect(EOA)
.deploy();

return testnetuniversalProfileURD.address;
};

// 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 ethers.Contract(
universalProfileAddress,
UniversalProfile.abi,
);

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[
'getData(bytes32)'
](ERC725YDataKeys.LSP6['AddressPermissions[]'].length);

const addressPermissionsNewArrayLength =
ethers.toBigInt(addressPermissionsOldArrayLengthHex) + ethers.toBigInt(1);

const addressPermissionsNewArrayLengthHex =
'0x' + addressPermissionsNewArrayLength.toString(16).padStart(32, '0');

// 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.connect(EOA).setDataBatch(dataKeys, dataValues);

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 ethers.Contract(
universalProfileAddress,
UniversalProfile.abi,
);

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

const addressPermissionsNewArrayLength =
ethers.toBigInt(addressPermissionsOldArrayLengthHex) + ethers.toBigInt(1);

const addressPermissionsNewArrayLengthHex =
'0x' + addressPermissionsNewArrayLength.toString(16).padStart(32, '0');

// 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.connect(EOA).setDataBatch(dataKeys, dataValues);
};

// 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 { ethers } from 'ethers';
import { ERC725 } from '@erc725/erc725.js';

// constants
const provider = new ethers.JsonRpcProvider(
'https://rpc.testnet.lukso.network',
);
const URD_DATA_KEY = ERC725YDataKeys.LSP1.LSP1UniversalReceiverDelegate;
const universalProfileAddress = '0x...';

// setup your EOA
const privateKey = '0x...'; // your EOA private key (controller address)
const EOA = new ethers.Wallet(privateKey).connect(provider);

const deployUniversalProfileURD = async () => {
// create a LSP1UniversalReceiverDelegateUP Contract Factory
let universalProfileURDFactory = new ethers.ContractFactory(
LSP1UniversalReceiverDelegateUP.abi,
LSP1UniversalReceiverDelegateUP.bytecode,
);

// deploy the Universal Receiver Delegate UP contract
const universalProfileURD = await universalProfileURDFactory
.connect(EOA)
.deploy();

return testnetuniversalProfileURD.address;
};

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

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

const addressPermissionsNewArrayLength =
ethers.toBigInt(addressPermissionsOldArrayLengthHex) + ethers.toBigInt(1);

const addressPermissionsNewArrayLengthHex =
'0x' + addressPermissionsNewArrayLength.toString(16).padStart(64, '0');

// 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.connect(EOA).setDataBatch(dataKeys, dataValues);
};

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

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