lip | title | author | discussions-to | status | type | created | requires |
---|---|---|---|---|---|---|---|
2 |
ERC725Y JSON Schema |
Fabian Vogelsteller <[email protected]> |
Draft |
LSP |
2020-07-01 |
ERC725Y |
Table of Content
- Simple Summary
- Abstract
- Motivation
- Specification
- keyType
- ValueType
- ValueContent
- Rationale
- Implementation
- Copyright
The LSP2 standard introduces a JSON schema for ERC725Y data keys. This schema defines how a single ERC725Y data key-value pair can be described. It can be used as an abstract structure over the storage of an ERC725Y smart contract.
The LUKSO Standard Proposal 2, or LSP2, introduces a new way to structure, store and represent data stored in smart contracts through the ERC725Y JSON Schema. This innovative approach allows for the storage of structured data directly on-chain, utilizing the flexibility and accessibility of JSON formatting for ERC725Y data keys. Imagine being able to store your blockchain identity's profile information, preferences, or even configuration settings in a standardized, easily retrievable format. That's what LSP2 enables.
By adopting the ERC725Y JSON Schema, developers can define clear, consistent data structures for their applications, enhancing interoperability and ease of integration across the LUKSO ecosystem. This structured approach not only simplifies data management but also paves the way for more complex and user-friendly decentralized applications. With LSP2, the potential for creating rich, interactive, and personalized user experiences on the blockchain becomes a reality, opening up a world of possibilities for developers and users alike.
ERC725Y enables storing any data in a smart contract as bytes32 => bytes
data key-value pairs.
Although this improves interaction with the data stored, it remains difficult to understand the layout of the contract storage. This is because both the data key and the value are addressed in raw bytes.
This schema allows to standardize those data keys and values so that they can be more easily accessed and interpreted. It can be used to create ERC725Y sub-standards, made of pre-defined sets of ERC725Y data keys.
A schema defines a blueprint for how a data store is constructed.
In the context of smart contracts, it can offer a better view of how the data is organised and structured within the contract storage.
Using a standardised schema over ERC725Y enables those data keys and values to be easily readable and automatically parsable. Contracts and interfaces can know how to read and interact with the storage of an ERC725Y smart contract.
The advantage of such schema is to allow interfaces or smart contracts to better decode (read, parse and interpret) the data stored in an ERC725Y contract. It is less error-prone due to knowing data types upfront. On the other hand, it also enables interfaces and contracts to know how to correctly encode data, before being set on an ERC725Y contract.
This schema is for example used in ERC725 based smart contracts like LSP3-Profile-Metadata and LSP4-DigitalAsset-Metadata.
Note: described sets might not yet be complete, as they could be extended over time.
To make ERC725Y data keys readable, we describe a data key-value pair as a JSON object containing the following entries:
{
"name": "...",
"key": "...",
"keyType": "...",
"valueType": "...",
"valueContent": "..."
}
The table below describes each entry with its available options.
Title | Description |
---|---|
name |
the name of the data key |
key |
the unique identifier of the data key |
keyType |
How the data key must be treated Singleton Array Mapping MappingWithGrouping |
valueType |
How a value MUST be decoded bool string address uintN intN bytesN bytes uintN[] intN[] string[] address[] bytes[] bytes[CompactBytesArray] bytesN[CompactBytesArray] Tuple: (valueType1,valueType2,...) |
valueContent |
How a value SHOULD be interpreted Boolean String Address Number BytesN Bytes Keccak256 BitArray URL VerifiableURI AssetURL (deprecated) JSONURL (deprecated) Markdown Literal (e.g.: 0x1345ABCD... ) |
The name
is the human-readable format of an ERC725Y data key. It's the basis which is used to generate the 32 bytes
key hash. Names can be arbitrarily chosen but SHOULD highlight the meaning of the content behind the data value.
In scenarios where an ERC725Y data key is part of an LSP Standard, the data key name
SHOULD be comprised of the following: LSP{N}{KeyName}
, where
LSP
: abbreviation for LUKSO Standards Proposal.N
: the Standard Number this data key refers to.KeyName
: base of the data key name. Should represent the meaning of a value stored behind the data key.
e.g.: MyCustomKeyName
or LSP4TokenName
The key
is a bytes32
value that acts as the unique identifier for the data key, and is what is used to retrieve the data value from an ERC725Y smart contract via ERC725Y.getData(bytes32 dataKey)
or ERC725Y.getData(bytes32[] dataKeys)
.
Usually keccak256
hashing algorithm is used to generate the bytes32
data key. However, how the data key is constructed varies, depending on the keyType
.
The keyType
determines the format of the data key(s).
keyType |
Description | Example |
---|---|---|
Singleton |
A simple data key | bytes32(keccak256("MyKeyName")) --- MyKeyName --> 0x35e6950bc8d21a1699e58328a3c4066df5803bb0b570d0150cb3819288e764b2 |
Array |
An array spanning multiple ERC725Y data keys | bytes32(keccak256("MyKeyName[]")) --- MyKeyName[] --> 0x24f6297f3abd5a8b82f1a48cee167cdecef40aa98fbf14534ea3539f66ca834c |
Mapping |
A data key that consists of 2 sections, where the last section can also be a dynamic value | bytes10(keccak256("MyKeyName")) +bytes2(0) +bytes20(keccak256("MyMapName") or <mixed type>) --- MyKeyName:MyMapName --> 0x35e6950bc8d21a1699e5000075060e3cd7d40450e94d415fb5992ced9ad8f058 |
MappingWithGrouping |
A data key that consists of 3 sections, where the last two sections can also be dynamic values | bytes6(keccak256("MyKeyName")) +bytes4(keccak256("MyMapName") or <mixed type>) +bytes2(0) +bytes20(keccak256("MySubMapName") or <mixed type>) --- MyKeyName:MyMapName:<address> --> 0x35e6950bc8d275060e3c0000cafecafecafecafecafecafecafecafecafecafe |
Describes the underlying data type(s) of a value stored under a specific ERC725Y data key. It refers to the type of smart contract language like Solidity.
The valueType
is relevant for interfaces to know how a value MUST be encoded/decoded. This includes:
- how to decode a value fetched via
ERC725Y.getData(...)
- how to encode a value that needs to be set via
ERC725Y.setData(...)
.
The valueType
can also be useful for typecasting. It enables contracts or interfaces to know how to manipulate the data and the limitations behind its type. To illustrate, an interface could know that it cannot set the value to 300
if its valueType
is uint8
(max uint8
allowed = 255
).
valueType |
Description |
---|---|
bool |
a value as either true or false |
string |
a UTF-8 encoded string |
address |
a 20 bytes long address |
uintN |
an unsigned integer (= only positive number) of size N |
bytesN |
a bytes value of fixed-size N , from bytes1 up to bytes32 |
bytes |
a bytes value of dynamic-size |
uintN[] |
an array of signed integers |
string[] |
an array of UTF-8 encoded strings |
address[] |
an array of addresses |
bytes[] |
an array of dynamic size bytes |
bytesN[] |
an array of fixed size bytes |
bytes[CompactBytesArray] |
a compacted bytes array of dynamic size bytes |
bytesN[CompactBytesArray] |
a compacted bytes array of fixed size bytes |
Tuple: (valueType1,valueType2,...) |
a tuple of valueTypes |
The valueContent
of an LSP2 Schema describes how to interpret the content of the returned decoded value.
Knowing how to interpret the data retrieved under a data key is the first step in understanding how to handle it. Interfaces can use the valueContent
to adapt their behaviour or know how to display data fetched from an ERC725Y smart contract.
As an example, a string could be interpreted in multiple ways, such as:
- a single word, or a sequence of words (e.g.: "My Custom Token Name")
- a URL (e.g.: "ipfs://QmW4nUNy3vtvr3DxZHuLfSLnhzKMe2WmgsUsEGPPFh8Ztp")
Using the following two LSP2 schemas as examples:
{
"name": "MyProfileDescription",
"key": "0xd0f1819a38d741fce6a6b74406251c521768033029cd254f0f5cd29ca58f3390",
"keyType": "Singleton",
"valueType": "string",
"valueContent": "String"
},
{
"name": "MyWebsite",
"key": "0x449560072375b299bab5a695ea268c32c52d4820e4458e5f02f308c588e6715a",
"keyType": "Singleton",
"valueType": "string",
"valueContent": "URL"
}
An interface could decode both values retrieved under these data keys as string
, but:
- display the profile description as plain text.
- display the website URL as an external link.
Valid valueContent
are:
valueContent |
Description |
---|---|
Boolean |
a boolean value (true or false ) |
String |
a UTF-8 encoded string |
Address |
an address |
Number |
a Number (positive or negative, depending on the keyType ) |
BytesN |
a bytes value of fixed-size N , from bytes1 up to bytes32 |
Bytes |
a bytes value of dynamic-size |
Keccak256 |
a 32 bytes long hash digest, obtained from the keccak256 hashing algorithm |
BitArray |
an array of single 1 or 0 bits |
URL |
a URL encoded as a UTF-8 string |
VerifiableURI |
The content contains respectively the verification method, the length of the verification data, the verification data, and the uri |
AssetURL |
The content contains the hash function, hash and link to the asset file |
JSONURL |
hash function, hash and link to the JSON file |
Markdown |
a structured Markdown mostly encoded as UTF-8 string |
0x1345ABCD... |
a literal value, when the returned value is expected to equal some specific bytes |
The valueContent
field can also define a tuple of value contents (for instance, when the valueType
is a tuple of types, as described above). In this case, each value content MUST be defined between parentheses. For instance: (Bytes4,Number)
.
This is useful for decoding tools, to know how to interpret each value type in the tuple.
In the case where:
a) the keyType
is an Array
.
b) or the valueType
is an array []
(compacted or not).
the valueContent
describes how to interpret each entry in the array, not the whole array itself. Therefore the valueContent
field MUST NOT include []
.
We can use the LSP2 Schema below as an example to better understand. This LSP2 Schema below defines a data key that represents a list of social media profiles related to a user.
Reading the ERC725Y storage using this data key will return an array of abi-encoded string[]
. Therefore the interface should use the valueType
to decode the retrieved value. The valueContent
however defines that each string in the array must be interpreted as a social media URL.
{
"name": "MySocialMediaProfiles",
"key": "0x161761c54f6b013a4b4cbb1247f703c94ae5dfe32081554ad861781f48d47513",
"keyType": "Singleton",
"valueType": "string[]",
"valueContent": "URL"
}
A Singleton data key refers to a simple data key. It is constructed using bytes32(keccak256("KeyName"))
,
Below is an example of a Singleton data key type:
{
"name": "MyKeyName",
"key": "0x35e6950bc8d21a1699e58328a3c4066df5803bb0b570d0150cb3819288e764b2",
"keyType": "Singleton",
"valueType": "...",
"valueContent": "..."
}
keccak256("MyKeyName")
= 0x
35e6950bc8d21a1699e58328a3c4066df5803bb0b570d0150cb3819288e764b2
An array of elements, where each element has the same valueType
.
The advantage of the
keyType
Array over a standard array of elements likeaddress[]
, is that the amount of elements that can be stored is unlimited. Storing an encoded array as a value, will require a set amount of gas, which can exceed the block gas limit.
Requirements:
A data key of Array type MUST have the following requirements:
- The
name
of the data key MUST have a[]
(square brackets) at the end. - The
key
itself MUST be the keccak256 hash digest of the full data keyname
, including the square brackets[]
- The value stored under the full data key hash MUST contain the total number of elements (= array length). It MUST be updated every time a new element is added or removed to/from the array.
- The value stored under the full data key hash MUST be stored as
uint128
(16 bytes long, padded left with leading zeros).
Construction:
For the Array keyType
, the initial key
contains the total number of elements stored in the Array (= array length). It is constructed using bytes32(keccak256(KeyName))
.
Each Array element can be accessed through its own key
. The key
of an Array element consists of bytes16(keccak256(KeyName)) + bytes16(uint128(ArrayElementIndex))
, where:
bytes16(keccak256(KeyName))
= The first 16 bytes are the keccak256 hash of the full Array data keyname
(including the[]
) (e.g.:LSP12IssuedAssets[]
)bytes16(uint128(ArrayElementIndex))
= the position (= index) of the element in the array (NB: elements index access start at0
)
Note: an ERC725Y data key of keyType Array can contain up to
max(uint128)
elements. This is because:
- the value stored under the Array length data key,
- the index part of an Array index data key,
are both 16 bytes long, which is equivalent to a
uint128
.
example:
Below is an example for the Array data key named LSP12IssuedAssets[]
.
- total number of elements:
- key:
0x7c8c3416d6cda87cd42c71ea1843df28ac4850354f988d55ee2eaa47b6dc05cd
, - value:
0x00000000000000000000000000000002
(2 elements)
- key:
- element 1: key:
0x7c8c3416d6cda87cd42c71ea1843df2800000000000000000000000000000000
, value:0x123...
(index 0) - element 2: key:
0x7c8c3416d6cda87cd42c71ea1843df2800000000000000000000000000000001
, value:0x321...
(index 1) ...
{
"name": "LSP12IssuedAssets[]",
"key": "0x7c8c3416d6cda87cd42c71ea1843df28ac4850354f988d55ee2eaa47b6dc05cd",
"keyType": "Array",
"valueType": "address", // describes the type of each element
"valueContent": "Address" // describes the value of each element
}
key: keccak256('LSP12IssuedAssets[]') = 0x7c8c3416d6cda87cd42c71ea1843df28ac4850354f988d55ee2eaa47b6dc05cd
value: uint128 (array length) e.g. 0x00000000000000000000000000000002
// array items
// 1st element (index 0)
key: 0x7c8c3416d6cda87cd42c71ea1843df2800000000000000000000000000000000
value: 0xcafecafecafecafecafecafecafecafecafecafe
// 2nd element (index 1)
key: 0x7c8c3416d6cda87cd42c71ea1843df2800000000000000000000000000000001
value: 0xcafecafecafecafecafecafecafecafecafecafe
A Mapping data key is constructed using:
bytes10(keccak256("MyKeyName"))
+ bytes2(0)
+ bytes20(keccak256("MyMapName") or <mixed type>)
.
<mixed type>
can be one of uint<M>
, address
, bool
or bytes<M>
types.
The following padding and cutting rules apply:
bool
will be left padded and left-cutuint<M>
will be left padded and left-cut ifM
is larger than160
bits (= the 20 right-most bytes are kept).
bytes<N>
will be right padded and right cut ifN
is larger than20 bytes
(= the 20 left most bytes are kept).bytes
andstring
will be:- right padded if they contain less than 20 bytes / characters
- right cut if they contain more than 20 bytes / characters
address
,bytes20
anduint160
are kept as they are since they are exactly 20 bytes long.
valueType |
Left padded | Right padded | Left cut | Right cut |
---|---|---|---|---|
bool |
✔️ | |||
uint8 to uint152 |
✔️ | |||
bytes1 to bytes19 |
✔️ | |||
uint168 to uint256 |
✔️ | |||
bytes21 to bytes32 |
✔️ |
example:
// Examples:
MyKeyName:MyMapName // 0x35e6950bc8d21a1699e58328a3c4066df5803bb0b570d0150cb3819288e764b2 + 0x75060e3cd7d40450e94d415fb5992ced9ad8f058649e805951f558364152f9ed
"0x35e6950bc8d21a1699e5000075060e3cd7d40450e94d415fb5992ced9ad8f058"
MyKeyName:<address> // 0xcafecafecafecafecafecafecafecafecafecafe
"0x35e6950bc8d21a1699e50000cafecafecafecafecafecafecafecafecafecafe"
MyKeyName:<uint32> // 4081242941 (in decimal) = 0xf342d33d (in hex)
"0x35e6950bc8d21a1699e5000000000000000000000000000000000000f342d33d"
MyKeyName:<bytes4> // 0xabcd1234
"0x35e6950bc8d21a1699e50000abcd123400000000000000000000000000000000"
MyKeyName:<bytes32> // 0xaaaabbbbccccddddeeeeffff111122223333444455556666777788889999aaaa
"0x35e6950bc8d21a1699e50000aaaabbbbccccddddeeeeffff1111222233334444"
MyKeyName:<bool> // true
"0x35e6950bc8d21a1699e500000000000000000000000000000000000000000001"
// ERC725Y JSON schema
{
"name": "FirstWord:<bytes4>",
"key": "0xf49648de3734d6c545820000<bytes4>",
"keyType": "Mapping",
"valueType": "...",
"valueContent": "..."
}
A MappingWithGrouping data key is constructed using:
bytes6(keccak256("MyKeyName"))
+ bytes4(keccak256("MyMapName") or <mixed type>)
+ bytes2(0)
+ bytes20(keccak256("MySubMapName") or <mixed type>)
.
<mixed type>
can be one of uint<M>
, address
, bool
or bytes<M>
types.
uint<M>
,bool
will be left padded and left-cut, if it's larger than the max bytes of that section.bytes<M>
andaddress
and static word hashes (bytes32
) will be left padded, but right-cut, if it's larger than the max bytes of that section.
e.g. AddressPermissions:Permissions:<address>
> 0x4b80742de2bf 82acb363 0000 cafecafecafecafecafecafecafecafecafecafe
.
example:
// Examples:
MyKeyName:MyMapName:MySubMapName // 0x35e6950bc8d21a1699e58328a3c4066df5803bb0b570d0150cb3819288e764b2 + 0x75060e3cd7d40450e94d415fb5992ced9ad8f058649e805951f558364152f9ed + 0000 + 0x221cba00b07da22c3775601ffea5d3406df100dbb7b1c86cb2fe3739f0fe79a1
"0x35e6950bc8d275060e3c0000221cba00b07da22c3775601ffea5d3406df100db"
MyKeyName:MyMapName:<address>
"0x35e6950bc8d275060e3c0000cafecafecafecafecafecafecafecafecafecafe"
// For more examples static examples see the "Mapping" examples
MyKeyName:<bytes2>:<uint32> // ffff 4081242941
"0x35e6950bc8d2ffff0000000000000000000000000000000000000000f342d33d"
MyKeyName:<address>:<address> // 0xabcdef11abcdef11abcdef11abcdef11ffffffff, 0xcafecafecafecafecafecafecafecafecafecafe
"0x35e6950bc8d2abcdef110000cafecafecafecafecafecafecafecafecafecafe"
MyKeyName:MyMapName:<bytes32> // 0xaaaabbbbccccddddeeeeffff111122223333444455556666777788889999aaaa
"0x35e6950bc8d275060e3c0000aaaabbbbccccddddeeeeffff1111222233334444"
MyKeyName:<bytes32>:<bool> // 0xaaaabbbbccccddddeeeeffff111122223333444455556666777788889999aaaa
"0x35e6950bc8d2aaaabbbb00000000000000000000000000000000000000000001"
// ERC725Y JSON schema
{
"name": "AddressPermissions:Permissions:<address>",
"key": "0x4b80742de2bf82acb3630000<address>",
"keyType": "MappingWithGrouping",
"valueType": "...",
"valueContent": "..."
}
A bytes[CompactBytesArray]
represents an array of bytes
values encoded in a compact way. The elements contained in the array are bytes
values with different dynamic lengths.
In a compact bytes array of bytes
, each element is prefixed with 2 bytes to specify its length.
For instance, 0xaabbccdd
in a bytes[CompactBytesArray]
is encoded as 0x0004aabbccdd
, where:
0x0004
=4
represents the total number ofbytes
in0xaabbccdd
.0xaabbccdd
is the actual value of the element.
Note: the maximum length of each element is 65535, because two bytes (equivalent to a
uint16
) are used to store the length of each element and the maximum value of auint16
is 65535.
example
If we want to have the following bytes as elements in the compacted bytes array:
[
0xaabbccdd, // element 1 length is 4 in hex: 0x04
0xcafecafecafecafecafecafecafe, // element 2 length is 14 in hex: 0x0E
0xff // element 3 length is 1 in hex: 0x01
]
The representation of these dynamic elements in a compacted bytes array would be:
0x0004 aabbccdd 000e cafecafecafecafecafecafecafe 0001 ff
> 0x0004aabbccdd000ecafecafecafecafecafecafecafe0001ff
An example of a bytes[CompactBytesArray]
, where the bytes are a tuple:
{
"name": "AddressPermissions:AllowedCalls:<address>",
"key": "0x4b80742de2bf393a64c70000<address>",
"keyType": "MappingWithGrouping",
"valueType": "(bytes4,address,bytes4,bytes4)[CompactBytesArray]",
"valueContent": "(BitArray,Address,Bytes4,Bytes4)"
}
Like a bytes[CompactBytesArray]
a bytesN[CompactBytesArray]
represents an array of bytesN
values encoded in a compact way. The difference is that all the elements contained in the array have the same length N
.
In a compact bytes array of bytesN
, each element is prefixed with 1 byte that specifies the length N
.
For instance, in a bytes8[CompactBytesArray]
an entry like 0x1122334455667788
is encoded as 0x00081122334455667788
, where:
0x0008
=8
to represent that0x1122334455667788
contains 8 bytes.0x1122334455667788
is the actual value of the element.
Note: because two bytes are used to store the length of each element, the maximum
N
length allowed is 65535 (two bytes are equivalent to the maximum value of auint16
is 65535)
example:
If we want to have the following bytes8
elements encoded as a bytes8[CompactBytesArray]
:
[
0x1122334455667788,
0xcafecafecafecafe,
0xbeefbeefbeefbeef
]
We will obtain the following:
0x0008 1122334455667788 0008 cafecafecafecafe 0008 beefbeefbeefbeef
> 0x000811223344556677880008cafecafecafecafe0008beefbeefbeefbeef
.
Where each byte 0x0008
in the final encoded value represents the length N
of each element.
vvvv vvvv vvvv
0x000811223344556677880008cafecafecafecafe0008beefbeefbeefbeef
The valueType
can also be a tuple of types. In this case, the value stored under the ERC725Y data key is a mixture of multiple values concatenated together (the values are just "glued together").
Tuples of valueTypes offer a convenient way to store more than one piece of information under a single ERC725Y data key. In the example below, the value below can be represented as a tuple to store the address of a smart contract (0xcafecafecafecafecafecafecafecafecafecafe
) + its interfaceID (0xbeefbeef
), all under one single data key.
(address,bytes4)
0xcafecafecafecafecafecafecafecafecafecafebeefbeef
The main purpose why tuples of valueTypes exist in LSP2 is because it offers a way to store more than one piece of information under a data key. For instance (address,bytes4) is a useful tuple to ????
LSP2 tuples are different than Solidity tuples. In Solidity, values defined in the tuple are padded. In the case of LSP2 they are not.
In the case of a tuple of valueType
s, the types MUST be defined between parentheses, comma separated without parentheses.
(valueType1,valueType2,valueType3,...)
example 1:
For a schema that includes the following tuple as valueType
.
{
"name": "...",
"key": "...",
"keyType": "...",
"valueType": "(bytes4,bytes8)",
"valueContent": "..."
}
And the following values:
bytes4
value =0xcafecafe
bytes8
value =0xbeefbeefbeefbeef
The tuple of valueType
MUST be encoded as:
0xcafecafebeefbeefbeefbeef
example 2:
For a schema that includes the following tuple as valueType
.
{
"name": "...",
"key": "...",
"keyType": "...",
"valueType": "(bytes8,address)",
"valueContent": "..."
}
And the following values:
bytes4
value =0xca11ab1eca11ab1e
bytes8
value =0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5
The tuple of valueType
MUST be encoded as:
0xca11ab1eca11ab1e95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5
example 3:
For a schema that includes the following tuple as valueType
.
{
"name": "...",
"key": "...",
"keyType": "...",
"valueType": "(address,uint128,bytes4,bool,bytes)",
"valueContent": "..."
}
And the following values:
address
value =0x388C818CA8B9251b393131C08a736A67ccB19297
uint128
value = the number5,918
(0x0000000000000000000000000000171E
in hex)bytes4
value =0xf00df00d
bool
value = true (0x01
in hex)bytes
value =0xcafecafecafecafecafecafecafe
0x388C818CA8B9251b393131C08a736A67ccB192970000000000000000000000000000171Ef00df00d01cafecafecafecafecafecafecafe
A BitArray describes an array that contains a sequence of bits (1
s and 0
s).
Each bit can be either set (1
) or not (0
). The point of the BitArray valueContent
is that there are only two possible values, so they can be stored in one bit.
A BitArray can be used as a mapping of values to states (on/off, allowed/disallowed, locked/unlocked, valid/invalid), where the max number of available values that can be mapped is n bits.
example:
The example shows how a BitArray
value can be read and interpreted.
{
"name": "MyPermissions",
"key": "0xaacedf1d8b2cc85524a881760315208fb03c6c26538760922d6b9dee915fd66a",
"keyType": "Singleton",
"valueType": "bytes1",
"valueContent": "BitArray"
}
As the data key name
suggests, it defines a list of (user-defined) permissions, where each permission maps to a single bit at position n
.
- When a bit at position
n
is set (1
), the permission defined at positionn
will be set. - When a bit at position
n
is not set (0
), the permission defined at positionn
will not be set.
Since the valueType
is of type bytes1
, this data key can hold 8 user-defined permissions.
For instance, for the following permissions:
SIGN |
TRANSFER VALUE |
DEPLOY |
DELEGATE CALL |
STATIC CALL |
CALL |
SET DATA |
CHANGE OWNER |
---|---|---|---|---|---|---|---|
0 / 1 |
0 / 1 |
0 / 1 |
0 / 1 |
0 / 1 |
0 / 1 |
0 / 1 |
0 / 1 |
Setting only the permission SET DATA
will result in the following bytes1
value (and its binary representation)
> Permission SET DATA = permissions set to 0000 0010
`0x02` (4 in decimal)
SIGN |
TRANSFER VALUE |
DEPLOY |
DELEGATE CALL |
STATIC CALL |
CALL |
SET DATA |
CHANGE OWNER |
---|---|---|---|---|---|---|---|
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
Setting multiple permissions like TRANSFER VALUE + CALL + SET DATA
will result in the following bytes1
value (and its binary representation)
> Permissions set to 0100 0110
`0x46` (70 in decimal)
SIGN |
TRANSFER VALUE |
DEPLOY |
DELEGATE CALL |
STATIC CALL |
CALL |
SET DATA |
CHANGE OWNER |
---|---|---|---|---|---|---|---|
0 |
1 |
0 |
0 |
0 |
1 |
1 |
0 |
The idea is to always read the value of a BitArray data key as binary digits, while its content is always written as a bytes1
(in hex) in the ERC725Y contract storage.
VerifiableURI allows for linked URIs to contain additional verification data to ensure the authenticity and integrity of content linked. A verifiable URI consists of bytes sliced into four parts:
0x0000 00000000 0000 0000...0000 0000...
^ ^ ^ ^ ^
VerifiableURI Verification Verification Verification Encoded URI
identifier method data length data
-
VerifiableURI identifier: MUST be
bytes2(0)
:0000
-
Verification method: This field specifies the method of verification that determines how the verification data is interpreted. The verification method is the first 4 bytes of the hash of the method name:
bytes4(keccak256('methodName'))
. **If the verification method is00000000
then the URI is NOT VERIFIABLE. An example of a non-verifiable URI could look as follows:0x0000000000000000fffffffffffff...
(wherefffff...
is the encoded link to the file/content) -
Verification data: The data varies based on the verification method. The following is a list of verification methods. Additional methods can be added:
"keccak256(utf8)"
(0x6f357c6a
): Means the data SHOULD bebytes32
hash of the content of the linked UTF-8 based file of the "Encoded URI""keccak256(bytes)"
(0x8019f9b1
): Means the data SHOULD bebytes32
hash of the content of the linked file of the "Encoded URI""ecdsa"
(0xac75a10e
): Means the data consists of two parts: The firstbytes20
are the ec-recover address, and the rest of the data bytes are the verification "source" containing an encoded string of a URI to a signature. The linked file of the "Encoded URI" then needs to be verified using the signature from the "source" URI and the linked file, which MUST recover to the address from the "verification data".- Other verification methods can be added over time...
-
Encoded URI: The actual string encoded URI to file or content to be verified.
The following shows an example of how to encode a VerifiableURI that will be verified based on the keccak256('utf8')
verification method:
// My custom JSON file
const json = JSON.stringify({
myProperty: 'is a string',
anotherProperty: {
sdfsdf: 123456
}
})
const verfiableUriIdentifier = '0x0000'
// Get the bytes4 representation of the verification method
const verificationMethod = web3.utils.keccak256('keccak256(utf8)').substr(0, 10)
> '0x6f357c6a'
// Get the hash of the JSON file (verification data)
const verificationData = web3.utils.keccak256(json)
> '0x820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361'
// Get the verification data length and pad it as 2 bytes
const verificationDataLength = web3.utils.padLeft(web3.utils.numberToHex((verificationData.substring(2).length) / 2), 4);
> 0x0020
// store the JSON anywhere and encode the URL
const url = web3.utils.utf8ToHex('ifps://QmYr1VJLwerg6pEoscdhVGugo39pa6rycEZLjtRPDfW84UAx')
> '0x696670733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178'
// final result (to be stored on chain)
const VerfiableURI = verfiableUriIdentifier + verificationMethod.substring(2) + verificationDatalength.substring(2) + verificationData.substring(2) + url.substring(2)
^ ^ ^ ^ ^
0000 6f357c6a 0020 820464ddfac1be... 696670733a2f2...
// structure of the VerifiableURI
0x0000 + 6f357c6a + 0020 + 820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361 + 696670733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178
^ ^ ^ ^ ^
0000 keccak256(utf8) verificationDatalength verificationData encoded URL
// example value
0x00006f357c6a0020820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361696670733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178
DEPRECATED Please use VerifiableURI
The content is bytes containing the following format:
bytes4(keccack256('hashFunction'))
+ bytes32(keccack256(assetBytes))
+ utf8ToHex('AssetURL')
Known hash functions:
0x8019f9b1
: keccak256('keccak256(bytes)')
example:
The following shows an example of how to encode an AssetURL:
const hashFunction = web3.utils.keccak256('keccak256(bytes)').substr(0, 10)
> '0x8019f9b1'
// Local file read
let hash = web3.utils.keccak256(fs.readFileSync('./file.png'))
> '0xd47cf10786205bb08ce508e91c424d413d0f6c48e24dbfde2920d16a9561a723'
// or browser fetch
const assetBuffer = await fetch('https://ipfs.lukso.network/ipfs/QmW4nUNy3vtvr3DxZHuLfSLnhzKMe2WmgsUsEGPPFh8Ztp').then(async (response) => {
return response.arrayBuffer().then((buffer) => new Uint8Array(buffer));
});
hash = web3.utils.keccak256(assetBuffer)
> '0xd47cf10786205bb08ce508e91c424d413d0f6c48e24dbfde2920d16a9561a723'
// store the asset file anywhere and encode the URL
const url = web3.utils.utf8ToHex('ipfs://QmW4nUNy3vtvr3DxZHuLfSLnhzKMe2WmgsUsEGPPFh8Ztp')
> '0x697066733a2f2f516d57346e554e7933767476723344785a48754c66534c6e687a4b4d6532576d67735573454750504668385a7470'
// final result (to be stored on chain)
const AssetURL = hashFunction + hash.substring(2) + url.substring(2)
^ ^ ^
0x8019f9b1 + d47cf10786205bb0... + 697066733a2f2...
// structure of the AssetURL
0x8019f9b1 + d47cf10786205bb08ce508e91c424d413d0f6c48e24dbfde2920d16a9561a723 + 697066733a2f2f516d57346e554e7933767476723344785a48754c66534c6e687a4b4d6532576d67735573454750504668385a7470
^ ^ ^
keccak256(utf8) hash encoded URL
// example value
0x8019f9b1d47cf10786205bb08ce508e91c424d413d0f6c48e24dbfde2920d16a9561a723697066733a2f2f516d57346e554e7933767476723344785a48754c66534c6e687a4b4d6532576d67735573454750504668385a7470
DEPRECATED Please use VerifiableURI
The content is bytes containing the following format:
bytes4(keccak256('hashFunction'))
+ bytes32(keccak256(JSON.stringify(JSON)))
+ utf8ToHex('JSONURL')
Known hash functions:
0x6f357c6a
: keccak256('keccak256(utf8)')
example:
The following shows an example of how to encode a JSON object:
const json = JSON.stringify({
myProperty: 'is a string',
anotherProperty: {
sdfsdf: 123456
}
})
const hashFunction = web3.utils.keccak256('keccak256(utf8)').substr(0, 10)
> '0x6f357c6a'
const hash = web3.utils.keccak256(json)
> '0x820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361'
// store the JSON anywhere and encode the URL
const url = web3.utils.utf8ToHex('ifps://QmYr1VJLwerg6pEoscdhVGugo39pa6rycEZLjtRPDfW84UAx')
> '0x696670733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178'
// final result (to be stored on chain)
const JSONURL = hashFunction + hash.substring(2) + url.substring(2)
^ ^ ^
0x6f357c6a + 820464ddfac1be... + 696670733a2f2...
// structure of the JSONURL
0x6f357c6a + 820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361 + 696670733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178
^ ^ ^
keccak256(utf8) hash encoded URL
// example value
0x6f357c6a820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361696670733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178
To decode, reverse the process:
const data = myContract.methods.getData('0xsomeKey..').call()
> '0x6f357c6a820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361696670733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178'
// slice the bytes to get its pieces
const hashFunction = data.slice(0, 10)
const hash = '0x' + data.slice(0, 74)
const url = '0x' + data.slice(74)
// check if it uses keccak256
if(hashFunction === '0x6f357c6a') {
// download the json file
const json = await ipfsMini.catJSON(
web3.utils.hexToUtf8(url).replace('ipfs://','')
);
// compare hashes
if(web3.utils.keccak256(JSON.stringify(json)) === hash)
return
? json
: false
}
The structure of the data key value layout as JSON allows interfaces to auto decode these data key values as they will know how to decode them.
Below is an example of an ERC725Y JSON Schema containing 3 x ERC725Y data keys.
Using such schema allows interfaces to auto decode and interpret the values retrieved from the ERC725Y data key-value store.
[
{
"name": "SupportedStandards:LSP3Profile",
"key": "0xeafec4d89fa9619884b600005ef83ad9559033e6e941db7d7c495acdce616347",
"keyType": "Mapping",
"valueType": "bytes4",
"valueContent": "0xabe425d6"
},
{
"name": "LSP3Profile",
"key": "0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5",
"keyType": "Singleton",
"valueType": "bytes",
"valueContent": "JSONURL"
},
{
"name": "LSP12IssuedAssets[]",
"key": "0x7c8c3416d6cda87cd42c71ea1843df28ac4850354f988d55ee2eaa47b6dc05cd",
"keyType": "Array",
"valueType": "address",
"valueContent": "Address"
}
]
Copyright and related rights waived via CC0.