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

Git Source

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

NameTypeDescription
databytes[]The encoded function data for each of the calls to make to this contract

Returns

NameTypeDescription
resultsbytes[]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

NameTypeDescription
toaddressThe address to act as delegate
rightsbytes32Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
enableboolWhether to enable or disable this delegation, true delegates and false revokes

Returns

NameTypeDescription
delegationHashbytes32The 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

NameTypeDescription
toaddressThe address to act as delegate
contract_addressThe contract whose rights are being delegated
rightsbytes32Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
enableboolWhether to enable or disable this delegation, true delegates and false revokes

Returns

NameTypeDescription
delegationHashbytes32The 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

NameTypeDescription
toaddressThe address to act as delegate
contract_addressThe contract whose rights are being delegated
tokenIduint256The token id to delegate
rightsbytes32Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
enableboolWhether to enable or disable this delegation, true delegates and false revokes

Returns

NameTypeDescription
delegationHashbytes32The 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

NameTypeDescription
toaddressThe address to act as delegate
contract_addressThe address for the fungible token contract
rightsbytes32Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
amountuint256The amount to delegate, > 0 delegates and 0 revokes

Returns

NameTypeDescription
delegationHashbytes32The 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

NameTypeDescription
toaddressThe address to act as delegate
contract_addressThe address of the contract that holds the token
tokenIduint256The token id to delegate
rightsbytes32Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
amountuint256The amount of that token id to delegate, > 0 delegates and 0 revokes

Returns

NameTypeDescription
delegationHashbytes32The 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

NameTypeDescription
toaddressThe potential delegate address
fromaddressThe potential address who delegated rights
rightsbytes32Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only

Returns

NameTypeDescription
<none>boolvalid 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

NameTypeDescription
toaddressThe delegated address to check
fromaddressThe cold wallet who issued the delegation
contract_addressThe specific contract address being checked
rightsbytes32Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only

Returns

NameTypeDescription
<none>boolvalid 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

NameTypeDescription
toaddressThe delegated address to check
fromaddressThe wallet that issued the delegation
contract_addressThe specific contract address being checked
tokenIduint256The token id for the token to delegating
rightsbytes32Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only

Returns

NameTypeDescription
<none>boolvalid 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

NameTypeDescription
toaddressThe delegated address to check
fromaddressThe cold wallet who issued the delegation
contract_addressThe address of the token contract
rightsbytes32Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only

Returns

NameTypeDescription
<none>uint256balance 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

NameTypeDescription
toaddressThe delegated address to check
fromaddressThe cold wallet who issued the delegation
contract_addressThe address of the token contract
tokenIduint256The token id to check the delegated amount of
rightsbytes32Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only

Returns

NameTypeDescription
<none>uint256balance 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

NameTypeDescription
toaddressThe address to retrieve delegations for

Returns

NameTypeDescription
delegationsDelegation[]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

NameTypeDescription
fromaddressThe address to retrieve delegations for

Returns

NameTypeDescription
delegationsDelegation[]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

NameTypeDescription
toaddressThe address to retrieve incoming delegation hashes for

Returns

NameTypeDescription
delegationHashesbytes32[]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

NameTypeDescription
fromaddressThe address to retrieve outgoing delegation hashes for

Returns

NameTypeDescription
delegationHashesbytes32[]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

NameTypeDescription
delegationHashesbytes32[]is an array of hashes that correspond to delegations

Returns

NameTypeDescription
delegationsDelegation[]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

Git Source

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

NameTypeDescription
delegateaddressThe address to check for delegation status
vaultaddressThe vault address to check against
nftContractaddressThe nft contract address to check
tokenIduint256The token id to check against

Returns

NameTypeDescription
<none>boolbool 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

NameTypeDescription
delegateaddressThe address to check for delegation status
vaultaddressThe vault address to check against
nftContractaddressThe nft contract address to check
tokenIduint256The token id to check against

Returns

NameTypeDescription
<none>boolbool True is delegated, False if not

TLNftDelegationRegistry

Git Source

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

NameTypeDescription
delegateaddressThe address to check for delegation status
vaultaddressThe vault address to check against
nftContractaddressThe nft contract address to check
tokenIduint256The token id to check against

Returns

NameTypeDescription
<none>boolbool 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

NameTypeDescription
delegateaddressThe address to check for delegation status
vaultaddressThe vault address to check against
nftContractaddressThe nft contract address to check
tokenIduint256The token id to check against

Returns

NameTypeDescription
<none>boolbool 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

NameTypeDescription
statusboolA boolean indicating to pause or unpause the contract