Skip to main content

Read LSP8 NFT Metadata

If your digital asset contract is an LSP8 NFT or Collection, you can fetch the metadata of specific token IDs.

There are two different ways to retrieve the metadata of a tokenId based on the configuration of the contract.

Method 1 - Call getDataForTokenId(...) (recommended)Method 2 - Read LSP8TokenMetadataBaseURI
This method retrieves individual metadata directly from the contract. Suitable for any token ID formats, including non-sequential token IDs, token IDs created after contract deployment, or if the metadata was updated for a certain subset.With this method, you can dynamically generate the URL to individual metadata files. This is usually done when the token IDs of an NFT or Collection can be pre-determined by a sequence, are set up in advance, or if metadata is uploaded or updated in bulk.
Order of Data Fetches

Due to different versions of setting and fetching token IDs based on the asset conditions, returned values might be empty. Therefore, it's recommended to first call the getDataForTokenId(...) to check for specific metadata. If the metadata is not set on the token ID, continue to retrieve the LSP8TokenMetadataBaseURI of the LSP8 contract.

If neither Token ID Metadata nor Base URI are set, you should fall back and fetch the Global Token Information, potentially including a Base URI to retrieve the JSON content.

Setup​

We will need the following dependencies to follow this guide:

npm i ethers @erc725/erc725.js @lukso/lsp-smart-contracts

Create instance of the LSP8 Contract​

Create an instance of the LSP8 NFT contract, replacing the <myAssetAddress> with the actual contract address. You can give it a try using a sample LSP8 asset on the LUKSO Testnet: 0x8734600968c7e7193BB9B1b005677B4edBaDcD18.

// Add the necessary imports to your JS file
import { ethers } from 'ethers';
import lsp8Artifact from '@lukso/lsp-smart-contracts/artifacts/LSP8IdentifiableDigitalAsset.json';

const provider = new ethers.JsonRpcProvider('https://4201.rpc.thirdweb.com');

// Create contract instance
const myAssetContract = new ethers.Contract(
'<myAssetAddress>',
lsp8Artifact.abi,
provider,
);

Preparing the Token IDs Format​

The LSP8 allows for different Token ID Formats, meaning developers can specify their token IDs as Number, String, Smart Contract Address, Byte Identifier or Hash Digest.

To call the contract, you must first prepare your token IDs to match the standardized Byte32 Hex Strings based on the LSP8TokenIdFormat standardization. The global token ID format can be fetched from the ERC725Y data storage using the getData() function:

// ...

// Get the global token ID format
let tokenIdFormat = parseInt(
await myAssetContract.getData(ERC725YDataKeys.LSP8['LSP8TokenIdFormat']),
16,
);

console.log(tokenIdFormat);
// 0

Its value can indicate whether the token ID format is equal or mixed across all token IDs

If your token ID is not yet compatible to LSP8TokenIdFormat, you have to convert it to the correct format first. Otherwise, you will be able to directly continue with fetching the metadata.

How to convert a tokenID to a Byte32 Hex String according to LSP8TokenIdFormat
// ...

// Sample Token ID of the contract
// Could be 1, my-token-id, 0x123, etc.
const myTokenId = '1';

// Convert a token ID according to LSP8TokenIdFormat
const convertTokenId = (tokenID: string, tokenIdFormat: number) => {
switch (tokenIdFormat) {
case 0:
case 100:
// uint256 - Number (Left padded)
return ethers.zeroPadValue('0x0' + BigInt(tokenID).toString(16), 32);
case 1:
case 101:
// string - String (Right padded)
return ethers.encodeBytes32String(tokenID).padEnd(32, '0');
case 2:
case 102:
// address - Smart Contract (Left padded)
return ethers.zeroPadValue(tokenID, 32);
case 3:
case 103:
// bytes32 - Unique Bytes (Right padded)
return ethers
.hexlify(ethers.getBytes(tokenID).slice(0, 32))
.padEnd(66, '0');
case 4:
case 104:
// bytes32 - Hash Digest (No padding)
return tokenID;
}
};

let byte32TokenId = convertTokenId(myTokenId, tokenIdFormat);
console.log(byte32TokenId);
// 0x0000000000000000000000000000000000000000000000000000000000000001

// If token ID format is mixed, retrieve it for the individual token ID
if (tokenIdFormat >= 100) {
tokenIdFormat = parseInt(
await myAssetContract.getDataForTokenId(
byte32TokenId,
ERC725YDataKeys.LSP8['LSP8TokenIdFormat'],
),
16,
);
byte32TokenId = convertTokenId(myTokenId, tokenIdFormat);
console.log(tokenIdFormat);
// 0x0000000000000000000000000000000000000000000000000000000000000001
}

Get Data from Token ID​

After preparing the token ID, you can start to fetch the ID-specific metadata.

To get token ID metadata, you will have to make a direct contract call by calling getDataForTokenId():

// ...

// Sample token ID (1) parsed according to LSP8TokenIDFormat
const byte32TokenId = '<myTokenID>';

async function fetchTokenIdMetadata(tokenID: string) {
// Get the encoded asset metadata
const tokenIdMetadata = await myAssetContract.getDataForTokenId(
tokenID,
ERC725YDataKeys.LSP4['LSP4Metadata'],
);

const erc725js = new ERC725(lsp4Schema);

// Decode the metadata
const decodedMetadata = erc725js.decodeData([
{
keyName: 'LSP4Metadata',
value: tokenIdMetadata,
},
]);
console.log(decodedMetadata);
}

fetchTokenIdMetadata(byte32TokenId);
info

Make sure to adjust <myTokenID> with the actual token ID as Byte32 Hex String according to LSP8TokenFormat. You can give it a try using a sample token ID: 0x0000000000000000000000000000000000000000000000000000000000000001 representing token ID 1 of the Number format.

Show result
{
"key": "0x9afb95cacc9f95858ec44aa8c3b685511002e30ae54415823f406128b85b238e",
"name": "LSP4Metadata",
"value": {
"verification": {
"method": "keccak256(utf8)",
"data": "0x9eefcdac3d60500619b075273c0371a6633d8f531179c882facd4f991281c658"
},
"url": "ipfs://QmeKNiTr4xfdHDUinGmYC4osu1ZHoFHDDj87WBSX5z4k7x"
}
}

After retrieving the metadata from the contract, you can continue to retrieve the actual JSON content. If the token ID data is empty, continue fetching the Base URI and retrieving the JSON using a file link.

Get Data from Base URI​

Version Support

Assets created with LSP versions below πŸ› οΈ @lukso/lsp-smart-contracts of v0.14.0 lack support for retrieving token ID metadata from a Base URI. For previous LSP8 assets, the Base URI may be retrievable by calling the fetchData function as described in the Global Token Information section.

You can fetch the LSP8TokenMetadataBaseURI and build the correct metadata link by concatinating the formatted token ID. Based on the LSP8TokenIDFormat, the Base URI can either be:

// ...

async function fetchBaseURI(tokenID: string, tokenIdFormat: number) {
// Retrieve the global Base URI
let tokenBaseURI = await myAssetContract.getData(
ERC725YDataKeys.LSP8['LSP8TokenMetadataBaseURI'],
);

if (tokenBaseURI == '0x') {
console.log('BaseURI not set.');
return;
}

if (tokenIdFormat >= 100) {
tokenBaseURI = await myAssetContract.getDataForTokenId(
byte32TokenId,
ERC725YDataKeys.LSP8['LSP8TokenIdFormat'],
);
}

// Decode the baseURI
const decodedBaseURI = erc725js.decodeData([
{
keyName: 'LSP8TokenMetadataBaseURI',
value: tokenBaseURI,
},
]);

// Return Base URI
return decodedBaseURI;
}

const baseURI = fetchBaseURI(byte32TokenId, tokenIdFormat);
console.log(baseURI);
// https://my.metadata-link.xyz/asset

Get the JSON Content​

After retrieving and decoding the Token ID Metadata or Base URI, you can fetch the metadata JSON.

If you retrieved the metadata using getDataFromTokenID(...), the URL will be nested within the value field of the metadata. However, this link might only be partial string or content ID, instead of a full link. Therefore, you may have to adjust the link before it can be fetched:

// ...

const metadataURL = decodedMetadata[0].value.url;

function generateMetadataLink(link: string) {
// If link is a regular Web2 Link, it can be passed back
if (link.startsWith('https://') || link.startsWith('http://')) {
// Use your default IPFS Gateway address
return link;
}
// If link has custom protocoll, adjust the link
if (link.startsWith('ipfs://')) {
// Use your default IPFS Gateway address
return `https://api.universalprofile.cloud/ipfs/${link.slice(7)}`;
} else {
return null;
}

// Handle other cases if needed ...
}

// Build link to JSON metadata
const metadataJsonLink = generateMetadataLink(metadataURL);

// Fetch the URL
if (metadataJsonLink) {
const response = await fetch(metadataJsonLink);
const jsonMetadata = await response.json();
console.log('Metadata Contents: ', jsonMetadata);
} else {
console.log('Could not generate metadata link based on value.url content.');
}
Show result
{
"LSP4Metadata": {
"name": "My Token Name",
"description": "Sample Description",
"links": [{ "title": "", "url": "" }],
"icon": [
{
"width": 1600,
"height": 1600,
"url": "ipfs://QmRHz3nbd2wQ4uNCJQS9JDnZRdD6aqueWRZ2h89dMkCLXf"
}
],
"images": [
[
{
"width": 1000,
"height": 1000,
"url": "ipfs://QmRHz3nbd2wQ4uNCJQS9JDnZRdD6aqueWRZ2h89dMkCLXf",
"verification": {
"method": "keccak256(bytes)",
"data": "0x7e89fc626703412916d27580af2bae16db479036f12d4d48f4efd24d70224dc2"
}

}
]
],
"assets": [],
"attributes": [
{
"key": "Standard type",
"value": "LSP",
"type": "string"
},
{
"key": "Standard number",
"value": 4,
"type": "number"
}
]
}
}