Accept & Reject Assets
Each user can create its own custom Universal Receiver Delegate contract that holds its own logic to be executed once the universalReceiver(..)
function on his profile is called.
Rejecting all Assets
In order to reject all the assets that are being transferred to the profile, we need to create a Universal Receiver Delegate contract that reverts when it's the case of asset transfer (LSP7 & LSP8). The typeId
is the parameter that will give us more context on the call being made.
e.g.
If
typeId
is0x429ac7a06903dbc9c13dfcb3c9d11df8194581fa047c96d7a4171fc7402958ea
_TYPEID_LSP7_TOKENSRECIPIENT, then we know that we are receiving a LSP7 Token.If
typeId
is0x20804611b3e2ea21c480dc465142210acf4a2485947541770ec1fb87dee4a55c
_TYPEID_LSP8_TOKENSRECIPIENT, then we know that we are receiving a LSP8 Token.
Deploy contract through Remix
The first step is to navigate to Remix's website and create a new solidity file under the contracts folder.
After creating the UniversalReceiverDelegate.sol file, copy the code snippet below inside the file created. This code snippet will be responsible for rejecting all LSP7 & LSP8 assets being transferred to your profile.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
// This code is only used for guides puprose, it is working but not verified nor audited.
// interfaces
import {LSP1UniversalReceiverDelegateUP} from "https://github.com/lukso-network/lsp-smart-contracts/blob/v0.6.2/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol";
// constants
import {_TYPEID_LSP7_TOKENSRECIPIENT} from "https://github.com/lukso-network/lsp-smart-contracts/blob/v0.6.2/contracts/LSP7DigitalAsset/LSP7Constants.sol";
import {_TYPEID_LSP8_TOKENSRECIPIENT} from "https://github.com/lukso-network/lsp-smart-contracts/blob/v0.6.2/contracts/LSP8IdentifiableDigitalAsset/LSP8Constants.sol";
contract CustomUniversalReceiverDelegate is LSP1UniversalReceiverDelegateUP {
/**
* @param asset The address of the asset being transferred to the UniversalProfile.
* @param asset The address disallowing receiving assets.
*/
error ReceivingAssetsNotAllowed(address asset, address recipient);
/**
* @dev Reverts when the typeId is relative to token receiving (LSP7 & LSP8)
* @param caller The address of the asset informing the `universalReceiver(..)` function on the UniversalProfile.
* @param value The amount of native tokens sent by the caller to the universalReceiver function on the UniversalProfile.
* @param typeId The typeId representing the context of the call to the universalReceiver function on the UniversalProfile.
* @param typeId The data sent to the universalReceiver function on the UniversalProfile.
*/
function universalReceiverDelegate(
address caller,
uint256 value,
bytes32 typeId,
bytes memory data
) public override returns (bytes memory result) {
if (typeId == _TYPEID_LSP7_TOKENSRECIPIENT || typeId == _TYPEID_LSP8_TOKENSRECIPIENT){
revert ReceivingAssetsNotAllowed(caller, msg.sender);
}
}
}
Please make sure to unlock MetaMask and disable Browser Extension while doing this step.
After copying the code, navigate to the Solidity Compiler tab and press the Compile UniversalReceiverDelegate.sol button. Then navigate to the Deploy & Run Transactions tab and choose Injected Provider as the environment.
You should be connected to L16 in MetaMask and Remix and have enough LYXt in the EOA used to deploy the URD.
After choosing the CustomUniversalReceiverDelegate contract in the CONTRACT section and deploying, you'll confirm the transaction and wait until the transaction is confirmed and the contract is deployed on the network. Once deployed, you can copy the contract address to be used later when setting the address inside the storage.
Set the address of the URD in the UP's storage
After deploying the contract, we need to set its address under the LSP1-UniversalReceiverDelegate Data Key inside the UP's storage.
Install dependencies
Make sure you have the following dependencies installed before beginning this tutorial:
- Either
web3.js
orethers.js
@lukso/lsp-smart-contracts
- web3.js
- ethers.js
npm install web3 @lukso/lsp-smart-contracts
npm install ethers @lukso/lsp-smart-contracts
Imports, contants and EOA
First, we need to get the ABIs for the contracts that we will use later.
After that we need to store the address of our Universal Profile and the new URD address.
Then we will initialize the controller address that will be used later.
- web3.js
- ethers.js
import UniversalProfile from '@lukso/lsp-smart-contracts/artifacts/UniversalProfile.json';
import LSP6KeyManager from '@lukso/lsp-smart-contracts/artifacts/LSP6KeyManager.json';
import { ERC725YDataKeys } from '@lukso/lsp-smart-contracts/constants.js';
import Web3 from 'web3';
// constants
const web3 = new Web3('https://rpc.l16.lukso.network');
const URD_DATA_KEY = ERC725YDataKeys.LSP1.LSP1UniversalReceiverDelegate;
const universalProfileAddress = '0x...';
const universalProfileURDAddress = '0x...';
// setup your EOA
const privateKey = '0x...';
const EOA = web3.eth.accounts.wallet.add(privateKey);
import UniversalProfile from '@lukso/lsp-smart-contracts/artifacts/UniversalProfile.json';
import LSP6KeyManager from '@lukso/lsp-smart-contracts/artifacts/LSP6KeyManager.json';
import { ERC725YDataKeys } from '@lukso/lsp-smart-contracts/constants.js';
import { ethers } from 'ethers';
// constants
const provider = new ethers.JsonRpcProvider('https://rpc.l16.lukso.network');
const URD_DATA_KEY = ERC725YDataKeys.LSP1.LSP1UniversalReceiverDelegate;
const universalProfileAddress = '0x...';
const universalProfileURDAddress = '0x...';
// setup your EOA
const privateKey = '0x...'; // your EOA private key (controller address)
const EOA = new ethers.Wallet(privateKey).connect(provider);
Create contract instances
At this point we need to create instances of the following contracts:
- web3.js
- ethers.js
// create an instance of the Universal Profile
const universalProfile = new web3.eth.Contract(
UniversalProfile.abi,
universalProfileAddress,
);
// get the owner of the Universal Profile
// in our case it should be the address of the Key Manager
const keyManagerAddress = await universalProfile.methods.owner().call();
// create an instance of the LSP6KeyManager
const keyManager = new web3.eth.Contract(LSP6KeyManager.abi, keyManagerAddress);
// create an instance of the Universal Profile
const universalProfile = new ethers.Contract(
universalProfileAddress,
UniversalProfile.abi,
);
// get the owner of the Universal Profile
// in our case it should be the address of the Key Manager
const keyManagerAddress = await universalProfile.methods.owner().call();
// create an instance of the LSP6KeyManager
const keyManager = new ethers.Contract(keyManagerAddress, LSP6KeyManager.abi);
Encode setData(...)
calldata
Encode a calldata for setData(bytes32,bytes)
that will update the URD of the Universal Profile.
- web3.js
- ethers.js
// encode setData Calldata on the Universal Profile
const setDataCalldata = await universalProfile.methods[
'setData(bytes32,bytes)'
](URD_DATA_KEY, universalProfileURDAddress).encodeABI();
// encode setData Calldata on the Universal Profile
const setDataCalldata = await universalProfile.interface.encodeFunctionData(
'setData(bytes32,bytes)',
[URD_DATA_KEY, universalProfileURDAddress],
);
Execute via the Key Manager
Finally, we need to send the transaction that will update the URD of the Universal Profile via the Key Manager.
- web3.js
- ethers.js
// execute the `setDataCalldata` on the Key Manager
await keyManager.methods['execute(bytes)'](setDataCalldata).send({
from: EOA.address,
gasLimit: 600_000,
});
// execute the `setDataCalldata` on the Key Manager
await keyManager.connect(EOA)['execute(bytes)'](setDataCalldata);
Final code
- web3.js
- ethers.js
import UniversalProfile from '@lukso/lsp-smart-contracts/artifacts/UniversalProfile.json';
import LSP6KeyManager from '@lukso/lsp-smart-contracts/artifacts/LSP6KeyManager.json';
import { ERC725YDataKeys } from '@lukso/lsp-smart-contracts/constants.js';
import Web3 from 'web3';
// constants
const web3 = new Web3('https://rpc.l16.lukso.network');
const URD_DATA_KEY = ERC725YDataKeys.LSP1.LSP1UniversalReceiverDelegate;
const universalProfileAddress = '0x...';
const universalProfileURDAddress = '0x...';
// setup your EOA
const privateKey = '0x...';
const EOA = web3.eth.accounts.wallet.add(privateKey);
// create an instance of the Universal Profile
const universalProfile = new web3.eth.Contract(
UniversalProfile.abi,
universalProfileAddress,
);
// get the owner of the Universal Profile
// in our case it should be the address of the Key Manager
const keyManagerAddress = await universalProfile.methods.owner().call();
// create an instance of the Key Manager
const keyManager = new web3.eth.Contract(LSP6KeyManager.abi, keyManagerAddress);
// encode setData Calldata on the Vault
const setDataCalldata = await universalProfile.methods[
'setData(bytes32,bytes)'
](URD_DATA_KEY, universalProfileURDAddress).encodeABI();
// execute the executeCalldata on the Key Manager
await keyManager.methods['execute(bytes)'](executeCalldata).send({
from: EOA.address,
gasLimit: 600_000,
});
import UniversalProfile from '@lukso/lsp-smart-contracts/artifacts/UniversalProfile.json';
import LSP6KeyManager from '@lukso/lsp-smart-contracts/artifacts/LSP6KeyManager.json';
import { ERC725YDataKeys } from '@lukso/lsp-smart-contracts/constants.js';
import { ethers } from 'ethers';
// constants
const provider = new ethers.JsonRpcProvider('https://rpc.l16.lukso.network');
const URD_DATA_KEY = ERC725YDataKeys.LSP1.LSP1UniversalReceiverDelegate;
const universalProfileAddress = '0x...';
const universalProfileURDAddress = '0x...';
// setup your EOA
const privateKey = '0x...'; // your EOA private key (controller address)
const EOA = new ethers.Wallet(privateKey).connect(provider);
// create an instance of the Universal Profile
const universalProfile = new ethers.Contract(
universalProfileAddress,
UniversalProfile.abi,
);
// get the owner of the Universal Profile
// in our case it should be the address of the Key Manager
const keyManagerAddress = await universalProfile.methods.owner().call();
// create an instance of the Key Manager
const keyManager = new ethers.Contract(keyManagerAddress, LSP6KeyManager.abi);
// encode setData Calldata on the Vault
const setDataCalldata = await universalProfile.interface.encodeFunctionData(
'setData(bytes32,bytes)',
[URD_DATA_KEY, universalProfileURDAddress],
);
// execute the executeCalldata on the Key Manager
await keyManager.connect(EOA)['execute(bytes)'](executeCalldata);
Accepting specific Assets
To accept specific assets, you should differentiate between the different assets being transferred to you. One way to do it is to have a mapping inside the URD contract that states if the asset being transferred is allowed to be received or not. Only the owner should be allowed to add these asset addresses. For simplicity, the owner could be the EOA address deploying the contract.
Repeat the deployment steps in Rejecting all Assets section and replace the solidity code with the one written below.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
// This code is only used for guides puprose, it is working but not verified nor audited.
// modules
import {LSP1UniversalReceiverDelegateUP} from "https://github.com/lukso-network/lsp-smart-contracts/blob/v0.6.2/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol";
// constants
import {_TYPEID_LSP7_TOKENSRECIPIENT} from "https://github.com/lukso-network/lsp-smart-contracts/blob/v0.6.2/contracts/LSP7DigitalAsset/LSP7Constants.sol";
import {_TYPEID_LSP8_TOKENSRECIPIENT} from "https://github.com/lukso-network/lsp-smart-contracts/blob/v0.6.2/contracts/LSP8IdentifiableDigitalAsset/LSP8Constants.sol";
contract CustomUniversalReceiverDelegate is LSP1UniversalReceiverDelegateUP {
address immutable public owner;
mapping (address => bool) public allowedAssets;
constructor(address _owner){
owner = _owner;
}
modifier onlyOwner(){
require(msg.sender == owner, "CustomUniversalReceiverDelegate : Caller is not the owner");
_;
}
function setAllowedAssets(address assets) public onlyOwner {
allowedAssets[assets] = true;
}
/**
* @dev Reverts when the asset being transferred is not allowed. If allowed, the address of the asset
* will be registered inside the storage, and removed when balance of the asset equal 0, according to
* the LSP5-ReceivedAssers standard.
*
* @param caller The address of the asset informing the `universalReceiver(..)` function on the UniversalProfile.
* @param value The amount of native tokens sent by the caller to the universalReceiver function on the UniversalProfile.
* @param typeId The typeId representing the context of the call to the universalReceiver function on the UniversalProfile.
* @param typeId The data sent to the universalReceiver function on the UniversalProfile.
*/
function universalReceiverDelegate(
address caller,
uint256 value,
bytes32 typeId,
bytes memory data
) public override returns (bytes memory result){
// checking if the asset being transferred is allowed or not.
if(typeId == _TYPEID_LSP8_TOKENSRECIPIENT || typeId == _TYPEID_LSP7_TOKENSRECIPIENT){
require(allowedAssets[caller], "Asset being transferred is not allowed to be received");
}
// using the default implementation code to register the address of assets received
result = super.universalReceiverDelegate(caller, value, typeId, data);
}
}
The code above will register the address of the assets allowed and remove them when the UP's balance for this asset is equal to 0. It will also reject assets that are not allowed. Since this code will need SUPER_SETDATA Permission, after deploying you will set the address of the URD in the storage using the code from the Set the address of the URD in the storage section.