Multiple third parties with their own NFT contracts (ERC721, ERC1155, etc) want to be part of Decentraland, but the current Decentraland collections implementations are not always a good fit for their use cases. Therefore, a smart contract is going to be created to have a decentralized way where they can map 3d assets to their already created NFTs.
urn:decentraland:{protocol}:collections-thirdparty:{third-party-name}:{collection-id}:{item-id}
urn:decentraland:matic:collections-thirdparty:cryptohats:0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd:0
urn:decentraland:matic:collections-thirdparty:cryptohats:summer:hat1
matic
in production environments because the
smart contract registry will be deployed in the Polygon network. For testing purposes, we
will use mumbai
.
Each Third Party will require to create and maintain an API with these endpoints:
@GET /registry/:registry-id/owners-bloom-filter
get a
bloom filter as a hex value
comprising all the owners a registry has
@GET /registry/:registry-id/address/:address/assets
get a list of assets
associated with a given address
@GET /registry/:registry-id/address/:address/assets/:id
get if a dcl item is
owned by a given address
@POST /registry/:registry-id/ownership
that receives an array of data and
performs validations in batch.
It is recommended to accept any format for the
:address
parameter: checksummed, lowercased, uppercased, mixed, etc. You can always checksum and validate if it is a valid Ethereum address later.
GET /registry/:registry-id/owners-bloom-filter
Example: https://api.cryptohats.io/registry/cryptohats/owners-bloom-filter
{
"data": "00100000000000000000000000000000080010000800000000000000000000000080000000000000000000000000000000000000000000000000000000000002000000000004000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000800000000040000000000000000000000000000020000000000000280000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040040000000000000000000000000000000010000000000000000000000000000"
}
If the registry is invalid or non-existent, the data property should return an empty string.
{
"data": ""
}
GET /registry/:registry-id/address/:address/assets
Example: https://api.cryptohats.io/registry/cryptohats/address/0x0f5d2fb29fb7d3cfee444a200298f468908cc942/assets
{
"address": "0x0f5d2fb29fb7d3cfee444a200298f468908cc942",
"assets": [
{
"id": "0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd:0",
"amount": 1,
"urn": {
"decentraland": "urn:decentraland:matic:collections-thirdparty:cryptohats:0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd:0"
}
},
{
"id": "0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd:1",
"amount": 1,
"urn": {
"decentraland": "urn:decentraland:matic:collections-thirdparty:cryptohats:0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd:1"
}
}
],
"total": 100,
"page": 1,
"next": "https://....&startAt=1234"
}
If the registry is invalid or the address does not own assets the assets
prop
should be an empty array. The next
property should be a falsy value, preferebly
an empty string for this scenario and when the last page is reached too.
{
"address": "0x0f5d2fb29fb7d3cfee444a200298f468908cc942",
"assets": [],
"total": 0,
"page": 1,
"next": ""
}
GET /registry/:registry-id/address/:address/assets/:id
Example: https://api.cryptohats.io/registry/cryptohats/address/0x0f5d2fb29fb7d3cfee444a200298f468908cc942
/assets/0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd:1
{
"id": "0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd:1",
"amount": 1,
"urn": {
"decentraland": "urn:decentraland:matic:collections-thirdparty:cryptohats:0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd:1"
}
}
If the registry is invalid, the address does not own the asset, or the id non-existent the
urn
prop should set decentraland
as an empty string.
{
"id": "0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd:1",
"amount": 0,
"urn": {
"decentraland": ""
}
}
When provided with a list of owners and their respective items, the expected functionality is to determine whether each owner owns the corresponding item or not.
POST /registry/:registry-id/ownership
Example: https://api.cryptohats.io/registry/cryptohats/ownership
body:
[
{
"urn_decentraland": "urn:decentraland:matic:collections-thirdparty:cryptohats:0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd:1",
"address": "0x0f5d2fb29fb7d3cfee444a200298f468908cc942"
},
{
"urn_decentraland": "...",
"address": "..."
},
...
]
[
{
"urn_decentraland": "urn:decentraland:matic:collections-thirdparty:cryptohats:0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd:1",
"address": "0x0f5d2fb29fb7d3cfee444a200298f468908cc942",
"owned": true
},
{
"urn_decentraland": "...",
"address": "...",
"owned": [true|false]
}
...
]
The TPR smart contract is going to be deployed on Polygon and support meta-transactions with the EIP-712 to reduce operational costs. The contract can be easily migrated because it doesn't store or mint any tokens. The main purposes of this registry is to have an on-chain way to check whether an item has been approved by a committee member and therefore can be submitted to the Decentraland catalysts. And, to check whether a third party or item has been approved or rejected.
The contract is not a storage-gas-consumption top efficient because it prioritizes looping through third parties and items without the need of indexing historical data.
The TPR smart contract supports different roles:
The third party record is going to be identified by a unique id. For simplicity and in order
to support different uses cases, this id is going to be a string. By using a string, we can
support ids as URNs, UUIDs, auto incremental values, etc. The current identifier used in
Decentraland is the URN, therefore, an id urn like
urn:decentraland:matic:collections-thirdparty1
is what we expect to be using.
Each third party record can only be added by a committee member and it has the following properties:
struct ThirdParty {
string metadata;
string resolver;
uint256 maxItems;
bool isApproved;
mapping(address => bool) managers;
mapping(string => Item) items;
string[] itemIds;
uint256 registered;
}
metadata
: string with the following shape: type:version:name:description
. i.e:
tp:1:third party 1:the third party 1 description
.
resolver
: string with the third party API resolver. This API will be used for services to get which
Decentraland asset should be mapped to which NFT token.
We call Decentraland asset to every asset that is submitted to the Decentraland
catalyst. i.e: https://api.thirdparty1.com/v1/get-owned-nfts/:owner
maxItems
: represents the maximum number of items that a third party can have. We call them
item slots. Item slots can be bought by everyone at any time, in multiple
occasions, by using MANA. So, it is not necessary to be a third party manager to buy item
slots. Item slots are going to be bought by tiers (tier1: 100 items, tier2: 1000 items,
tier3: 1000, etc.). The tiers' value and price are going to be defined in another smart
contract called Tiers that will allow querying the tiers' price and value by its index.
isApproved
: whether a third party is approved or not.
managers
: third party managers.
items
: third party's items. An item has its own properties defined here.
itemIds
: in order to allow looping through the items added without the need of indexing historic
events, we need to keep track of their ids.
registered
: simple boolean that helps to check whether a third party has been added or not.
A third party record can't be removed but approved/rejected by a committee member.
As we mentioned with the items, the third parties can be looped off-chain without the need of indexing historic events.
Items are going to be identifying with an id like the third party records. In order to support
the concept of erc721 contracts or collections, the item id will looks like:
collection:item
. i.e:
0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd:1
(NFT smart contract:
0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd, NFT tokenId: 1),
great_collection:type1
, etc.
As you may notice, the concept of a collection is merely "virtual" and will be part of the item id. If there is a use case where a third party has multiple collections, they can be easily filtered by comparing strings off-chain.
Items can only be added to a third party if there are item slots available.
struct Item {
string metadata;
string contentHash;
bool isApproved;
uint256 registered;
}
metadata
: string with the following shape:
type:version:name:description:category:bodyshapes
. i.e:
w:1:third party item 1:the third party item 1 description:hat:BaseMale,BaseFemale
.
contentHash
: string with the content hash of the item. We are using content hashing like IPFS.
isApproved
: whether an item is approved or not.
registered
: simple boolean that helps to check whether an item has been added or not.
Similar to third parties, items can't be removed but approved/rejected by committee members.
Each deployment must check if the URN has collections-thirdparty
in order to know
that the tpr-graph should be used. The
query to that subgraph must check:
urn:decentraland:matic:collections-thirdparty:cryptohats:0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd:0
.
0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd:0
is the same as the content hash of
the item that is being uploaded