Transient Labs NFT Delegation Registry
An immutable delegation registry that provides a common interface for Transient Labs creator contracts to check nft ownership delegation.
Problem Statement
NFT security is one of the biggest concerns in our space. As such, most people keep their NFTs in wallets/vaults that they don't connect to any dApps. This can either be in the form of a hardware wallet or a mutli-sig smart contract wallet. If someone ones to sell an NFT, they just simply transfer the NFT to a selling wallet that they can connect to marketplaces.
Transient Labs contracts have features that are unique and provide more utlity to NFT owners. To start, we initially required the NFT owner to send the transaction to either add a story inscription, accept a metadata update, or something else. Looking towards the future, with NFT security in mind, we want to create a universal interface for Transient Labs contracts to check NFT ownership delegations.
Solution
NFT delegation is not new. delegate.xyz has done a good job of bringing NFT delegation to the market and many projects are integrating with it for allowlisting, airdrops, etc. There are also other products coming to the market outside of delegate.xyz. For example, Punk 6529 and his team have created their own delegation registry, NFTDelegation, that allows for more fine-grained control. This registry is used for all memecard drops from 6529.
The purpose of this NFT Delegation Registry is not to sping up our own version, but rather to provide a universal interface that our contracts can call, without having to worry about what delegation solutions are being checked under the hood. We think that there will be many delegation solutions in the future and want to support them all.
To start, the Transient Labs NFT Delegation Registry just checks delegate.xyz v2. We considered supporting NFTDelegation, however, we have several questions and concerns and are in discussion with that team to address prior to integrating into this project.
Immutable by Design
The registry we create follows a specific interface src/ITlNftDelegationRegistry.sol
. However, we are not making the implementation upgradeable and rather will build our creator contracts to be able to migrate registries in the future.
How This Registry Should Be Used
This registry MUST NEVER be used for any on-chain functionality that affects NFT ownership. As an example, delegates MUST NOT be able transfer or sell tokens on behalf of the token owner. ERC-721 and ERC-1155 already have mechanisms in place to handle this. Future token standards will also have those provisions in place.
Disclaimer
This codebase is provided on an "as is" and "as available" basis.
We do not give any warranties and will not be liable for any loss incurred through any use of this codebase.
License
This code is copyright Transient Labs, Inc 2023 and is licensed under the MIT license.
Contents
IDelegateRegistry
A standalone immutable registry storing delegated permissions from one address to another
Functions
multicall
----------- WRITE -----------
Call multiple functions in the current contract and return the data from all of them if they all succeed
function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);
Parameters
Name | Type | Description |
---|---|---|
data | bytes[] | The encoded function data for each of the calls to make to this contract |
Returns
Name | Type | Description |
---|---|---|
results | bytes[] | The results from each of the calls passed in via data |
delegateAll
Allow the delegate to act on behalf of msg.sender
for all contracts
function delegateAll(address to, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash);
Parameters
Name | Type | Description |
---|---|---|
to | address | The address to act as delegate |
rights | bytes32 | Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights |
enable | bool | Whether to enable or disable this delegation, true delegates and false revokes |
Returns
Name | Type | Description |
---|---|---|
delegationHash | bytes32 | The unique identifier of the delegation |
delegateContract
Allow the delegate to act on behalf of msg.sender
for a specific contract
function delegateContract(address to, address contract_, bytes32 rights, bool enable)
external
payable
returns (bytes32 delegationHash);
Parameters
Name | Type | Description |
---|---|---|
to | address | The address to act as delegate |
contract_ | address | The contract whose rights are being delegated |
rights | bytes32 | Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights |
enable | bool | Whether to enable or disable this delegation, true delegates and false revokes |
Returns
Name | Type | Description |
---|---|---|
delegationHash | bytes32 | The unique identifier of the delegation |
delegateERC721
Allow the delegate to act on behalf of msg.sender
for a specific ERC721 token
function delegateERC721(address to, address contract_, uint256 tokenId, bytes32 rights, bool enable)
external
payable
returns (bytes32 delegationHash);
Parameters
Name | Type | Description |
---|---|---|
to | address | The address to act as delegate |
contract_ | address | The contract whose rights are being delegated |
tokenId | uint256 | The token id to delegate |
rights | bytes32 | Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights |
enable | bool | Whether to enable or disable this delegation, true delegates and false revokes |
Returns
Name | Type | Description |
---|---|---|
delegationHash | bytes32 | The unique identifier of the delegation |
delegateERC20
Allow the delegate to act on behalf of msg.sender
for a specific amount of ERC20 tokens
The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound)
function delegateERC20(address to, address contract_, bytes32 rights, uint256 amount)
external
payable
returns (bytes32 delegationHash);
Parameters
Name | Type | Description |
---|---|---|
to | address | The address to act as delegate |
contract_ | address | The address for the fungible token contract |
rights | bytes32 | Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights |
amount | uint256 | The amount to delegate, > 0 delegates and 0 revokes |
Returns
Name | Type | Description |
---|---|---|
delegationHash | bytes32 | The unique identifier of the delegation |
delegateERC1155
Allow the delegate to act on behalf of msg.sender
for a specific amount of ERC1155 tokens
The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound)
function delegateERC1155(address to, address contract_, uint256 tokenId, bytes32 rights, uint256 amount)
external
payable
returns (bytes32 delegationHash);
Parameters
Name | Type | Description |
---|---|---|
to | address | The address to act as delegate |
contract_ | address | The address of the contract that holds the token |
tokenId | uint256 | The token id to delegate |
rights | bytes32 | Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights |
amount | uint256 | The amount of that token id to delegate, > 0 delegates and 0 revokes |
Returns
Name | Type | Description |
---|---|---|
delegationHash | bytes32 | The unique identifier of the delegation |
checkDelegateForAll
----------- CHECKS -----------
Check if to
is a delegate of from
for the entire wallet
function checkDelegateForAll(address to, address from, bytes32 rights) external view returns (bool);
Parameters
Name | Type | Description |
---|---|---|
to | address | The potential delegate address |
from | address | The potential address who delegated rights |
rights | bytes32 | Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only |
Returns
Name | Type | Description |
---|---|---|
<none> | bool | valid Whether delegate is granted to act on the from's behalf |
checkDelegateForContract
Check if to
is a delegate of from
for the specified contract_
or the entire wallet
function checkDelegateForContract(address to, address from, address contract_, bytes32 rights)
external
view
returns (bool);
Parameters
Name | Type | Description |
---|---|---|
to | address | The delegated address to check |
from | address | The cold wallet who issued the delegation |
contract_ | address | The specific contract address being checked |
rights | bytes32 | Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only |
Returns
Name | Type | Description |
---|---|---|
<none> | bool | valid Whether delegate is granted to act on from's behalf for entire wallet or that specific contract |
checkDelegateForERC721
Check if to
is a delegate of from
for the specific contract
and tokenId
, the entire contract_
, or the entire wallet
function checkDelegateForERC721(address to, address from, address contract_, uint256 tokenId, bytes32 rights)
external
view
returns (bool);
Parameters
Name | Type | Description |
---|---|---|
to | address | The delegated address to check |
from | address | The wallet that issued the delegation |
contract_ | address | The specific contract address being checked |
tokenId | uint256 | The token id for the token to delegating |
rights | bytes32 | Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only |
Returns
Name | Type | Description |
---|---|---|
<none> | bool | valid Whether delegate is granted to act on from's behalf for entire wallet, that contract, or that specific tokenId |
checkDelegateForERC20
Returns the amount of ERC20 tokens the delegate is granted rights to act on the behalf of
function checkDelegateForERC20(address to, address from, address contract_, bytes32 rights)
external
view
returns (uint256);
Parameters
Name | Type | Description |
---|---|---|
to | address | The delegated address to check |
from | address | The cold wallet who issued the delegation |
contract_ | address | The address of the token contract |
rights | bytes32 | Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only |
Returns
Name | Type | Description |
---|---|---|
<none> | uint256 | balance The delegated balance, which will be 0 if the delegation does not exist |
checkDelegateForERC1155
Returns the amount of a ERC1155 tokens the delegate is granted rights to act on the behalf of
function checkDelegateForERC1155(address to, address from, address contract_, uint256 tokenId, bytes32 rights)
external
view
returns (uint256);
Parameters
Name | Type | Description |
---|---|---|
to | address | The delegated address to check |
from | address | The cold wallet who issued the delegation |
contract_ | address | The address of the token contract |
tokenId | uint256 | The token id to check the delegated amount of |
rights | bytes32 | Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only |
Returns
Name | Type | Description |
---|---|---|
<none> | uint256 | balance The delegated balance, which will be 0 if the delegation does not exist |
getIncomingDelegations
----------- ENUMERATIONS -----------
Returns all enabled delegations a given delegate has received
function getIncomingDelegations(address to) external view returns (Delegation[] memory delegations);
Parameters
Name | Type | Description |
---|---|---|
to | address | The address to retrieve delegations for |
Returns
Name | Type | Description |
---|---|---|
delegations | Delegation[] | Array of Delegation structs |
getOutgoingDelegations
Returns all enabled delegations an address has given out
function getOutgoingDelegations(address from) external view returns (Delegation[] memory delegations);
Parameters
Name | Type | Description |
---|---|---|
from | address | The address to retrieve delegations for |
Returns
Name | Type | Description |
---|---|---|
delegations | Delegation[] | Array of Delegation structs |
getIncomingDelegationHashes
Returns all hashes associated with enabled delegations an address has received
function getIncomingDelegationHashes(address to) external view returns (bytes32[] memory delegationHashes);
Parameters
Name | Type | Description |
---|---|---|
to | address | The address to retrieve incoming delegation hashes for |
Returns
Name | Type | Description |
---|---|---|
delegationHashes | bytes32[] | Array of delegation hashes |
getOutgoingDelegationHashes
Returns all hashes associated with enabled delegations an address has given out
function getOutgoingDelegationHashes(address from) external view returns (bytes32[] memory delegationHashes);
Parameters
Name | Type | Description |
---|---|---|
from | address | The address to retrieve outgoing delegation hashes for |
Returns
Name | Type | Description |
---|---|---|
delegationHashes | bytes32[] | Array of delegation hashes |
getDelegationsFromHashes
Returns the delegations for a given array of delegation hashes
function getDelegationsFromHashes(bytes32[] calldata delegationHashes)
external
view
returns (Delegation[] memory delegations);
Parameters
Name | Type | Description |
---|---|---|
delegationHashes | bytes32[] | is an array of hashes that correspond to delegations |
Returns
Name | Type | Description |
---|---|---|
delegations | Delegation[] | Array of Delegation structs, return empty structs for nonexistent or revoked delegations |
readSlot
----------- STORAGE ACCESS -----------
Allows external contracts to read arbitrary storage slots
function readSlot(bytes32 location) external view returns (bytes32);
readSlots
Allows external contracts to read an arbitrary array of storage slots
function readSlots(bytes32[] calldata locations) external view returns (bytes32[] memory);
Events
DelegateAll
Emitted when an address delegates or revokes rights for their entire wallet
event DelegateAll(address indexed from, address indexed to, bytes32 rights, bool enable);
DelegateContract
Emitted when an address delegates or revokes rights for a contract address
event DelegateContract(
address indexed from, address indexed to, address indexed contract_, bytes32 rights, bool enable
);
DelegateERC721
Emitted when an address delegates or revokes rights for an ERC721 tokenId
event DelegateERC721(
address indexed from, address indexed to, address indexed contract_, uint256 tokenId, bytes32 rights, bool enable
);
DelegateERC20
Emitted when an address delegates or revokes rights for an amount of ERC20 tokens
event DelegateERC20(
address indexed from, address indexed to, address indexed contract_, bytes32 rights, uint256 amount
);
DelegateERC1155
Emitted when an address delegates or revokes rights for an amount of an ERC1155 tokenId
event DelegateERC1155(
address indexed from, address indexed to, address indexed contract_, uint256 tokenId, bytes32 rights, uint256 amount
);
Errors
MulticallFailed
Thrown if multicall calldata is malformed
error MulticallFailed();
Structs
Delegation
Struct for returning delegations
struct Delegation {
DelegationType type_;
address to;
address from;
bytes32 rights;
address contract_;
uint256 tokenId;
uint256 amount;
}
Enums
DelegationType
Delegation type, NONE is used when a delegation does not exist or is revoked
enum DelegationType {
NONE,
ALL,
CONTRACT,
ERC721,
ERC20,
ERC1155
}
ITLNftDelegationRegistry
Author: transientlabs.xyz
Interface for the TL NFT Delegation Registry
Functions
checkDelegateForERC721
Function to check if an address is delegated for a vault for an ERC-721 token
This function does not ensure the vault is the current owner of the token
This function SHOULD return True
if the delegate is delegated for the vault whether it's on the token level, contract level, or wallet level (all)
function checkDelegateForERC721(address delegate, address vault, address nftContract, uint256 tokenId)
external
view
returns (bool);
Parameters
Name | Type | Description |
---|---|---|
delegate | address | The address to check for delegation status |
vault | address | The vault address to check against |
nftContract | address | The nft contract address to check |
tokenId | uint256 | The token id to check against |
Returns
Name | Type | Description |
---|---|---|
<none> | bool | bool True is delegated, False if not |
checkDelegateForERC1155
Function to check if an address is delegated for a vault for an ERC-1155 token
This function does not ensure the vault has a balance of the token in question
This function SHOULD return True
if the delegate is delegated for the vault whether it's on the token level, contract level, or wallet level (all)
function checkDelegateForERC1155(address delegate, address vault, address nftContract, uint256 tokenId)
external
view
returns (bool);
Parameters
Name | Type | Description |
---|---|---|
delegate | address | The address to check for delegation status |
vault | address | The vault address to check against |
nftContract | address | The nft contract address to check |
tokenId | uint256 | The token id to check against |
Returns
Name | Type | Description |
---|---|---|
<none> | bool | bool True is delegated, False if not |
TLNftDelegationRegistry
Inherits: Ownable, Pausable, ITLNftDelegationRegistry
Author: transientlabs.xyz
Transient Labs NFT Delegation Registry, providing a universal interface for TL contracts to check NFT delegation and use it for features like Story Inscriptions, Synergy, Multi-Metadata and more.
This registry is not intended to be used in core ownership functions defined by ERC-721 and ERC-1155.
State Variables
delegateRegistry
IDelegateRegistry public immutable delegateRegistry;
Functions
constructor
constructor(address initOwner, address delegateRegistry_) Ownable(initOwner);
checkDelegateForERC721
Function to check if an address is delegated for a vault for an ERC-721 token
This function does not ensure the vault is the current owner of the token
function checkDelegateForERC721(address delegate, address vault, address nftContract, uint256 tokenId)
external
view
returns (bool);
Parameters
Name | Type | Description |
---|---|---|
delegate | address | The address to check for delegation status |
vault | address | The vault address to check against |
nftContract | address | The nft contract address to check |
tokenId | uint256 | The token id to check against |
Returns
Name | Type | Description |
---|---|---|
<none> | bool | bool True is delegated, False if not |
checkDelegateForERC1155
Function to check if an address is delegated for a vault for an ERC-1155 token
This function does not ensure the vault has a balance of the token in question
function checkDelegateForERC1155(address delegate, address vault, address nftContract, uint256 tokenId)
external
view
returns (bool);
Parameters
Name | Type | Description |
---|---|---|
delegate | address | The address to check for delegation status |
vault | address | The vault address to check against |
nftContract | address | The nft contract address to check |
tokenId | uint256 | The token id to check against |
Returns
Name | Type | Description |
---|---|---|
<none> | bool | bool True is delegated, False if not |
setPaused
Function to pause the contract (always return false)
Only callable by the owner
function setPaused(bool status) external onlyOwner;
Parameters
Name | Type | Description |
---|---|---|
status | bool | A boolean indicating to pause or unpause the contract |