Skip to main content

Read Asset Data

Recommendation

Complete "ready to use" JSON and JS files are available at the end in the Final Code section.

In this guide, we will learn how to:

  • get all assets ever received by a profile.
  • get all assets ever issued by a profile.
  • fetch the data of all owned assets.
Asset example on universalprofile.cloud
The Lambada Dyed Red White Blue asset as seen on UniversalProfile.cloud

We will use:

Setup

Open a terminal in the project's folder of your choice and install all required libraries.

npm install web3 @erc725/erc725.js isomorphic-fetch @lukso/lsp-smart-contracts

Step 1 - Get all assets ever received

In the previous guide, we learned how to read the Universal Profile properties and use the data key names with the fetchData() function of the erc725.js library. In the same way, we can now fetch all ever received assets using fetchData('LSP5ReceivedAssets[]').

read_assets.js

// Import and network setup
const Web3 = require("web3");
const { ERC725 } = require("@erc725/erc725.js");
require("isomorphic-fetch");

// Static variables
const SAMPLE_PROFILE_ADDRESS = "0x0Ac71c67Fa5E4c9d4af4f99d7Ad6132936C2d6A3";
const RPC_ENDPOINT = "https://rpc.l14.lukso.network";
const IPFS_GATEWAY = "https://2eff.lukso.dev/ipfs/";

// Parameters for the ERC725 instance
const erc725schema = require("@erc725/erc725.js/schemas/LSP3UniversalProfileMetadata.json");
const provider = new Web3.providers.HttpProvider(RPC_ENDPOINT);
const config = { ipfsGateway: IPFS_GATEWAY };

/*
* Fetch the LSP5 data of the Universal Profile
* to get its ever received assets
*
* @param address of the Universal Profile
* @return address[] of received assets or custom error
*/
async function fetchReceivedAssets(address) {
try {
const profile = new ERC725(erc725schema, address, provider, config);
const result = await profile.fetchData("LSP5ReceivedAssets[]")
return result.value;
} catch (error) {
return console.log("This is not an ERC725 Contract");
}
}

// Debug
fetchReceivedAssets(SAMPLE_PROFILE_ADDRESS).then((profileData) =>
console.log(JSON.stringify(profileData, undefined, 2))
);

Step 2 - Get all assets ever issued

The same way, we fetched received assets, we can fetch all ever issued assets with the fetchData('LSP12IssuedAssets[]') function from the erc725.js library.

read_assets.js

// ...

/*
* Fetch the ever issued assets from
* the Universal Profile
*
* @param address of the Universal Profile
* @return address[] of the issued assets or custom error
*/
async function fetchIssuedAssets(address) {
try {
const profile = new ERC725(erc725schema, address, provider, config);
const result = await profile.fetchData("LSP12IssuedAssets[]");
return result.value;
} catch (error) {
return console.log("This is not an ERC725 Contract");
}
}

// Debug
fetchIssuedAssets(SAMPLE_PROFILE_ADDRESS).then((profileData) =>
console.log(JSON.stringify(profileData, undefined, 2))
);

Step 3 - Check ownership of assets

After receiving a list of asset addresses, we can check which assets are owned by the Universal Profile. We can do this by comparing the balances of the assets within the receiver contract. If the balance is greater than zero, the asset is still owned by the address.

Difference between Token Ownership
  • For LSP7, you will get back the amount of tokens you own.
  • For LSP8, you will get back the number of NFTs you own (without knowing which specific tokenId you own).
read_assets.js

// ...

// ABI for the asset
const LSP8 = require("@lukso/lsp-smart-contracts/artifacts/LSP8IdentifiableDigitalAsset.json");

// New Web3 instance for LUKSO L14
const web3 = new Web3("https://rpc.l14.lukso.network");

/*
* Return an array of assets
* that are owned by the
* Universal Profile.
*
* @param owner of the Universal Profile
* @return address[] of owned assets
*/
async function fetchOwnedAssets(owner) {
const digitalAssets = await fetchReceivedAssets(owner);
const ownedAssets = [];

for (let i = 0; i < digitalAssets.length; i++) {

// Create instance of the asset to check owner balance
const LSP8Contract = new web3.eth.Contract(LSP8.abi, digitalAssets[i]);

const isCurrentOwner = await LSP8Contract.methods.balanceOf(owner).call();
if (isCurrentOwner > 0) {
ownedAssets[ownedAssets.length] = digitalAssets[i];
}
}
return ownedAssets;
}

// Debug
fetchOwnedAssets(SAMPLE_PROFILE_ADDRESS).then((ownedAssets) =>
console.log(ownedAssets)
);

Step 4 - Check the type of an asset

Now that we have retrieved all the owned assets, we need to check which interface is behind these smart contract addresses, to get their data.

UniversalProfile contracts on the profile explorer on the LUKSO L14 test network have been deployed using different ERC725Y interfaces. We have to know which interface to use, to assure the right interaction and bypass errors.

info

By using function overloading, the ERC725Y interface function getData(...) can accept:

  • either one data key: getData(key) to fetch a single value.
  • or an array of data keys: getData(keys[]) to fetch multiple values at once.
read_assets.js

// ...

const SAMPLE_ASSET_ADDRESS = "0xfE85568Fea15A7ED3c56F7ca6544F2b96Aeb1774";
const LSP4 = require("@lukso/lsp-smart-contracts/artifacts/LSP4DigitalAssetMetadata.json");
const {
ERC725Y_INTERFACE_IDS,
} = require("@erc725/erc725.js/build/main/src/lib/constants");

/*
* Check the ERC725Y interface of an asset
*
* @param assetAddress of the smart contract
* @return boolean isERC725Y
*/
async function checkErc725YInterfaceId(assetAddress) {
// Create an instance of the asset
const asset = new web3.eth.Contract(LSP4.abi, assetAddress);

let isERC725Y = false;

// Check if the contract has a key-value store
try {
isERC725Y = await asset.methods
.supportsInterface(ERC725Y_INTERFACE_IDS["3.0"])
.call();
} catch (error) {
console.log("Address could not be checked for ERC725Y interface");
}
return isERC725Y;
}

// Debug
checkErc725YInterfaceId(SAMPLE_ASSET_ADDRESS).then((isERC725Y) =>
console.log(isERC725Y)
);

Step 5 - Receive the encoded asset data

Now we can safely retrieve the metadata for the asset address. The LSP4 metadata is saved under the ERC725Y key-value store of the digital asset. We need to input the correct data key to fetch the associated value. There are multiple LSP4 keys for different properties.

  • LSP4TokenName
  • LSP4TokenSymbol
  • LSP4Metadata
  • LSP4Creators[]

In this guide, we will use the LSP4Metadata key to read the asset metadata.

read_assets.js

// ...

// ABIs
const LSP4schema = require('@erc725/erc725.js/schemas/LSP4DigitalAsset.json');

// Data keys for asset properties
const TokenNameKey = LSP4schema[1].key;
const TokenSymbolKey = LSP4schema[2].key;
const MetaDataKey = LSP4schema[3].key;
const CreatorsKey = LSP4schema[4].key;

/*
* Get the dataset of an asset
*
* @param data key of the property to fetch
* @return string of the encoded data
*/
async function getAssetData(key, address) {
try {

// Instantiate the asset
const digitalAsset = new web3.eth.Contract(LSP4.abi, address);

// Get the encoded data
return await digitalAsset.methods["getData(bytes32)"](key).call();
} catch (error) {
return console.error("Data of assets address could not be loaded");
}
}

// Debug
getAssetData(MetaDataKey, SAMPLE_ASSET_ADDRESS).then((encodedData) =>
console.log(encodedData)
);

Step 6 - Decode the asset data

We can now decode the encoded metadata to fetch readable information. We use the decodeData() function from the erc725.js library. We will continue the step before and showcase decoding the LSP4Metadata key.

read_assets.js

// ...

/*
* Decode the value from ERC725Y storage
* based on its data key and phrase
*
* @param data key of the asset property to fetch
* @param encodedData as string
* @return JSON of the decoded data
*/
async function decodeAssetData(keyName, encodedData) {
try {

// Instance the asset
const digitalAsset = new ERC725(
LSP4schema,
SAMPLE_ASSET_ADDRESS,
provider,
config
);

// Decode the assets data
return digitalAsset.decodeData({
keyName: keyName,
value: encodedData,
});
} catch (error) {
console.log("Data of an asset could not be decoded");
}
}

// Debug
getAssetData(MetaDataKey, SAMPLE_ASSET_ADDRESS).then((encodedData) => {
decodeAssetData(MetaDataKey, encodedData).then((decodedData) =>
console.log(decodedData)
);
});

The LSP4 Digital Asset Metadata will resolve in a following JSON structure:

Show Metadata JSON response
{
key: '0x9afb95cacc9f95858ec44aa8c3b685511002e30ae54415823f406128b85b238e',
name: 'LSP4Metadata',
value: {
hashFunction: 'keccak256(utf8)',
hash: '0x...',
url: 'ipfs:...'
}
}

To fetch the data for the previously decoded metadata, we can access the JSON file and change the URL to access its properties. You may not need this library if you use browser environments like ReactJS or VueJS.

info

Profiles created on the Profile Explorer currently use IPFS. Therefore, we will use a static IPFS link for the guide. If there are several storage solutions, you can change them or make distinctions.

read_assets.js
// ...

/*
* Create a fetchable storage link that
* was embeded into the decoded asset data
*
* @param decodedAssetMetadata as JSON
* @return string of asset data URL
*/
async function getMetaDataLink(decodedAssetMetadata) {
try {
// Generate IPFS link from decoded metadata
return decodedAssetMetadata.value.url.replace('ipfs://', IPFS_GATEWAY);
} catch (error) {
console.log("URL could not be fetched");
}
}

// Debug
getAssetData(MetaDataKey, SAMPLE_ASSET_ADDRESS).then((encodedData) => {
decodeAssetData(MetaDataKey, encodedData).then((decodedData) => {
getMetaDataLink(decodedData).then((dataURL) => console.log(dataURL));
});
});

Step 8 - Get the asset data

We can now access the created storage link through a simple URL call and are using isomorphic-fetch to read the HTTP response from the asset URL in our node environment.

note

You may not need the isomorphic-fetch library if you use browser environments like ReactJS or VueJS.

read_assets.js

// ...

/*
* Fetch the asset data from the provided
* storage link
*
* @param dataURL as string
* @return JSON of asset data
*/
async function fetchAssetData(dataURL) {
try {
const response = await fetch(dataURL);
return await response.json();
} catch (error) {
console.log("JSON data of IPFS link could not be fetched");
}
}

// Debug
getAssetData(MetaDataKey, SAMPLE_ASSET_ADDRESS).then((encodedData) => {
decodeAssetData(MetaDataKey, encodedData).then((decodedData) => {
getMetaDataLink(decodedData).then((dataURL) => {
fetchAssetData(dataURL).then((assetJSON) => console.log(assetJSON));
});
});
});

For fetching LSP4Metadata, the JSON file will have the following structure:

Show JSON response
{
"LSP4Metadata": {
"description": "...",
"links": [
...
],
"images": [
[
{
"width": 1512,
"height": 1998,
"hashFunction": "keccak256(bytes)",
"hash": "0x...",
"url": "..."
},
...
]
],
"assets": [
{
"hash": "0x...",
"hashFunction": "keccak256(bytes)",
"fileType": "...",
"url": "..."
}
]
}
}

Final Code

Below is the complete code snippet of this guide, with all the steps compiled together.

read_assets.js

// Imports
const Web3 = require("web3");
const { ERC725 } = require("@erc725/erc725.js");
require("isomorphic-fetch");
const erc725schema = require("@erc725/erc725.js/schemas/LSP3UniversalProfileMetadata.json");
const LSP4schema = require("@erc725/erc725.js/schemas/LSP4DigitalAsset.json");
const LSP8 = require("@lukso/lsp-smart-contracts/artifacts/LSP8IdentifiableDigitalAsset.json");
const LSP4 = require("@lukso/lsp-smart-contracts/artifacts/LSP4DigitalAssetMetadata.json");
const {
ERC725Y_INTERFACE_IDS,
} = require("@erc725/erc725.js/build/main/src/lib/constants");

// Sample addresses
const SAMPLE_PROFILE_ADDRESS = "0x0Ac71c67Fa5E4c9d4af4f99d7Ad6132936C2d6A3";
const SAMPLE_ASSET_ADDRESS = "0xfE85568Fea15A7ED3c56F7ca6544F2b96Aeb1774";

// Network and storage
const RPC_ENDPOINT = "https://rpc.l14.lukso.network";
const IPFS_GATEWAY = "https://2eff.lukso.dev/ipfs/";

// Parameters for the ERC725 instance
const provider = new Web3.providers.HttpProvider(RPC_ENDPOINT);
const config = { ipfsGateway: IPFS_GATEWAY };

// Setup Web3
const web3 = new Web3("https://rpc.l14.lukso.network");

// Data keys for asset properties
const TokenNameKey = LSP4schema[1].key;
const TokenSymbolKey = LSP4schema[2].key;
const MetaDataKey = LSP4schema[3].key;
const CreatorsKey = LSP4schema[4].key;

/*
* Fetch the LSP5 data of the Universal Profile
* to get its ever received assets
*
* @param address of the Universal Profile
* @return address[] of received assets or custom error
*/
async function fetchReceivedAssets(address) {
try {
const profile = new ERC725(erc725schema, address, provider, config);
const result = await profile.fetchData("LSP5ReceivedAssets[]")
return result.value;
} catch (error) {
return console.log("This is not an ERC725 Contract");
}
}

/*
* Fetch the ever issued assets from
* the Universal Profile
*
* @param address of the Universal Profile
* @return address[] of the issued assets or custom error
*/
async function fetchIssuedAssets(address) {
try {
const profile = new ERC725(erc725schema, address, provider, config);
const result = await profile.fetchData("LSP12IssuedAssets[]");
return result.value;
} catch (error) {
return console.log("This is not an ERC725 Contract");
}
}

/*
* Return an array of assets
* that are owned by the
* Universal Profile.
*
* @param owner of the Universal Profile
* @return address[] of owned assets
*/
async function fetchOwnedAssets(owner) {
const digitalAssets = await fetchReceivedAssets(owner);
const ownedAssets = [];

for (let i = 0; i < digitalAssets.length; i++) {
// Create instance of the asset to check owner balance
const LSP8Contract = new web3.eth.Contract(LSP8.abi, digitalAssets[i]);

const isCurrentOwner = await LSP8Contract.methods.balanceOf(owner).call();
if (isCurrentOwner > 0) {
ownedAssets[ownedAssets.length] = digitalAssets[i];
}
}
return ownedAssets;
}

/*
* Check the ERC725Y interface of an asset
*
* @param assetAddress of the smart contract
* @return boolean isERC725Y
*/
async function checkErc725YInterfaceId(assetAddress) {
// Create an instance of the asset
const asset = new web3.eth.Contract(LSP4.abi, assetAddress);

let isERC725Y = false;

// Check if the contract has a key-value store
try {
isERC725Y = await asset.methods
.supportsInterface(ERC725Y_INTERFACE_IDS["3.0"])
.call();
} catch (error) {
console.log("Address could not be checked for ERC725Y interface");
}

return isERC725Y;
}

/*
* Get the dataset of an asset
*
* @param data key of the property to fetch
* @return string of the encoded data
*/
async function getAssetData(key, address) {
try {

// Instantiate the asset
const digitalAsset = new web3.eth.Contract(LSP4.abi, address);

// Get the encoded data
return await digitalAsset.methods["getData(bytes32)"](key).call();
} catch (error) {
return console.error("Data of assets address could not be loaded");
}
}

/*
* Decode the value from ERC725Y storage
* based on its data key and phrase
*
* @param data key of the asset property to fetch
* @param encodedData as string
* @return JSON of the decoded data
*/
async function decodeAssetData(keyName, encodedData) {
try {
// Instanciate the asset
const digitalAsset = new ERC725(
LSP4schema,
SAMPLE_ASSET_ADDRESS,
provider,
config
);

// Decode the assets data
return digitalAsset.decodeData({
keyName: keyName,
value: encodedData,
});
} catch (error) {
console.log("Data of an asset could not be decoded");
}
}

/*
* Create a fetchable storage link that
* was embeded into the decoded asset data
*
* @param decodedAssetMetadata as JSON
* @return string of asset data URL
*/
async function getMetaDataLink(decodedAssetMetadata) {
try {
// Generate IPFS link from decoded metadata
return decodedAssetMetadata.value.url.replace('ipfs://', IPFS_GATEWAY);
} catch (error) {
console.log("URL could not be fetched");
}
}

/*
* Fetch the asset data from the provided
* storage link
*
* @param dataURL as string
* @return string with asset data as JSON
*/
async function fetchAssetData(dataURL) {
try {
const response = await fetch(dataURL);
return await response.json();
} catch (error) {
console.log("JSON data of IPFS link could not be fetched");
}
}

// Step 1
fetchReceivedAssets(SAMPLE_PROFILE_ADDRESS).then((profileData) =>
console.log(JSON.stringify(profileData, undefined, 2))
);

// Step 2
fetchIssuedAssets(SAMPLE_PROFILE_ADDRESS).then((profileData) =>
console.log(JSON.stringify(profileData, undefined, 2))
);

// Step 3
fetchOwnedAssets(SAMPLE_PROFILE_ADDRESS).then((ownedAssets) =>
console.log(ownedAssets)
);

// Step 4
checkErc725YInterfaceId(SAMPLE_ASSET_ADDRESS).then((isERC725Y) =>
console.log(isERC725Y)
);

// Step 5
getAssetData(MetaDataKey, SAMPLE_ASSET_ADDRESS).then((encodedData) =>
console.log(encodedData)
);

// Step 6
getAssetData(MetaDataKey, SAMPLE_ASSET_ADDRESS).then((encodedData) => {
decodeAssetData(MetaDataKey, encodedData).then((decodedData) =>
console.log(decodedData)
);
});

// Step 7
getAssetData(MetaDataKey, SAMPLE_ASSET_ADDRESS).then((encodedData) => {
decodeAssetData(MetaDataKey, encodedData).then((decodedData) => {
getMetaDataLink(decodedData).then((dataURL) => console.log(dataURL));
});
});

// Step 8
getAssetData(MetaDataKey, SAMPLE_ASSET_ADDRESS).then((encodedData) => {
decodeAssetData(MetaDataKey, encodedData).then((decodedData) => {
getMetaDataLink(decodedData).then((dataURL) => {
fetchAssetData(dataURL).then((assetJSON) => console.log(assetJSON));
});
});
});