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 π.
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.
Setupβ
Make sure you have the following dependencies installed before beginning this tutorial:
- Either
web3.js
orethers.js
@lukso/lsp-smart-contracts
- ethers
- web3
npm install ethers @lukso/lsp-smart-contracts @erc725/erc725.js
npm install web3 @lukso/lsp-smart-contracts @erc725/erc725.js
Step 1 - Imports, constants and EOAβ
We need to;
- Get the ABIs of the contracts that we will use and the bytecode of the
LSP1UniversalReceiverDelegateUP
. - Store the address of our Universal Profile.
- Initialize the EOA that we will further use.
- ethers
- web3
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);
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β
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.
- ethers
- web3
// create a LSP1UniversalReceiverDelegateUP Contract Factory
let universalProfileURDFactory = new ethers.ContractFactory(
LSP1UniversalReceiverDelegateUP.abi,
LSP1UniversalReceiverDelegateUP.bytecode,
);
// 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.
- ethers
- web3
// deploy the Universal Receiver Delegate UP contract
const universalProfileURD = await universalProfileURDFactory
.connect(EOA)
.deploy();
// 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β
- ethers
- web3
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();
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.
- ethers
- web3
// create an instance of the Universal Profile
const universalProfile = new ethers.Contract(
universalProfileAddress,
UniversalProfile.abi,
);
// 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.
- ethers
- web3
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,
];
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.
- ethers
- web3
// update the Universal Profile data
await universalProfile.connect(EOA).setDataBatch(dataKeys, dataValues);
// update the Universal Profile data
await universalProfile.methods.setDataBatch(dataKeys, dataValues).send({
from: EOA.address,
gasLimit: 600_000,
});
Final codeβ
- ethers
- web3
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);
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β
- ethers
- web3
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);
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);