ADR-109: On-chain validators

More details about this document
Latest published version:
GitHub decentraland/adr (pull requests, new issue, open issues)
Edit this documentation:
GitHub View commits View commits on


Evaluates an alternative to validate deployments in the content server to remove the dependency on TheGraph. Using an RPC provider and a set of upgradable smart contracts are enough to validate access to the entities being deployed.


Remove the usage of subgraphs on behalf of smart contracts to perform validations when deploying entities (access-checkers). This removes the dependency with subgraphs and creates an immutable and decentralized way of validating entity deployments that need to query blockchain information. The content server will still need to use an RPC provider but this RPC is also used nowadays to validate smart contract wallets' deployments.

The final goal is to have a set of different upgradable smart contracts managed by the DAO that emulate the subgraphs. The DAO will be in charge of upgrading them in case of any issues or new logic needed.

Disclaimer: This document also describes some migration ideas needed to make profile validations possible. The effort needed for the migration won't be covered. It will require a new RFC or ADR describing the migration proposal. Lambdas are out of the scope for this iteration as they are not part of the Decentraland Protocol.


Subgraphs are being used by the content server to check the access to:

There are two types of deployments:

The following sections will describe how to use only RPC calls and smart contracts to validate deployments by entity type.

Ethereum & Polygon block numbers

Each entity deployment has a timestamp. We get the block number for that timestamp by using subgraphs per each chain. It is not possible to get a block number by timestamp directly with only one RPC request because timestamps are just part of the block metadata. In order to remove this dependency, we need extra work:


Decentraland Name

The DCLRegistrar contract has a method to get who is the owner of a specific domain: getOwnerOf(subdomain: string).

Wearables & Emotes

Each Profile entity stores the wearables and emotes equipped by the user. Wearables are stored under entity.metadata.avatar.wearables as an array of item URNs, and Emotes are stored under entity.metadata.avatar.emotes as an array of { slot: string, urn: string }. In both cases, the URNs used are the item's urn, not the token's urn. Checking if a user owns a specific item does not scale, since it would require looping within a contract method. Users that own too many NFTs or collections that are too big could cause this to lag or timeout. Another alternative could be making several RPC calls between the catalyst and the Ethereum nodes, which also would not scale well. We should start storing the token's urn within the profiles to make the access checks possible in a single RPC call. The ERC721 standard has a method ownerOf(tokenId: uint256) which serves to get the owner of the NFT.

This document will propose two ideas on how the old profiles can be migrated but an new RFC or ADR should be needed describing the final approach.

For new profiles, a new ADR with a date to make it effective to start receiving profiles with an extended urn with the token id at the end. This implies changes in the Explorer, Kernel, Catalysts, and URN Resolver. New urn resolvers

The Explorer should show the items grouped by item kinds but allow the user to select the specific NFT to add to their profile. Once the users save their profile for the first time once the ADR is running effectively, a process in the client should select the first NFT for each item kind saved in the profile. E.g: if the user has this item urn:decentraland:ethereum:collections-v1:wonderzone_steampunk:steampunk_jacket selected in their profile but he has 10 of them with the token ids from 1 to 10, the explorer should select the first (urn:decentraland:ethereum:collections-v1:wonderzone_steampunk:steampunk_jacket:1) one and replace it. For off-chain wearables, we should not do any kind of validation.

The Kernel and the Catalysts should accept and return all the NFTs. The lambdas e.g: /collections/wearables-by-owner/{address} and /collections/emotes-by-owner/{address} will return the NFTs' urns and not the items' urns. There is a change needed in the collections subgraphs as well to use the NFT urn instead of the item urn for the NFT entity (1; 2).

The catalyst should extract the contract address and token id from each asset in the profile to check if the profile owner owns the NFT by using IERC721(contract_address).ownerOf(tokenId). Also, the contract address must be a valid one: a Decentraland or ThirdParty collection.

Entity Example
  "entityVersion": "v3",
  "entityType": "profile",
  "entityId": "bafkreihf6pm7ethywxzjta364wixtrqcrc4th6miumczizldc32rythzua",
  "entityTimestamp": 1663327122490,
  "deployedBy": "0x87956abc4078a0cc3b89b419928b857b8af826ed",
  "pointers": [
  "content": [
      "key": "body.png",
      "hash": "bafkreibalpcvevsbke5r4vzkbkltjwwgfxm6mkl74asxtxlgrvgbqxafma"
      "key": "face256.png",
      "hash": "bafkreietgcdkbx6mtuxcfjxwifxyicpe5s2mtumycbvlfxvsdn3htnxlse"
  "metadata": {
    "avatars": [
        "hasClaimedName": true,
        "name": "Nacho",
        "description": "This is fine",
        "tutorialStep": 355,
        "userId": "0x87956abc4078a0cc3b89b419928b857b8af826ed",
        "email": "",
        "ethAddress": "0x87956abc4078a0cc3b89b419928b857b8af826ed",
        "version": 62,
        "avatar": {
          "bodyShape": "urn:decentraland:off-chain:base-avatars:BaseMale",
          "wearables": [
          "emotes": [
              "slot": 1,
              "urn": "wave"
              "slot": 2,
              "urn": "urn:decentraland:matic:collections-v2:0x875146d1d26e91c80f25f5966a84b098d3db1fc8:1"
              "slot": 3,
              "urn": "urn:decentraland:matic:collections-v2:0xef832a5183bf2e4099efed4c6ec981b7b41aa545:0"
              "slot": 4,
              "urn": "raiseHand"
              "slot": 5,
              "urn": "headexplode"
              "slot": 6,
              "urn": "money"
              "slot": 7,
              "urn": "kiss"
              "slot": 8,
              "urn": "fistpump"
              "slot": 9,
              "urn": "handsair"
          "snapshots": {
            "body": "bafkreibalpcvevsbke5r4vzkbkltjwwgfxm6mkl74asxtxlgrvgbqxafma",
            "face256": "bafkreietgcdkbx6mtuxcfjxwifxyicpe5s2mtumycbvlfxvsdn3htnxlse"
          "eyes": {
            "color": {
              "r": 0.23046875,
              "g": 0.625,
              "b": 0.3125,
              "a": 1
          "hair": {
            "color": {
              "r": 0.35546875,
              "g": 0.19140625,
              "b": 0.05859375,
              "a": 1
          "skin": {
            "color": {
              "r": 0.94921875,
              "g": 0.76171875,
              "b": 0.6484375,
              "a": 1
        "interests": [],
        "hasConnectedWeb3": true
  "localTimestamp": 1663327126487



Checks by using the coordinates and the entity deployer (signer):


If the LAND is owned by the Estate Contract. The estate id should be retrieved. Checks by using the estate id and the entity deployer (signer):

If any of the above checks is valid, the deployment is valid.

Ethereum Items

Polygon Items: Wearables & Emotes

If every of the above checks are true, the deployment is valid.

Third Party Wearables

If every of the above checks are true, the deployment may be valid. The catalyst also checks if the content hash of the deployment is part of the Merkle Tree in order to consider the deployment valid.


Copyright and related rights waived via CC0-1.0. Draft