LSP6 - Key Manager
Introduction
An LSP0ERC725Account on its own comes with limited usability. Since it is an owned contract, only the account's owner can write data into it or use it to interact with other smart contracts.
Here comes the Key Manager. A smart contract that controls an LSP0ERC725Account, acting as its new owner. It then functions as a gateway for the account contract.
The idea is to give permissions to any address
, like Externally Owned Accounts (EOA) or smart contracts. These can then interact with the LSP0ERC725Account through the Key Manager. The Key Manager will allow or restrict access based on the permissions set for the calling address
.
Permissioned addresses can interact directly with the Key Manager or can sign a message to be executed by any other parties (users, relay services).
❌ Without a Key Manager, only the LSP0ERC725Account's owner can use its Account.
✅ With a Key Manager attached to an LSP0ERC725Account, other addresses (EOAs or contracts) can use an Account on behalf of its owner.
Permissions for addresses are not stored on the Key Manager. Instead, they are stored inside the data key-value store of the LSP0ERC725Account linked to the Key Manager. This way, it is possible to easily upgrade the Key Manager without resetting all the permissions again.
Permissions
You can use the encodePermissions(...)
and decodePermissions(...)
functions from the erc725.js tool to create, combine or decode permission values.
Click on the toggles below to learn more about the features enabled by each permission.
CHANGEOWNER
- Allows changing the owner of the controlled contract
value = 0x0000000000000000000000000000000000000000000000000000000000000001
The CHANGEOWNER
permission enables the change of the owner of the linked ERC725Account.
Using this permission, you can easily upgrade the LSP6KeyManager
attached to the Account by transferring ownership to a new Key Manager.
CHANGEPERMISSIONS
- Allows changing existing permissions of addresses
value = 0x0000000000000000000000000000000000000000000000000000000000000002
This permission allows for editing permissions of any address that already has some permissions set on the ERC725Account (including itself).
Bear in mind that the behavior of CHANGEPERMISSIONS
slightly varies depending on the new permissions value being set (see figure below).
ADDPERMISSIONS
- Allows giving permissions to new addresses.
value = 0x0000000000000000000000000000000000000000000000000000000000000004
This permission allows giving permissions to new addresses. This role-management enables the authorization of new addresses to interact with the ERC725Account.
SETDATA
- Allows setting data on the controlled contract
value = 0x0000000000000000000000000000000000000000000000000000000000000008
Allows an address to write any form of data in the ERC725Y data key-value store of the linked ERC725Account
(except permissions, which require the permissions CHANGEPERMISSIONS
).
NB: an
address
can be restricted to set only specific data keys via allowed ERC725Y keys
CALL
- Allows calling other contracts through the controlled contract
value = 0x0000000000000000000000000000000000000000000000000000000000000010
This permission enables anyone to use the ERC725Account linked to Key Manager to make external calls (to contracts or Externally Owned Accounts). Allowing state changes at the address being called.
STATICCALL
- Allows calling other contracts through the controlled contract
value = 0x0000000000000000000000000000000000000000000000000000000000000020
This permission enables the ERC725Account linked to Key Manager to make external calls to contracts while disallowing state changes at the address being called. It uses the STATICCALL opcode when performing the external call.
NB: If any state is changed at a target contract through a
STATICCALL
, the call will revert silently.
DELEGATECALL
- Allows delegate calling other contracts through the controlled contract
value = 0x0000000000000000000000000000000000000000000000000000000000000040
This permission allows executing code and functions from other contracts in the UP context.
DELEGATECALL
is currently disallowed (even if set on the KeyManager) because of its dangerous nature, as vicious developers can execute some malicious code in the linked Account contract.
DEPLOY
- Allows deploying other contracts through the controlled contract
value = 0x0000000000000000000000000000000000000000000000000000000000000080
Enables the caller to deploy a smart contract, using the linked ERC725Account as a deployer. Developers should provide the contract's bytecode to be deployed in the payload (ABI-encoded) passed to the Key Manager.
Both the
CREATE
orCREATE2
opcodes can be used to deploy a contract.
TRANSFERVALUE
- Allows transfering value to other contracts from the controlled contract
value = 0x0000000000000000000000000000000000000000000000000000000000000100
Enables sending native tokens from the linked ERC725Account to any address.
Note: For a simple native token transfer, no data (
""
) should be passed to the fourth parameter of theexecute
function of the Account contract. For instance:account.execute(operationCall, recipient, amount, "")
The caller will need the permission
CALL
to send any data along the LYX transfer.
SIGN
: Allows signing on behalf of the controlled account, for example for login purposes
value = 0x0000000000000000000000000000000000000000000000000000000000000200
Developers can use the SIGN
permission for keys to sign login messages. It is primarily for web2.0 apps to know which key SHOULD sign.
When deployed with our lsp-factory.js tool, the Universal Profile owner will have all the permissions above set by default.
SUPER Permissions
The super permissions grants the same permissions as their non-super counter parts, with the difference being that they check on restrictions for addresses
, standards
, or functions
are skipped. This allows for cheaper transactions where, these restrictions aren't set anyway.
SUPER_SETDATA
value = 0x0000000000000000000000000000000000000000000000000000000000000400
Same as SETDATA
, but allowing to set any ERC725Y data keys.
SUPER_TRANSFERVALUE
value = 0x0000000000000000000000000000000000000000000000000000000000000800
Same as TRANSFERVALUE
, but allowing to send native tokens to any address
(EOA or contract). This will also not check for allowed standards or allowed functions when transferring value to contracts.
SUPER_CALL
value = 0x0000000000000000000000000000000000000000000000000000000000001000
Same as CALL
, but allowing to interact with any contract. This will not check for allowed address
, standard or functions if the caller has any of these restrictions set.
SUPER_STATICCALL
value = 0x0000000000000000000000000000000000000000000000000000000000002000
Same as STATICCALL
, but allowing to interact with any contract. This will not check for allowed address
, standard or functions if the caller has any of these restrictions set.
SUPER_DELEGATECALL
value = 0x0000000000000000000000000000000000000000000000000000000000004000
Same as DELEGATECALL
, but allowing to interact with any contract. This will not check for allowed address
, standard or functions if the caller has any of these restrictions set.
Use with caution, as even if restrictions to certain addresses
, standards
, or functions
are set for an controller address, they will be ignored.
Combining Permissions
Permissions can be combined if an address
needs to hold more than one permission. To do so:
- calculate the sum of the decimal value of each permission.
- convert the result back into hexadecimal.
Example
permissions: CALL + TRANSFERVALUE
0x0000000000000000000000000000000000000000000000000000000000000010 (16 in decimal)
+ 0x0000000000000000000000000000000000000000000000000000000000000100 (256)
---------------------------------------------------------------------
= 0x0000000000000000000000000000000000000000000000000000000000000110 (= 272)
permissions: CHANGEPERMISSIONS + SETDATA
0x0000000000000000000000000000000000000000000000000000000000000002 (2 in decimal)
+ 0x0000000000000000000000000000000000000000000000000000000000000008 (8)
---------------------------------------------------------------------
= 0x000000000000000000000000000000000000000000000000000000000000000a (= 10)
Retrieving addresses with permissions
The convenience function getData(...)
from erc725.js will return you the whole list of addresses with permissions, when providing the AddressPermission[]
array key as a parameter.
You can obtain the list of address
that have some permissions set on the linked ERC725Account by querying the AddressPermission[]
data key, on the ERC725Y storage via getData(...)
.
- key:
0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3
- value return: the total number of address that have some permissions set (= array length)
Each address
can be retrieved by accessing each index in the array (see LSP2 > Array docs and LSP2 > Array Standard specs for more detailed instructions).
{
"name": "AddressPermissions[]",
"key": "0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3",
"keyType": "Array",
"valueType": "address",
"valueContent": "Address"
}
example:
if the AddressPermission[]
array data key returns 0x0000000000000000000000000000000000000000000000000000000000000004
(array length = 4), each address
can be obtained by querying the following data keys:
0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000000
: 1staddress
(array index 0 =AddressPermissions[0]
)0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000001
: 2ndaddress
(array index 1 =AddressPermissions[1]
)0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000002
: 3rdaddress
(array index 2 =AddressPermissions[2]
)0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000003
: 4thaddress
(array index 3 =AddressPermissions[3]
)
Types of permissions
Permission Type | Description | bytes32 data key |
---|---|---|
Address Permissions | defines a set of permissions for an address . | 0x4b80742de2bf82acb3630000<address> |
Allowed Addresses | defines which EOA or contract addresses an address is allowed to interact with them. | 0x4b80742de2bfc6dd6b3c0000<address> |
Allowed Functions | defines which function selector(s) an address is allowed to run on a specific contract. | 0x4b80742de2bf8efea1e80000<address> |
Allowed Standards | defines a list of interfaces standards an address is allowed to interact with when calling contracts (using ERC165 and interface ids). | 0x4b80742de2bf3efa94a30000<address> |
Allowed ERC725Y Keys | defines a list of bytes32 ERC725Y keys an address is only allowed to set when doing setData(...) on the linked ERC725Account. | 0x4b80742de2bf90b8b4850000<address> |
The values set under these permission keys MUST be of the following format to ensure correct behavior of these functionalities.
- Address Permissions: a
bytes32
value. - Allowed Addresses: an ABI-encoded array of
address[]
. - Allowed Functions: an ABI-encoded array of
bytes4[]
. - Allowed Standards: an ABI-encoded array of
bytes4[]
. - Allowed ERC725Y Keys: an ABI-encoded array of
bytes32[]
.
See the section Contract ABI Specification > Strict Encoding Mode in the Solidity documentation.
To add / remove entries in the list of allowed addresses, functions, standards or ERC725Y keys, the whole array should be ABI-encoded again and reset. Each update overrides the entire previous state.
Note that this process is expensive since the data being set is an ABI-encoded array.
Address Permissions
An address can hold one (or more) permissions, enabling it to perform multiple "actions" on an ERC725Account. Such "actions" include setting data on the ERC725Account, calling other contracts, transferring native tokens, etc.
To grant permission(s) to an <address>
, set the following key-value pair below in the ERC725Y storage of the ERC725Account linked to the Key Manager.
- key:
0x4b80742de2bf82acb3630000<address>
- value: one of the available permission below. To give multiple permission, see the Combining permissions section.
NB: remember to remove the
0x
prefix in the<address>
field above.
{
"name": "AddressPermissions:Permissions:<address>",
"key": "0x4b80742de2bf82acb3630000<address>",
"keyType": "MappingWithGrouping",
"valueType": "bytes32",
"valueContent": "BitArray"
}
Granting permissions to the linked ERC725Account itself is dangerous!
A caller can craft a payload via ERC725X.execute(...)
to be sent back to the KeyManager, leading to potential re-entrancy attacks.
Such transaction flow can lead an initial caller to use more permissions than allowed initially by using the permissions granted to the linked ERC725Account's address.
Each permission MUST be exactly 32 bytes long and zero left-padded:
0x0000000000000000000000000000000000000000000000000000000000000008
✅0x0800000000000000000000000000000000000000000000000000000000000000
❌
For instance, if you try to set the permission SETDATA for an address as 0x08
, this will be stored internally as 0x0800000000000000000000000000000000000000000000000000000000000000
, and will cause incorrect behaviour with odd revert messages.
Allowed addresses
You can restrict an address to interact only with specific contracts or EOAs.
To restrict an <address>
to only talk to a specific contract at address <target-contract-address>
(or additional addresses), the key-value pair below can be set in the ERC725Y contract storage.
- key:
0x4b80742de2bfc6dd6b3c0000<address>
- possible values:
[ <target-contract-address>, 0x.... ]
: an ABI-encoded array ofaddress[]
defining the allowed addresses.0x
(empty): if the value is an empty byte (=0x
), the caller<address>
is allowed to interact with any address (= all addresses are whitelisted).
{
"name": "AddressPermissions:AllowedAddresses:<address>",
"key": "0x4b80742de2bfc6dd6b3c0000<address>",
"keyType": "MappingWithGrouping",
"valueType": "address[]",
"valueContent": "Address"
}
Allowed functions
You can also restrict which functions a specific address can run by providing a list of bytes4
function selectors.
To restrict an <address>
to only execute the function transfer(address,uint256)
(selector: a9059cbb
), the following key-value pair can be set in the ERC725Y contract storage.
- key:
0x4b80742de2bf8efea1e80000<address>
- possible values:
[ 0xa9059cbb, 0x... ]
: an ABI-encoded array ofbytes4[]
values, definiting the function selectors allowed.0x
(empty): if the value is an empty byte (=0x
), the caller<address>
is allowed to execute any function (= allbytes4
function selectors are whitelisted).
{
"name": "AddressPermissions:AllowedFunctions:<address>",
"key": "0x4b80742de2bf8efea1e80000<address>",
"keyType": "MappingWithGrouping",
"valueType": "bytes4[]",
"valueContent": "Bytes4"
}
The receive()
and fallback()
functions can always be called on a target contract if no calldata is passed, even if you restrict an <address>
to call a certain set of functions.
Allowed standards
It is possible to restrict an address to interact only with contracts that implement specific interface standards. These contracts MUST implement the ERC165 standard to be able to detect their interfaces.
For example, to restrict an <address>
to only be allowed to interact with ERC725Account contracts (interface ID = 0x63cb749b
), the following key-value pair can be set in the ERC725Y contract storage.
- key:
0x4b80742de2bf3efa94a30000<address>
- possible values:
[ 0x63cb749b, 0x... ]
: an ABI-encoded array ofbytes4[]
ERC165 interface ids.0x
(empty): if the value is an empty byte (=0x
), the caller<address>
is allowed to interact with any contracts, whether they implement a specific standard interface or not.
{
"name": "AddressPermissions:AllowedStandards:<address>",
"key": "0x4b80742de2bf3efa94a30000<address>",
"keyType": "MappingWithGrouping",
"valueType": "bytes4[]",
"valueContent": "Bytes4"
}
Below is an example use case. With this permission key, an <address>
can be allowed to use the linked ERC725Account to interact with LSP7 contracts (= token contracts only ✅), but not with LSP8 contracts (= NFT contracts ❌).
This type of permission does not offer security over the inner workings or the correctness of a smart contract. It should be used more as a "mistake prevention" mechanism than a security measure.
Allowed ERC725Y Keys
If an address is allowed to SETDATA
on an ERC725Account, it is possible to restrict which keys this address can update.
To restrict an <address>
to only be allowed to set the key LSP3Profile
(0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5
), the following key-value pair can be set in the ERC725Y contract storage.
- key:
0x4b80742de2bf90b8b4850000<address>
- value(s):
[ 0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5 ]
{
"name": "AddressPermissions:AllowedERC725YKeys:<address>",
"key": "0x4b80742de2bf90b8b4850000<address>",
"keyType": "MappingWithGrouping",
"valueType": "bytes32[]",
"valueContent": "Bytes32"
}
Below is an example use case. An ERC725Account can allow some applications to add/edit informations on its storage via setData(...)
. The account can restrict such Dapps and protocols to edit the data keys that are only relevant to the logic of their applications.
As a result, this provide context for the Dapp on which data they can operate on the account, while preventing them to edit other information, such as the account metadata, or data relevant to other dapps.
If no bytes32 values are set, the caller address can set values for any keys.
Types of Execution
There are 2 ways to interact with the ERC725Account linked with the Key Manager.
- direct execution, where the caller
address
directly sends a payload to the Key Manager (= abi-encoded function call on the linked ERC725Account) to the KeyManager viaexecute(...)
. - relay execution, where a signer
address
A signs a payload and an executoraddress
B (e.g. a relay service) executes the payload on behalf of the signer viaexecuteRelayCall(...)
.
The main difference between direct vs relay execution is that with direct execution, the caller address
is the actual address making the request + paying the gas cost of the execution. With relay execution, a signer address
can interact with the ERC725Account without having to pay for gas fee.
Relay Execution
Relay execution enables users to interact with smart contracts on the blockchain without needing native tokens to pay for transaction fees. This allows a better onboarding experience for users new to cryptocurrencies and blockchain.
Relay execution minimizes UX friction for dapps, including removing the need for users to worry about gas fee, or any complex steps needed to operate on blockchains (KYC, seedphrases, gas).
Dapps can then leverage the relay execution features to create their own business model around building their own relay service, smart contracts solution on top of the Key Manager to pay with their tokens, or agree with users on payment methods including subscriptions, ads, etc ..
Out of order execution
Since the Key Manager offers relay execution via signed message, it's important to provide security measurements to ensure that the signed message can't be repeated once executed. Nonces existed to solve this problem, but with the following drawback:
- Signed messages with sequentiel nonces should be executed in order, meaning a signed message with nonce 4 can't be executed before the signed message with nonce 3. This is a critical problem which can limit the usage of relay execution.
Here comes the Multi-channel nonces which provide the ability to execute signed message with/without a specific order depending on the signer choice.
Signed messages should be executed sequentielially if signed on the same channel and should be executed independently if signed on different channel.
- Message signed with nonce 4 on channel 1 can't be executed before the message signed with nonce 3 on channel 1 but can be executed before the message signed with nonce 3 on channel 2.
Learn more about Multi-channel nonces usecases and its internal construction.