Technical Update on LSP7 Digital Asset and LSP8 Identifiable Digital Asset
Today’s article focuses on the main changes in the LUKSO token standards, commonly known as LSP7 Digital Asset and LSP8 Identifiable Digital Asset.
With these new updates, the LSP7 and LSP8 standards have now moved to the “review” stage.
We will cover the latest changes introduced in the LSP7 and LSP8 token standards by breaking down changes that apply to both and changes specific to each one.
Table of Content
Changes applicable to both LSP7 + LSP8
- 🧬 Interface IDs changes
- 🧩 LSP7 + LSP8 now support LSP17 Extendable by default
- 🗄️ New LSP4 Token Type data key
- 🔔 Operators get notified via LSP1 Universal Receiver
- 📣 Events related to operators renamed
- 🔁 New
batchCalls(bytes[])
function added to LSP7 + LSP8 - 📊 Full data value emitted in
DataChanged
event - 🔗 LSP4Metadata valueContent: change from
JSONURL
toVerifiableURI
- 📋 Data sent when notifying via LSP1 is now ABI-encoded
Changes specific to LSP7
- ➕
increaseAllowance(…)
and ➖decreaseAllowance(…)
functions added - 🛃
getOperatorsOf(address)
function added in LSP7
Changes specific to LSP8
- ✏️ New argument
lsp8TokenIdFormat
set on deployment - 🔠 New function
get/setDataForTokenId(...)
to set metadata per tokenId (including batch function) - 📢 New event
TokenIdDataChanged
- 🗑️ Deprecated data keys:
LSP8MetadataTokenURI
Conclusion, Guides and Useful Links
Changes applicable to both LSP7 + LSP8
Interface IDs changes
With most breaking changes come interface ID changes. The introduction of new functions in the LSP7 + LSP8 interfaces, the ERC165 interface ID of both standards now changed to the following bytes4
identifiers:
- LSP7 new interface ID:
0xb3c4928f
- LSP8 new interface ID:
0x3a271706
Developers should now use these 2 identifiers when calling the supportsInterface(bytes4)
function of a contract to identify if it is a LSP7 or LSP8 token contract.
The interface ID change is due to the following functions that were added:
batchCalls(bytes[])
in LSP7: Pull Request #810get/setDataForTokenId(...)
+get/setDataBatchForTokenIds(…)
in LSP8: Pull Request #822
You can also use the following guide on docs.lukso.tech to learn how to detect the new interface ID of LSP7 and LSP8.
LSP7 + LSP8 now support LSP17 Extendable by default
One of the major additions to both standards and their implementations is that they now fully support the LSP17 Extendable standard by default.
LSP17 allows a smart contract to add any new functions to the contract after it has been deployed on a network.
By “any new functions” we mean any publicly callable function that is not supported natively in its ABI. Furthermore, these functions can be added and removed during the contract’s lifetime.
This works by setting the extension contract for this non-native function selector as a value for the LSP17Extension:<selector>
data key. The code logic in these extension contracts will act as the behaviour for this non-native function. See the following schema below for this data key:
{
"name": "LSP17Extension:<bytes4>",
"key": "0xcee78b4094da860110960000<bytes4>",
"keyType": "Mapping",
"valueType": "address",
"valueContent": "Address"
}
See the LSP17 Extendable Standard on docs.lukso.tech for more details.
By adding LSP17 by default in LSP7 and LSP8, this means new functionalities can be added to the token contracts. Such functionalities could include:
- making the NFTs directly buyable by interacting with the LSP8 contract.
- implement royalty distributions mechanisms, to allow tokens or NFT holders to claim their royalty payments.
Check out the “Adding Functionalities to Universal Profiles using LSP17 — Contract Extension” video to learn how to build using the LSP17 standard.
New LSP4 Token Type data key
When deploying an LSP7 or LSP8 contract, the LSP4TokenType
data key must be set to define the type of digital asset being created. Whether it is:
- a Token (value
0
) - a NFT (value
1
) - or a Collection (value
2
)
Additionally, more custom token types could be defined in the future.
This value is then publicly readable under the LSP4TokenType
data key after the contract has been deployed. For instance, our erc725.js library can be used to retrieve this information and decode it as follows:
import { ERC725 } from '@erc725/erc725.js';
import LSP4Schema from '@erc725/erc725.js/schemas/LSP4DigitalAssetMetadata.json';
const tokenContractAddress = '0x0Dc07C77985fE31996Ed612F568eb441afe5768D';
const RPC_URL = 'https://rpc.testnet.lukso.network';
const config = {
ipfsGateway: 'https://YOUR-IPFS-GATEWAY/ipfs/',
};const erc725 = new ERC725(LSP4Schema, tokenContractAddress, RPC_URL, config);await erc725.fetchData('LSP4Metadata');/**
{
key: '0x9afb95cacc9f95858ec44aa8c3b685511002e30ae54415823f406128b85b238e',
name: 'LSP4Metadata',
value: {
LSP4Metadata: {
name: 'Digital Handbags collection',
description: 'The first digital golden handbags collection.',
links: [
{ title: 'Twitter', url: 'https://twitter.com/goldenhandbags123' },
{ title: 'goldenhandbags.org', url: 'https://goldenhandbags.org' }
],
icon: [ ... ],
images: [ ... ], // list of images thatCOULD be used for LSP8 NFT art
assets: [{
verification: {
method: 'keccak256(bytes)',
data: '0x98fe032f81c43426fbcfb21c780c879667a08e2a65e8ae38027d4d61cdfe6f55',
},
url: 'ifps://QmPJESHbVkPtSaHntNVY5F6JDLW8v69M2d6khXEYGUMn7N',
fileType: 'fbx'
}],
attributes: [
...
]
}
}
*/
The Solidity implementation LSP7 and LSP8 implementation on the @lukso/lsp-smart-contracts
package set this data key on deployment.
You can check our guides in docs.lukso.tech to see how these parameters are passed to your deployment scripts when deploying an LSP7 Token or deploying an LSP8 NFT Collection.
The addition of the LSP4TokenType
data key solves the following problem:
Currently, dApps, users and interfaces cannot differentiate if a LSP7 contract is a token or an NFT, and if a LSP8 contract is a NFT or a collection.
In fact, the LSP7 and LSP8 standards can both be used to create complex NFT collections as described below.
- LSP7 can also be used to create NFTs. where the
LSP4Metadata
represents the information of a single NFT that has multiple ownable amounts or IDs, and NFTs can be minted in large quantities in a single transaction. - Additionally, LSP8 is more useful for physical or NFTs with unique properties per item, where these properties can also evolve.
- LSP8 can also represent a singular item, with many ownable IDs (e.g. a physical handbag), but the same metadata for each tokenIds.
- Finally, LSP8 can also be used to create a collection of items, where each item has its own ID, or even smart contract address
The new LSP4TokenType
data key helps in defining in which of these categories the LSP7 or LSP8 contract fits, which was less clear before.
You can learn more thanks to our Youtube video guide below!
See also the PR and the LSP4 specs for more details about LSP4TokenType
.
Operators get notified via LSP1 Universal Receiver
Similarly to other token standards like ERC20 and ERC721, LSP7 and LSP8 offer functionalities to allow other addresses to spend some of your balance (for LSP7) or transfer some of your NFTs (LSP8) on your behalf. These approved addresses are commonly referred to as operators.
When authorising an operator via the authorizeOperator(…)
, the operator is always notified. This means that if the operator is a contract that supports the LSP1 interface, its universalReceiver(...)
function will be called with a specific LSP1 typeId being the keccak225 hash of:
- the word
LSP7Tokens_OperatorNotification
for LSP7 - the word
LSP8Tokens_OperatorNotification
for LSP8
When revoking an operator via the revokeOperator(…)
function, the operator can be notified via its LSP1 Universal Receiver optionally. This can be done by setting the notify
parameter to true
.
The same change applies to LSP8, despite the code snippet showing LSP7.
Events related to operators renamed
For the following events emitted when authorising or revoking operators:
- From
AuthorizedOperator
→ toOperatorAuthorizationChanged
- From
RevokedOperator
→ toOperatorRevoked
LSP7 Operators Events before update:
event AuthorizedOperator(
address indexed operator,
address indexed tokenOwner,
uint256 indexed amount,
bytes operatorNotificationData
);
event RevokedOperator(
address indexed operator,
address indexed tokenOwner,
bool notified,
bytes operatorNotificationData
);
LSP8 Operators Events before udpate:
event AuthorizedOperator(
address indexed operator,
address indexed tokenOwner,
bytes32 indexed tokenId,
bytes operatorNotificationData
);
event RevokedOperator(
address indexed operator,
address indexed tokenOwner,
bytes32 indexed tokenId,
bool notified,
bytes operatorNotificationData
);
LSP7 Operators Events Now:
event OperatorAuthorizationChanged(
address indexed operator,
address indexed tokenOwner,
uint256 indexed amount,
bytes operatorNotificationData
);
event OperatorRevoked(
address indexed operator,
address indexed tokenOwner,
bool indexed notified,
bytes operatorNotificationData
);
LSP8 Operators Events now:
event OperatorAuthorizationChanged(
address indexed operator,
address indexed tokenOwner,
bytes32 indexed tokenId,
bytes operatorNotificationData
);
event OperatorRevoked(
address indexed operator,
address indexed tokenOwner,
bytes32 indexed tokenId,
bool notified,
bytes operatorNotificationData
);
New batchCalls(…) function added to LSP7 + LSP8
Similarly to Universal Profiles, the function batchCalls(bytes[])
was added to the LSP7 and LSP8 standards. This function enables batching calls to multiple functions from the interface into a single transaction.
For instance, it is possible to do things such as; minting a tokenId for an address or authorising an operator for this newly minted tokenId, all in one single transaction.
This can be done by abi-encoding the function calls to these two functions, and passing this array of bytes[]
ABI-encoded function calls to the batchCalls(bytes[])
function and calling it.
JSONURL deprecated, LSP4 Metadata now encoded as VerifiableURI
The metadata of LSP7 and LSP8 digital assets — stored under the LSP4Metadata
data key — is now encoded and decoded as a VerifiableURI
{
"name": "LSP4Metadata",
"key": "0x9afb95cacc9f95858ec44aa8c3b685511002e30ae54415823f406128b85b238e",
"keyType": "Singleton",
"valueType": "bytes",
"valueContent": "VerifiableURI"
}
This new encoding scheme was introduced over JSONURL
(which is now deprecated) as it allows more features over JSONURL. Unlike a JSONURL
, a VerifiableURI can contain verification data that can be used to verify the authenticity and integrity of the content linked. For instance, the content can be verified using various verification methods such as content hashing with keccak256 or using ECDSA signature recovery.
For more details, see the LSP2 specification for VerifiableURI.
DataChanged event now emits the full bytes data value parameter
The data value of the DataChanged event was capped to emit the first 256 bytes of the data value; now, the data is not capped and will be fully emitted.
This detail was initially intended to minimise the gas cost when setting metadata in the ERC725Y storage of digital assets when the data to be set was large with many bytes.
However, capping to 256 bytes being emitted in the dataValue
parameter of the DataChanged
event is very limiting for users who are already storing big amount of data in their ERC725Y storage. Since storing large data size is already costly, the additional gas cost of emitting the full data value is minimal in proportion to the total gas cost of the transaction.
In addition, emitting the full bytes of the data value can be useful for interfaces to detect on the fly the previous data that was set, for instance to track the values that were set under a data key for historical reasons.
Data sent when notifying via LSP1 is now ABI-encoded
Among the new changes in LSP7 and LSP8 is how the notification data (related to LSP1) is encoded when notifying a sender, recipient or operator.
Previously, the data was packed encoded, so the number of bytes
sent would be as small as possible. This was changed to be abi-encoded.
The following information is sent when notifying:
- on token transfers, minting and burning: the
operator
,sender
,recipient
,amount
(for LSP7) ortokenId
(for LSP8) and the additionaldata
parameter sent. - when authorising operators: the
tokenOwner
, theamount
(for LSP7) ortokenId
(for LSP8),true
when authorising,false
when revoking (or if the operator balance reached0
for LSP7), and thebytes operatorNotificationData
.
The main benefit of this change is that it makes it easier for any smart contract implementing LSP1 acting as a sender, recipient or operator, to decode the LSP1 data received.
- Either in Solidity using
abi.decode(...)
. See an example in our Create a LSP1 Forwarder guide on docs.lukso.tech
- Or in Javascript with web3.js or ethers.js, by decoding the 3rd parameter
receivedData
from theUniversalReceiver
event emitted on the sender / recipient / operator contract.
Changes specific to LSP7
Functions increaseAllowance and decreaseAllowance added to LSP7
These functions were added so that they can be used to mitigate the risk of double spending allowance related to fungible tokens.
Function getOperatorsOf added to LSP7 interface
The function getOperatorsOf(address)
was added to the LSP7 interface, so that it enables any token holder to retrieve the list of operators it has allowed to spend allowance on its balance.
Changes specific to LSP8
Setting the tokenId format on deployment
Note: the data key
LSP8TokenIdFormat
was formerly known asLSP8TokenIdType
. It was renamed to avoid confusion withLSP4TokenType
.
LSP8TokenIdFormat is a data key defined in LSP8-IdentifiableDigitalAsset to highlight what is the format used for each tokenId.
{
"name": "LSP8TokenIdFormat",
"key": "0xf675e9361af1c1664c1868cfa3eb97672d6b1a513aa5b81dec34c9ee330e818d",
"keyType": "Singleton",
"valueType": "uint256",
"valueContent": "Number"
}
There can be:
- one format per tokenId (
0
to4
)
- or mixed formats with tokenIds, meaning a default one, but some
tokenId
can have a different one (100
to104
)
These constants can be imported in Javascript from the package as follows:
import { LSP8_TOKEN_ID_FORMAT } from "@lukso/lsp-smart-contracts";
With the following values available, based on the table above:
export const LSP8_TOKEN_ID_FORMAT = {
NUMBER: 0,
STRING: 1,
ADDRESS: 2,
UNIQUE_ID: 3,
HASH: 4,
MIXED_DEFAULT_NUMBER: 100,
MIXED_DEFAULT_STRING: 101,
MIXED_DEFAULT_ADDRESS: 102,
MIXED_DEFAULT_UNIQUE_ID: 103,
MIXED_DEFAULT_HASH: 104,
};
When deploying an LSP8IdentifiableDigitalAsset
, the tokenId format must be provided when deploying the contract.
Functions to get/set metadata per tokenId
The new functions get/setDataForTokenId(…)
have been added in the interface of the LSP8 standard.
function getDataForTokenId(
bytes32 tokenId,
bytes32 dataKey
) external returns (bytes memory dataValue);
function getDataBatchForTokenIds(
bytes32[] memory tokenIds,
bytes32[] memory dataKeys
) external returns (bytes[] memory dataValues);function setDataForTokenId(
bytes32 tokenId,
bytes32 dataKey,
bytes memory dataValue
) external;function setDataBatchForTokenIds(
bytes32[] memory tokenIds,
bytes32[] memory dataKeys,
bytes[] memory dataValues
) external;
Previously in the LSP8 standard, metadata for each NFT (= defined below by their tokenId
) could be set using the following data key:
{
"name": "LSP8MetadataTokenURI:<address|uint256|bytes32|string>",
"key": "0x1339e76a390b7b9ec9010000<address|uint256|bytes32|string>",
"keyType": "Mapping",
"valueType": "(bytes4,string)",
"valueContent": "(Bytes4,URI)"
}
This Mapping data key (now deprecated, see further below in the article) presented the problem of the tokenId
truncation. When constructing the actual 32 bytes long "key”
, only the first 20 bytes of the tokenId
would be kept to be concatenated with the left part 0x1339e76a390b7b9ec9010000
.
Since tokenId
are represented as bytes32
, truncating to only the first 20 bytes in the Mapping data key presented the risk of generating the same data key for multiple token IDs, and therefore to have multiple same LSP8MetadataTokenURI
that would collide.
This was especially risky if the tokenId
were represented as strings (LSP8TokenIdFormat
= string, value = 2
), and the first 20 characters of the strings were common to each token ID, while the last 12 characters were the unique part of each token ID.
Moreover, the different parts of the bytes32 tokenId
could represent different things depending on the NFT collections being created. For instance, the first 10 bytes could signal the overall rarity, the next 10 bytes a sub-category, etc… Setting the metadata for these token IDs using this former method (keeping only the first 20 bytes of the tokenId
) would not have been possible.
These new functions setDataForTokenId(...)
and setDataBatchForTokenIds(...)
solve this problem above. They enable to define a subset of metadata per specific tokenId
, while also offering flexible functionalities, such as in one transaction:
- set multiple
dataKeys
for a singletokenId
. - set the same data key (e.g: the
LSP4Metadata
) for multiple differenttokenIds
. - a combination of the two previous cases.
For more details and code examples, see the section “Setting metadata for one or multiple token IDs” on docs.luks.tech.
New event TokenIdDataChanged
With the new functions listed above, a new event was introduced in LSP8:
The TokenIdDataChanged
event offers a flexible way to store metadata specific to a particular NFT on-chain while also making this metadata discoverable off-chain.
The TokenIdDataChanged
event enables indexers and services listening for events to keep track of which metadata and data keys have been updated for specific token IDs. Additionally, both parameters tokenId
and dataKey
are indexed
, so they can be filtered.
For instance, a service could use these indexed parameters to keep track of the metadata of NFTs being updated. To illustrate:
- for a single NFT: which data keys have been updated for a specific
tokenId
, by filtering thetokenId
parameter. - for multiple NFTs: all the
tokenId
s that have had a specific data key updated (e.g:LSP4Metadata
), by filtering thedataKey
parameter.
See “Check if the metadata of a tokenId changed” in docs.lukso.tech for details.
Deprecated data keys: LSP8MetadataTokenId
Finally, the last update specific to LSP8 is the deprecation of the data key, LSP8MetadataTokenId
.
To set the metadata specific to a tokenId, the data key LSP4Metadata
can be set for a specific tokenId by passing this tokenId
and this data key to the setDataForTokenIf
function.
Conclusion, Guides and Useful Links
The full specifications of the LSP7 Digital Asset and LSP8 Identifiable Digital Asset standards can be found under the links below:
You can also check the Learn section on docs.lukso.tech to familiarise yourself with building your LSP7 token or LSP8 NFT contract on LUKSO.
We hope this article will help you build and deploy Tokens and NFT 2.0 collections on the LUKSO blockchain using the LSP7 and LSP8 standards!
If you have any questions or comments on this update, we welcome you to join our Discord and write to us in our Developer channels or browse our Technical Documentation.