Skip to content

Latest commit

 

History

History
854 lines (783 loc) · 33.3 KB

File metadata and controls

854 lines (783 loc) · 33.3 KB
.. _protocol-ref-data-contract:

Data Contract

Data Contract Overview

Data contracts define the schema (structure) of data an application will store on Dash Platform. Contracts are described using JSON Schema which allows the platform to validate the contract-related data submitted to it.

The following sections provide details that developers need to construct valid contracts. All data contracts must define at least one document or token. A contract may define multiple documents and/or tokens.

Fees

Dash Platform charges fees for registering data contracts based on complexity. These fees compensate evonodes for their role in storing and processing contract-related data.

The table below outlines the current fee structure for various data contract components. Fees are denominated in DASH and are charged at registration time based on the structure of the contract.

Fee Component Amount (DASH) Description
base_contract_registration_fee 0.1 Fixed fee for every data contract. Covers the baseline cost of anchoring a contract into platform state.
document_type_registration_fee 0.02 Charged per document type. Reflects indexing and storage schema overhead.
document_type_base_non_unique
_index_registration_fee
0.01 Per non-unique index in a document type. Supports query operations.
document_type_base_unique_index
_registration_fee
0.01 Per unique index. Enforces uniqueness and adds validation complexity.
document_type_base_contested
_index_registration_fee
1.0 Per contested index. Used for identity/username resolution; requires voting and conflict resolution by masternodes and evonodes.
token_registration_fee 0.1 Per token defined in the contract. Reflects additional overhead from managing balances, transfers, and supply.
token_uses_perpetual
_distribution_fee
0.1 Additional fee for tokens that use perpetual (e.g., block-based) distribution mechanisms. These create ongoing state changes triggered by network events.
token_uses_pre_programmed
_distribution_fee
0.1 Charged when tokens use scheduled distributions (e.g., airdrops). Adds periodic complexity.
search_keyword_fee 0.1 per keyword Charged per search keyword defined. Keywords enable reverse lookups and indexing, increasing on-chain storage and filtering load.

These fees are additive. For example, a contract that defines two document types, each with one unique index, and one token using a perpetual distribution will incur the following total fee:

0.1 (base) + 0.02×2 (documents) + 0.01×2 (unique indices) = 0.16 DASH
0.1 (token) + 0.1 (perpetual) = 0.2 DASH

Total fee: 0.16 + 0.2 = 0.36 DASH

General Constraints

There are a variety of constraints currently defined for performance and security reasons. The following constraints are applicable to all aspects of data contracts.

Data Size

Parameter Size
Maximum serialized data contract size 16384 bytes (16 KB)
Maximum field value size 5120 bytes (5 KB)
Maximum state transition size 20480 bytes (20 KB)

A document cannot exceed the maximum state transition size in any case. For example, although it is possible to define a data contract with 10 document fields that each support the maximum field size (5120), it is not possible to create a document where all 10 fields contain the full 5120 bytes. This is because the overall document and state transition containing it would be too large (5120 * 10 = 51200 bytes).

Additional Properties

Although JSON Schema allows additional, undefined properties by default, they are not allowed in Dash Platform data contracts. Data contract validation will fail if they are not explicitly forbidden using the additionalProperties keyword anywhere properties are defined (including within document properties of type object).

Include the following at the same level as the properties keyword to ensure proper validation:

"additionalProperties": false

Data Contract Object

The data contract object consists of the following fields as defined in the Rust reference client (rs-dpp):

Property Type Size Description
$version unsigned integer 32 bits The platform protocol version (currently 8)
$schema string Varies A valid URL
id array of bytes 32 bytes Contract ID generated from ownerId and entropy (content media type: application/x.dash.dpp.identifier)
version unsigned integer Yes The data contract version
ownerId array of bytes 32 bytes Identity that registered the data contract defining the document (content media type: application/x.dash.dpp.identifier)
documents object Varies (Optional *) Document definitions (see Contract Documents for details)
config DataContractConfig Varies (Optional) Internal configuration for the contract
$defs object Varies (Optional) Definitions for $ref references used in the documents object (if present, must be a non-empty object with <= 100 valid properties)
groups Group Varies (Optional) Groups that allow for specific multiparty actions on the contract.
tokens object Varies (Optional *) Token definitions (see Contract Tokens for details)
keywords array of strings Varies (Optional) Keywords associated with the contract to improve searchability. Maximum of 20 words.
description string 3-100 characters (Optional) Brief description of the contract.

* The data contract object must define documents or tokens. It may include both documents and tokens.

Data Contract schema

The full schema is defined is rs-dpp, hosted on GitHub, and can be viewed by expanding this dropdown:

::: {dropdown} Full schema

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/dashpay/platform/blob/master/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json",
  "type": "object",
  "$defs": {
    "documentProperties": {
      "type": "object",
      "patternProperties": {
        "^[a-zA-Z0-9-_]{1,64}$": {
          "type": "object",
          "allOf": [
            {
              "$ref": "#/$defs/documentSchema"
            }
          ],
          "unevaluatedProperties": false
        }
      },
      "propertyNames": {
        "pattern": "^[a-zA-Z0-9-_]{1,64}$"
      },
      "minProperties": 1,
      "maxProperties": 100
    },
    "documentSchemaArray": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/$defs/documentSchema"
          }
        ],
        "unevaluatedProperties": false
      }
    },
    "documentSchema": {
      "type": "object",
      "properties": {
        "$id": {
          "type": "string",
          "pattern": "^#",
          "minLength": 1
        },
        "$ref": {
          "type": "string",
          "pattern": "^#",
          "minLength": 1
        },
        "$comment": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/core#/properties/$comment"
        },
        "description": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/meta-data#/properties/description"
        },
        "examples": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/meta-data#/properties/examples"
        },
        "multipleOf": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/properties/multipleOf"
        },
        "maximum": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/properties/maximum"
        },
        "exclusiveMaximum": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/properties/exclusiveMaximum"
        },
        "minimum": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/properties/minimum"
        },
        "exclusiveMinimum": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/properties/exclusiveMinimum"
        },
        "maxLength": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/properties/maxLength"
        },
        "minLength": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/properties/minLength"
        },
        "pattern": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/properties/pattern"
        },
        "maxItems": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/properties/maxItems"
        },
        "minItems": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/properties/minItems"
        },
        "uniqueItems": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/properties/uniqueItems"
        },
        "contains": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/applicator#/properties/contains"
        },
        "maxProperties": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/properties/maxProperties"
        },
        "minProperties": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/properties/minProperties"
        },
        "required": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/properties/required"
        },
        "additionalProperties": {
          "type": "boolean",
          "const": false
        },
        "properties": {
          "$ref": "#/$defs/documentProperties"
        },
        "dependentRequired": {
          "type": "object",
          "minProperties": 1,
          "additionalProperties": {
            "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/$defs/stringArray"
          }
        },
        "const": true,
        "enum": {
          "type": "array",
          "items": true,
          "minItems": 1,
          "uniqueItems": true
        },
        "type": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/properties/type"
        },
        "format": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/format-annotation#/properties/format"
        },
        "contentMediaType": {
          "$ref": "https://json-schema.org/draft/2020-12/meta/content#/properties/contentMediaType"
        },
        "byteArray": {
          "type": "boolean",
          "const": true
        },
        "position": {
          "type": "integer",
          "minimum": 0
        }
      },
      "dependentSchemas": {
        "byteArray": {
          "description": "should be used only with array type",
          "properties": {
            "type": {
              "type": "string",
              "const": "array"
            }
          }
        },
        "contentMediaType": {
          "if": {
            "properties": {
              "contentMediaType": {
                "const": "application/x.dash.dpp.identifier"
              }
            }
          },
          "then": {
            "properties": {
              "byteArray": {
                "const": true
              },
              "minItems": {
                "const": 32
              },
              "maxItems": {
                "const": 32
              }
            },
            "required": [
              "byteArray",
              "minItems",
              "maxItems"
            ]
          }
        },
        "pattern": {
          "description": "prevent slow pattern matching of large strings",
          "properties": {
            "maxLength": {
              "type": "integer",
              "minimum": 0,
              "maximum": 50000
            }
          },
          "required": [
            "maxLength"
          ]
        },
        "format": {
          "description": "prevent slow format validation of large strings",
          "properties": {
            "maxLength": {
              "type": "integer",
              "minimum": 0,
              "maximum": 50000
            }
          },
          "required": [
            "maxLength"
          ]
        }
      },
      "allOf": [
        {
          "$comment": "require index for object properties",
          "if": {
            "properties": {
              "type": {
                "const": "object"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "properties": {
              "properties": {
                "type": "object",
                "additionalProperties": {
                  "type": "object",
                  "properties": {
                    "position": true
                  },
                  "required": [
                    "position"
                  ]
                }
              }
            }
          }
        },
        {
          "$comment": "allow only byte arrays",
          "if": {
            "properties": {
              "type": {
                "const": "array"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "properties": {
              "byteArray": true
            },
            "required": [
              "byteArray"
            ]
          }
        },
        {
          "$comment": "all object properties must be defined",
          "if": {
            "properties": {
              "type": {
                "const": "object"
              }
            },
            "not": {
              "properties": {
                "$ref": true
              },
              "required": [
                "$ref"
              ]
            }
          },
          "then": {
            "properties": {
              "properties": {
                "$ref": "#/$defs/documentProperties"
              },
              "additionalProperties": {
                "$ref": "#/$defs/documentSchema/properties/additionalProperties"
              }
            },
            "required": [
              "properties",
              "additionalProperties"
            ]
          }
        }
      ]
    },
    "documentActionTokenCost": {
      "type": "object",
      "properties": {
        "contractId": {
          "type": "array",
          "contentMediaType": "application/x.dash.dpp.identifier",
          "byteArray": true,
          "minItems": 32,
          "maxItems": 32
        },
        "tokenPosition": {
          "type": "integer",
          "minimum": 0,
          "maximum": 65535
        },
        "amount": {
          "type": "integer",
          "minimum": 1,
          "maximum": 281474976710655
        },
        "effect": {
          "type": "integer",
          "enum": [
            0,
            1
          ],
          "description": "0 - TransferTokenToContractOwner (default), 1 - Burn"
        },
        "gasFeesPaidBy": {
          "type": "integer",
          "enum": [
            0,
            1,
            2
          ],
          "description": "0 - DocumentOwner (default), 1 - ContractOwner, 2 - PreferContractOwner"
        }
      },
      "required": [
        "tokenPosition",
        "amount"
      ],
      "additionalProperties": false
    }
  },
  "properties": {
    "type": {
      "type": "string",
      "const": "object"
    },
    "$schema": {
      "type": "string",
      "const": "https://github.com/dashpay/platform/blob/master/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json"
    },
    "$defs": {
      "$ref": "#/$defs/documentProperties"
    },
    "indices": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 32
          },
          "properties": {
            "type": "array",
            "items": {
              "type": "object",
              "propertyNames": {
                "maxLength": 256
              },
              "additionalProperties": {
                "type": "string",
                "enum": [
                  "asc"
                ]
              },
              "minProperties": 1,
              "maxProperties": 1
            },
            "minItems": 1,
            "maxItems": 10
          },
          "unique": {
            "type": "boolean"
          },
          "nullSearchable": {
            "type": "boolean"
          },
          "contested": {
            "type": "object",
            "properties": {
              "fieldMatches": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 256
                    },
                    "regexPattern": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 256
                    }
                  },
                  "additionalProperties": false,
                  "required": [
                    "field",
                    "regexPattern"
                  ]
                },
                "minItems": 1
              },
              "resolution": {
                "type": "integer",
                "enum": [
                  0
                ],
                "description": "Resolution. 0 - Masternode Vote"
              },
              "description": {
                "type": "string",
                "minLength": 1,
                "maxLength": 256
              }
            },
            "required": [
              "resolution"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "properties",
          "name"
        ],
        "additionalProperties": false
      },
      "minItems": 1,
      "maxItems": 10
    },
    "signatureSecurityLevelRequirement": {
      "type": "integer",
      "enum": [
        1,
        2,
        3
      ],
      "description": "Public key security level. 1 - Critical, 2 - High, 3 - Medium. If none specified, High level is used"
    },
    "documentsKeepHistory": {
      "type": "boolean",
      "description": "True if the documents keep all their history, default is false"
    },
    "documentsMutable": {
      "type": "boolean",
      "description": "True if the documents are mutable, default is true"
    },
    "canBeDeleted": {
      "type": "boolean",
      "description": "True if the documents can be deleted, default is true"
    },
    "transferable": {
      "type": "integer",
      "enum": [
        0,
        1
      ],
      "description": "Transferable without a marketplace sell. 0 - Never, 1 - Always"
    },
    "tradeMode": {
      "type": "integer",
      "enum": [
        0,
        1
      ],
      "description": "Built in marketplace system. 0 - None, 1 - Direct purchase (The user can buy the item without the need for an approval)"
    },
    "creationRestrictionMode": {
      "type": "integer",
      "enum": [
        0,
        1,
        2
      ],
      "description": "Restrictions of document creation. 0 - No restrictions, 1 - Owner only, 2 - No creation (System Only)"
    },
    "requiresIdentityEncryptionBoundedKey": {
      "type": "integer",
      "enum": [
        0,
        1,
        2
      ],
      "description": "Key requirements. 0 - Unique Non Replaceable, 1 - Multiple, 2 - Multiple with reference to latest key."
    },
    "requiresIdentityDecryptionBoundedKey": {
      "type": "integer",
      "enum": [
        0,
        1,
        2
      ],
      "description": "Key requirements. 0 - Unique Non Replaceable, 1 - Multiple, 2 - Multiple with reference to latest key."
    },
    "tokenCost": {
      "type": "object",
      "properties": {
        "create": {
          "$ref": "#/$defs/documentActionTokenCost"
        },
        "replace": {
          "$ref": "#/$defs/documentActionTokenCost"
        },
        "delete": {
          "$ref": "#/$defs/documentActionTokenCost"
        },
        "transfer": {
          "$ref": "#/$defs/documentActionTokenCost"
        },
        "update_price": {
          "$ref": "#/$defs/documentActionTokenCost"
        },
        "purchase": {
          "$ref": "#/$defs/documentActionTokenCost"
        }
      },
      "additionalProperties": false
    },
    "properties": {
      "type": "object",
      "additionalProperties": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/$defs/documentSchema"
          }
        ],
        "unevaluatedProperties": false
      },
      "properties": {
        "$id": true,
        "$ownerId": true,
        "$revision": true,
        "$createdAt": true,
        "$updatedAt": true,
        "$transferredAt": true,
        "$createdAtBlockHeight": true,
        "$updatedAtBlockHeight": true,
        "$transferredAtBlockHeight": true,
        "$createdAtCoreBlockHeight": true,
        "$updatedAtCoreBlockHeight": true,
        "$transferredAtCoreBlockHeight": true
      },
      "propertyNames": {
        "oneOf": [
          {
            "type": "string",
            "pattern": "^[a-zA-Z0-9-_]{1,64}$"
          },
          {
            "type": "string",
            "enum": [
              "$id",
              "$ownerId",
              "$revision",
              "$createdAt",
              "$updatedAt",
              "$transferredAt",
              "$createdAtBlockHeight",
              "$updatedAtBlockHeight",
              "$transferredAtBlockHeight",
              "$createdAtCoreBlockHeight",
              "$updatedAtCoreBlockHeight",
              "$transferredAtCoreBlockHeight"
            ]
          }
        ]
      },
      "minProperties": 1,
      "maxProperties": 100
    },
    "transient": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "keywords": {
      "type": "array",
      "description": "List of up to 20 descriptive keywords for the contract, used in the Keyword Search contract",
      "items": {
        "type": "string",
        "minLength": 3,
        "maxLength": 50
      },
      "maxItems": 20,
      "uniqueItems": true
    },
    "additionalProperties": {
      "type": "boolean",
      "const": false
    }
  },
  "required": [
    "$schema",
    "type",
    "properties",
    "additionalProperties"
  ]
}

:::

Data Contract id

The data contract id is a hash of the ownerId and entropy as shown here.

// From the Rust reference implementation (rs-dpp)
// generate_data_contract.rs
/// Generate data contract id based on owner id and identity nonce
pub fn generate_data_contract_id_v0(
    owner_id: impl AsRef<[u8]>,
    identity_nonce: IdentityNonce,
) -> Identifier {
    let mut b: Vec<u8> = vec![];
    let _ = b.write(owner_id.as_ref());
    let _ = b.write(identity_nonce.to_be_bytes().as_slice());
    Identifier::from(hash_double(b))
}

Data Contract version

The data contract version is an integer representing the current version of the contract. This
property must be incremented if the contract is updated.

Data Contract documents

See the data contract documents page for details.

Data Contract config

The data contract config defines configuration options for data contracts, controlling their lifecycle, mutability, history management, and encryption requirements. Data contracts support three categories of configuration options to provide flexibility in contract design. It is only necessary to include them in a data contract when non-default values are used. The default values for these configuration options are defined in the Rust DPP implementation.

Contract option Default Description
canBeDeleted false Determines if the contract can be deleted
readonly false Determines if the contract is read-only. Read-only contracts cannot be updated.
keepsHistory false Determines if changes to the contract itself are tracked, maintaining a historical record of contract modifications.
Document default option Default Description
documentsKeepHistory
ContractDefault
false Sets the default behavior for tracking historical changes of documents within the contract
documentsMutable
ContractDefault
true Sets the default mutability of documents within the contract, indicating if documents can be edited.
documentsCanBeDeleted
ContractDefault
true Sets the default behavior for whether documents within the contract can be deleted

Key Management

Dash Platform provides an advanced level of security and control by enabling the isolation of encryption and decryption keys on a contract-specific or document-specific basis. This granular approach to key management enables developers to configure their applications for whatever level of security they require.

Security option Description
requiresIdentity
EncryptionBoundedKey
Indicates the contract requires a contract-specific identity encryption key. Key options:
0 - Unique non-replaceable
1 - Multiple
2 - Multiple with reference to latest
requiresIdentity
DecryptionBoundedKey
Indicates the contract requires a contract-specific identity decryption key. Key options:
0 - Unique non-replaceable
1 - Multiple
2 - Multiple with reference to latest

:::{tip} These security options can be set at the root level of the data contract or the root level of specific documents within the contract depending on requirements. :::

Example

The following example (from the DashPay contract's contactRequest document) demonstrates the use of both key-related options at the document level:

"contactRequest": {
  "requiresIdentityEncryptionBoundedKey": 2,
  "requiresIdentityDecryptionBoundedKey": 2,
}

See the data contract config implementation in rs-dpp for more details.

Data Contract groups

Groups can be used to distribute contract configuration and update authorization across multiple identities. They are particularly useful for contracts where multiple parties are involved in controlling or managing contract-specific features. Each group defines a set of member identities, the voting power of each member, and the required power threshold to authorize an action.

  • Each member is assigned an integer power.
  • The group itself has a required power threshold to authorize an action.
  • Groups can have up to 256 members, each with a maximum power of 2^16 - 1.
  • Changes to a token (e.g., mint, burn, freeze) can be configured so they require group authorization.
    • Example: "2-of-3 multisig” among three admins, each with certain voting power.

See the groups implementation in rs-dpp for more details.

Data Contract tokens

:::{versionadded} 2.0.0 :::

  • Tokens provide token-related functionality within the contract, such as base supply, maximum supply, and manual minting/burning rules.
  • Token configurations include change control rules, ensuring proper governance for modifying supply limits and token-related settings.
  • This enables contracts to define and manage tokens while ensuring compliance with governance rules (e.g., who can mint or burn tokens).

Data Contract State Transition Details

There are two data contract-related state transitions: data contract create and data contract update. Details are provided in this section.

Data Contract Create

Data contracts are created on the platform by submitting the data contract object in a data contract create state transition consisting of:

Field Type Size Description
$version unsigned integer 32 bits The platform protocol version (currently 1)
type unsigned integer 8 bits State transition type (0 for data contract create)
dataContract data contract object Varies Object containing the data contract details
identityNonce unsigned integer 64 bits Identity nonce for this transition to prevent replay attacks
entropy array of bytes 32 bytes Entropy used to generate the data contract ID. Generated as shown here.
userFeeIncrease unsigned integer 16 bits Extra fee to prioritize processing if the mempool is full. Typically set to zero.
signaturePublicKeyId unsigned integer 32 bits The id of the identity public key that signed the state transition (=> 0)
signature array of bytes 65 bytes Signature of state transition data

See the data contract create implementation in rs-dpp for more details.

Data Contract Update

Existing data contracts can be updated in certain backwards-compatible ways. The following aspects of a data contract can be updated:

  • Adding a new document
  • Adding a new optional property to an existing document
  • Adding non-unique indices for properties added in the update

Data contracts are updated on the platform by submitting the modified data contract
object
in a data contract update state transition consisting of:

Field Type Size Description
$version unsigned integer 32 bits The platform protocol version (currently 1)
type unsigned integer 8 bits State transition type (4 for data contract update)
dataContract data contract object Varies Object containing the updated data contract details
Note: the data contract's version property must be incremented with each update
signaturePublicKeyId unsigned integer 32 bits The id of the identity public key that signed the state transition (=> 0)
signature array of bytes 65 bytes Signature of state transition data

See the data contract update implementation in rs-dpp for more details.

Data Contract State Transition Signing

Data contract state transitions must be signed by a private key associated with the contract owner's identity. See the state transition signing page for full signing details.

:maxdepth: 2
:titlesonly:
:hidden:

data-contract-document
data-contract-token