ERC721 extensions for complex control of transferability and burnability of tokens
- What is Commander Token?
- Who needs it?
- Mechanism description
- Interface
- Implementation
- State of development
- Getting involved
- License
- Credits and references
Commander Token consists of two token standards enhancing ERC721 with refined control options for transferring or burning tokens.
The first standard, the eponymous Commander Token, is an ERC721 token with partial transferability and burnability. The Commander Token standard allows any behavior from full transferability (regular ERC721), non-transferability (Soulbound Tokens), or anything in between, including transferability controlled by a community (community-bounded tokens), or transferability that changes with time. The same goes for burnability.
The second standard, Locked Tokens, basically binds Tokens together. If a group of tokens, that belong to the same owner, are locked together, it means that they will always be transferred together. As long as the locking exists, the owner can't transfer any of the tokens separately.
We describe below in more detail what each of these standards means, and how their mechanisms work.
Commander Token was created for the Woolball project, but is independent of it and can be used for many other use cases, see Motivation below.
Commander Tokens enable the easy creation of Soulbound tokens. i.e., tokens that cannot be transferred. They even make it easy to create Soulbound tokens that are attached to a name. Further extensions of Soulbound tokens can also be implemented using Commander Tokens, such as community-controlled tokens, i.e. a community can recover them and decide to transfer them to another wallet.
Woolball is an ID system where IDs can create links to one another. For Wooolball links to be meaningful commitments, they need to be able to impose limitations on the ID that created the link. Commander Tokens are used for that.
Locked tokens are created for a collection of tokens, where we want the whole collection to always have the same owner. An example is a classic name system with domains and subsomains. Each domain and each subdomain is represented by a token. If we lock all the subdomains to the domain, then each time the domain is transferred, so are all of its subdomains.
In what follows we discuss for brevity only the "transferability mechanism" of Commander Token, but everything applies mutatis mutandis to the "burnability mechanism".
There are two mechanisms to control the transferability of a Commander Token.
The first is a simple setTransferable
function, that marks the token as transferable or not. In our reference implementation this function is called by the owner of the token, but more elaborate implementations giving control to someone else, a community, or even a smart contract are possible.
The second mechanism is dependence. It is a more sophisticated one. Using this mechanism, the owner of a token can set a token to depend on another token, possibly from another contract. Once a token depends on another token, it is transferable only if the other token is transferable as well. A token can be dependent on many other different tokens, in which case it is transferable only if they are all transferable as well.
Dependence can be removed by the owner of the token only if the token it depends on is transferable. Otherwise, to remove a dependency, a call from the contract of the token we depend on is needed.
Locked Tokens enable the automatic transfer of tokens.
If token A is locked to B, then:
- A cannot be transferred or burned unless B is transferred or burned, and
- every transfer of B also transfers A.
Locking is possible if and only if both tokens have the same owner.
The interface is a list of public and external functions that the contract provides.
The full interface in solidity is in the file ICommanderToken.sol
.
Sets dependence of one token (called tokenId
in most functions) on another token (CTId
, referred to as the Commander Token).
The dependence means that if CT is not transferable or burnable, then the same holds for the token represented by tokenId
.
A dependency can be removed either by the owner of tokenId
(in case CTId
is both transferable or burnable) or by the transaction from contract CTContractAddress
.
setDependence(tokenId, CTContractAddress, CTId) external; setDependenceUnsafe(tokenId, CTContractAddress, CTId) external; removeDependence(tokenId, CTContractAddress, CTId) external; isDependent(tokenId, CTContractAddress, CTId) external view returns (bool);
These functions are for managing the effect of the dependence on tokens. If a token is nontransferable, then all of the tokens depending on it are nontransferable as well. If a token is unburnable, then all the tokens depending on it are unburnable as well.
setTransferable(tokenId, transferable) external; setBurnable(uint256 tokenId, burnable) external;
The following functions set and check the transferability/burnability properties of the token.
setTransferable(tokenId, transferable) external; setBurnable(tokenId, bool burnable) external; isTransferable(tokenId) external view returns (bool); isBurnable(tokenId) external view returns (bool);
The following functions check if all the dependencies of a token are transferable/burnable.
isDependentTransferable(tokenId) external view returns (bool); isDependentBurnable(tokenId) external view returns (bool);
Finally, the following functions check if the token can be transferred/burned, i.e. it is a combination of the previous two methods.
isTokenTransferable(tokenID) external view returns (bool); isTokenBurnable(tokenID) external view returns (bool);
The whitelist mechanism allows selective transferability, meaning that even if a token is nontransferable in general, it can still be transferable to a specific list of tokens.
The following functions are provided.
setTransferWhitelist(tokenId, whitelistAddress, isWhitelisted) isAddressWhitelisted(tokenId, whitelistAddress) returns (bool); isTransferableToAddress(tokenId, transferToAddress) returns (bool); isDependentTransferableToAddress(tokenId, transferToAddress) returns (bool); isTokenTransferableToAddress(tokenId, transferToAddress) returns (bool);
The full interface in Solidity is in the file ILockedToken.sol
.
lock(tokenId, CTContract, CTId) unlock(tokenId) isLocked(tokenId) returns (address, uint256);
This repository includes a reference implementation of Commander Token and Locked Token.
All of the functions are virtual and can be overridden in case you need to extend the functionality.
Commander Token and Locked Token are both a work in progress. The functionality is fully implemented, and there are tests for all of the functions, but the code has not been audited and is not suitable for use on live blockchains at the moment.
Commander Token and Locked Token are part of Woolball project. Join the Woolball Discord if you would like to get involved in this project.
The code in this repository is published under MIT license. The content in this repository is published under CC-BY-SA 3.0 license.
Commander Token and Locked Tokens were created by the Woollball team, led by Tomer Leicht and Eyal Ron.