Data Contracts#
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 submitted contract-related data. This minimal example shows a data contract for a simple note-taking application, where each document represents a note with a single text message.
{
"note": {
"properties": {
"message": {
"type": "string",
"position": 0,
"description": "Stores a note message"
}
},
"additionalProperties": false
}
}
The following sections provide details that developers need to configure and construct valid contracts. All data contracts must define at least one document or token, each conforming to the general data contract constraints. Additionally, several contract-level configuration parameters can be set to modify the mutability, retention, and security behavior of the contract and its documents.
Contract Configuration#
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 |
|---|---|---|
|
|
Determines if the contract can be deleted |
|
|
Determines if the contract is read-only. Read-only contracts cannot be updated. |
|
|
Enables or disables the storing of contract update history |
|
|
Use sized integer types for |
|
None |
(Optional) Declares the contract’s moderation lists and moderators. Requires protocol version 14. See Contract Moderation. |
Document default option |
Default |
Description |
|---|---|---|
|
|
Sets the default behavior for whether documents keep history within the contract |
|
|
Sets the default mutability of documents within the contract |
|
|
Sets the default behavior for whether documents within the contract can be deleted |
Data contracts may also define the following top-level fields:
Contract field |
Type |
Description |
|---|---|---|
|
object |
(Optional) Token definitions keyed by token contract position. Each entry configures base supply, maximum supply, minting and burning rules, and change control. See Contract Tokens. |
|
object |
(Optional) Groups that allow for specific multiparty actions on the contract. See Data Contract groups. |
|
array of strings |
(Optional) Keywords associated with the contract to improve searchability via the |
|
string |
(Optional) Brief human-readable description of the contract (3-100 characters). Also added to the |
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 |
|---|---|
|
Indicates the contract requires a contract-specific identity encryption key. Key options: |
|
Indicates the contract requires a contract-specific identity decryption key. Key options: |
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,
}
Documents#
The documents object defines each type of document required by the data contract. At a minimum, a document must consist of 1 or more properties. Documents may also define indices and a list of required properties. The additionalProperties properties keyword must be included as described in the constraints section.
The following example shows a minimal documents object defining a single document (note) with one property (message).
{
"note": {
"properties": {
"message": {
"type": "string",
"position": 0
}
},
"additionalProperties": false
}
}
Document Configuration#
Documents support the following 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.
Document option |
Type |
Description |
|---|---|---|
|
boolean |
If true, documents keep a history of all changes. Default: false. |
|
boolean |
If true, documents are mutable. Default: true. |
|
boolean or string |
Whether a document’s owner can delete it: |
|
integer |
Transferable without a marketplace sell: |
|
integer |
Built-in marketplace system: |
|
integer |
Restriction of document creation: |
|
boolean |
If true, transfers of these documents are recorded in the document history system contract. Default: false. |
|
boolean |
If true, purchases of these documents are recorded in the document history system contract. Default: false. |
|
boolean |
If true, price updates on these documents are recorded in the document history system contract. Default: false. |
|
boolean |
If true, documents are never written to primary storage - the index entries are the rows. Requires protocol version 14; see indexOnly document types. Default: false. |
Security option |
Type |
Description |
|---|---|---|
integer |
Key requirements for identity encryption: |
|
integer |
Key requirements for identity decryption: |
|
|
integer |
Public key security level: |
Document types may also define a tokenCost object requiring token payment per operation. See Token Costs in the protocol reference for the full schema.
Added in version 4.0.0: Document types can opt into aggregate queries with the flags documentsCountable, documentsSummable, documentsAverageable, and their range* variants, which enable COUNT/SUM/AVG support. See Aggregate Query Flags in the protocol reference for the full schema.
List of all usable document properties
This list of properties is defined in the Rust DPP implementation and the document meta-schema.
Property Name |
Type |
Description |
|---|---|---|
|
string |
Specifies the type of the document, constrained to “object”. |
|
string |
The schema URL reference for the document. |
|
object |
References the |
array |
Defines indices for the document with properties like |
|
|
integer |
Public key security level: |
|
boolean |
If true, documents keep a history of all changes. Default: false. |
|
boolean |
If true, documents are mutable. Default: true. |
|
boolean or string |
Whether a document’s owner can delete it: |
|
integer |
Transferable without a marketplace sell: |
|
integer |
Built-in marketplace system: |
|
integer |
Restriction of document creation: |
|
boolean |
If true, transfers of these documents are recorded in the document history system contract. Default: false. |
|
boolean |
If true, purchases of these documents are recorded in the document history system contract. Default: false. |
|
boolean |
If true, price updates on these documents are recorded in the document history system contract. Default: false. |
integer |
Key requirements for identity encryption: |
|
integer |
Key requirements for identity decryption: |
|
object |
Defines the properties of the document. |
|
array |
An array of strings specifying transient properties that are validated by Platform but not stored. |
|
|
object |
Defines token costs for document operations (create, replace, update_price, delete, transfer, purchase). See Token Costs. |
boolean |
Doctype-wide count support. See Aggregate Query Flags. |
|
boolean |
Per-index range counts. See Aggregate Query Flags. |
|
string |
Doctype-wide sums of the named integer property. See Aggregate Query Flags. |
|
boolean |
Per-index range sums. See Aggregate Query Flags. |
|
string |
Doctype-wide averages of the named integer property. See Aggregate Query Flags. |
|
boolean |
Per-index range averages. See Aggregate Query Flags. |
|
boolean |
Specifies whether additional properties are allowed. Must be set to false, meaning no additional properties are allowed beyond those defined. |
Example
The following example (from the DPNS contract’s domain document) demonstrates the use of several configuration options:
{
"domain": {
"documentsMutable": false,
"canBeDeleted": true,
"transferable": 1,
"tradeMode": 1,
"keepsTransferHistory": true,
"keepsPurchaseHistory": true,
"keepsPricingHistory": true,
"..."
}
}
indexOnly document types#
Added in version 5.0.0: Requires protocol version 14.
An indexOnly document type is never written to primary storage. Its index entries are the rows: each terminates in a value keyed by the index’s terminal rather than a reference keyed by a document ID. Only what the indices hold exists and is recoverable, which makes the type cheaper to store at the cost of being queryable only along its declared indices.
Declaring indexOnly: true carries co-requirements:
Every property must be required and appear in at least one index that sets neither
skipIfAbsentnoroutlivesDelete. The exception is a property in askipIfAbsentskip set, which may be optional.Except for a
summableOffCountIndexindex, every index must include$ownerId, either as one of its properties or in itsterminal.documentsMutablemust be false.Transfers, trading, history, and transient properties are not allowed.
Document-type-level aggregate keywords are not allowed; use the index-level flags instead.
Indices cannot be
unique, contested, ornullSearchable: false, and only the$ownerIdand$createdAtsystem properties may be indexed.At least one index must be free of
$createdAtand notskipIfAbsent- the index the executed-transition proof relies on.
indexOnly types are the inner half of a chained query.
Document Properties#
The properties object defines each field that a document will use. Each field consists of an object that, at a minimum, must define its data type (string, number, integer, boolean, array, object) and a position.
Fields may also apply a variety of optional JSON Schema constraints related to the format, range, length, etc. of the data. A full explanation of JSON Schema capabilities is beyond the scope of this document. For more information regarding its data types and the constraints that can be applied, please refer to the JSON Schema reference documentation.
Platform-specific property keywords#
Added in version 5.0.0: Requires protocol version 14.
Beyond the standard JSON Schema constraints, Dash Platform defines its own property keywords.
refersTo declares that a property - typically an identifier - points at another Platform object, so consensus can enforce that the target exists and stays consistent. Its type names what is referenced - identity, contract, token, identityPublicKey, or a document (permanentDocument, moderatedDocument, or deletableDocument, depending on how the referenced document type’s documents can be removed). Further members such as findBy, where, inList, consume, and keyRequirements refine the target, and anyOf / allOf combine declarations. See Property References for the full grammar. The earlier lookup and propertyAgreement keywords and the listElement type are rejected.
A permanentDocument refersTo is what makes a property usable as the join property of a chained query or the by-ID binding of a composite query.
requiredSince is an integer naming the contract version from which the property is required, letting a later contract version add a required property without invalidating documents written under earlier versions. On a contract update, a newly required property must carry a requiredSince equal to the new contract version; an existing property cannot become required.
Other property keywords - maxBytes, generatedFrom, distinctFrom, and encryptedFor - are described in Additional Property Keywords.
Document types can also declare Platform-specific keywords at the type level: ownerRefersTo and creatorRefersTo (Property References), propertyConstraints, immutable, retractedWhen, and entryPayload (Document Configuration), deleteConstraints and ttl (Document Removal), moderatorAbilities (Moderator Abilities), and actionFees (Action Fees).
Property Constraints#
There are a variety of constraints currently defined for performance and security reasons.
Assigning property position#
Each property in a level must be assigned a unique position value, with ordering starting at zero and incrementing with each property. When using nested objects, position counting resets to zero for each level. This structure supports backward compatibility in data contracts by ensuring consistent ordering for serialization and deserialization processes.
Special requirements for object properties#
The object type cannot be an empty object but must have one or more defined properties. For example, the body property shown below is an object containing a single string property (objectProperty):
const contractDocuments = {
message: {
type: "object",
properties: {
body: {
type: "object",
position: 0,
properties: {
objectProperty: {
type: "string",
"position": 0
},
},
additionalProperties: false,
},
header: {
type: "string",
"position": 1
}
},
additionalProperties: false
}
};
Required Properties#
Each document may have some fields that are required for the document to be valid and other optional fields. Required fields are defined via the required array, which contains a list of the field names that must be present in the document. The required object should only be included for documents with at least one required property.
Example
The following example (excerpt from the DPNS contract’s domain document) demonstrates a document that has defined required fields:
"required": [
"$createdAt",
"$updatedAt",
"$transferredAt",
"label",
"normalizedLabel",
"normalizedParentDomainName",
"preorderSalt",
"records",
"subdomainRules"
],
Transient Properties#
Each document may have transient fields that require validation but do not need to be stored by the system once validated. Transient fields are defined in the transient array. The transient object should only be included for documents with at least one transient property.
Example
The following example (from the DPNS contract’s domain document) demonstrates a document that has 1 transient field:
"transient": [
"preorderSalt"
]
Document Indices#
Document indices may be defined if indexing on document fields is required. The indices object should only be included for documents with at least one index.
The indices array consists of one or more objects that each contain:
A unique
namefor the indexA
propertiesarray composed of a<field name: sort order>object for each document field that is part of the indexCompound Indices
When defining an index with multiple properties, the ordering of properties is important. Refer to the mongoDB documentation for details. Dash uses GroveDB, which works similarly but requires listing all the index’s fields in query order by statements.
An optional
uniqueelement that determines if duplicate values are allowed for the documentAn optional
nullSearchableelement that indicates whether the index allows searching for NULL values. If nullSearchable is false (default: true) and all properties of the index are null then no reference is added.An optional
contestedelement that configures a masternode-voting contest over documents whose field values match a defined pattern (see Contested indices). It is an object composed offieldMatches(field andregexPatternconditions) and aresolutionmethod.Optional aggregate flags that let the index answer
COUNT/SUM/AVGqueries without walking every document (see Aggregate index flags).Optional ranked, time-range,
skipIfAbsent, andindexOnlykeywords added at protocol version 14 (see Ranked index flags and indexOnly index keywords).
"indices": [
{
"name": "<index name a>",
"properties": [
{ "<field name a>": "asc" },
{ "<field name b>": "asc" }
],
"unique": true|false,
"nullSearchable": true|false,
"countable": "countable"|"countableAllowingOffset",
"rangeCountable": true|false,
"summable": "<integer field name>",
"rangeSummable": true|false,
"averageable": "<integer field name>",
"rangeAverageable": true|false,
"rankedCountable": true|false|{ "at": "<property>" },
"rankedSummable": true|false,
"rankedAverageable": true|false,
"timeRange": { "on": "<$createdAt|$updatedAt|$transferredAt>", "range": <seconds>, "step": <seconds>, "phase": <seconds>, "ttl": <seconds> },
"integerRange": { "on": "<integer property>", "range": <number>, "step": <number>, "phase": <number> },
"terminal": "$ownerId"|"<property>"|["<property>", ...],
"preallocated": true|false,
"skipIfAbsent": true|false|["<property>", ...],
"outlivesDelete": true|false,
"summableOffCountIndex": "<index name>",
"contested": {
"fieldMatches": [
{
"field": "<field name a>",
"regexPattern": "<regex>"
}
],
"resolution": 0
}
},
{
"name": "<index name b>",
"properties": [
{ "<field name c>": "asc" },
],
}
]
Contested indices#
Contested unique indices provide a way for multiple identities to compete for ownership when a new document field matches a predefined pattern. This system enables fair distribution of valuable documents through community-driven decision-making.
A two week contest begins when a match occurs. For the first week, additional contenders can join by paying the contested document vote resolution fund fee (0.1 Dash from protocol version 14; 0.2 Dash in earlier versions). Starting in protocol version 14, the fee doubles for every 50 contenders beyond the first 250, and a contest accepts at most 1,000 contenders. During this period, masternodes and evonodes vote on the outcome. The contest can result in the awarding of the document to the winner, a locked vote where no document is awarded, or potentially a restart of the contest if specific conditions are met.
The table below describes the properties used to configure a contested index:
Property Name |
Type |
Description |
|---|---|---|
fieldMatches |
array |
Array containing conditions to check |
fieldMatches.field |
string |
Name of the field to check for matches |
fieldMatches.regexPattern |
string |
Regex used to check for matches |
resolution |
integer |
Method to resolve the contest: |
Example
This example (from the DPNS contract’s domain document) demonstrates the use of a contested index:
"contested": {
"fieldMatches": [
{
"field": "normalizedLabel",
"regexPattern": "^[a-zA-Z01-]{3,19}$"
}
],
"resolution": 0,
"description": "If the normalized label part of this index is less than 20 characters (all alphabet a-z, A-Z, 0, 1, and -) then a masternode vote contest takes place to give out the name"
}
Aggregate index flags#
An index can carry aggregate flags so the node answers COUNT, SUM, and AVG queries from the index itself rather than by walking every matching document. See Aggregate Queries for the query side.
Keyword |
Type |
Description |
|---|---|---|
countable |
string or boolean |
Whether and how the index supports count fast paths - |
rangeCountable |
boolean |
Makes range-count queries on the indexed property O(log n). Requires |
summable |
string |
Names an integer document property whose values are aggregated into a sum at the index. The property must exist on the document type, be listed in |
rangeSummable |
boolean |
Makes range-sum queries on the indexed property O(log n). Requires |
averageable |
string |
Shorthand for |
rangeAverageable |
boolean |
Shorthand for |
Ranked index flags#
Added in version 5.0.0: Requires protocol version 14.
Ranking axes let an index answer “top / bottom K groups by aggregate” queries with proofs. Each axis adds its own ordered secondary tree keyed by the group’s aggregate, and each is opted into separately - none implies another. See Ranked aggregate queries.
Keyword |
Type |
Description |
|---|---|---|
rankedCountable |
boolean or object |
Adds the Count ranking axis. Requires |
rankedSummable |
boolean |
Adds the Sum ranking axis. Requires |
rankedAverageable |
boolean |
Adds the Avg ranking axis. Requires |
An index may also declare a timeRange transform, which buckets the first index property’s timestamp into fixed-length, regularly spaced (possibly overlapping) windows:
Property |
Type |
Required |
Description |
|---|---|---|---|
on |
string |
Yes |
The timestamp property to bucket. Must be the index’s first property and name one of |
range |
integer |
Yes |
Window length in seconds. Must be an exact multiple of |
step |
integer |
Yes |
Spacing between consecutive window starts, in seconds. When |
phase |
integer |
No |
Offset of the grid’s origin, in seconds. Must be less than |
ttl |
integer |
No |
Entry lifetime in seconds, measured from the start of its window. Must be at least |
The stored key is each window’s start as a millisecond timestamp. Several indices may bucket the same timestamp with different grids; each grid gets its own subtree. At most 24 windows may overlap a single timestamp at protocol version 14. A time-range index may be unique only when range equals step and on is $createdAt, and it cannot be contested. A single-property time-range index cannot be ranked, because its only level is the bucketed one, and a time-range index cannot declare preallocated. Query these windows with the inTimeRange operator.
Bytes written under a ttl index are charged as processing at the TTL ephemeral rate instead of as storage, and are not refunded when removed.
Indices may also declare integerRange, which buckets the first index property’s integer value into fixed-length windows, and, on indexOnly types, summableOffCountIndex and (for a timeRange index with a ttl) outlivesDelete. See Optional Index Fields.
An index may declare skipIfAbsent, so that a document omitting a property of the index’s skip set writes no entry and the index holds only documents carrying every skip property. true makes the skip set every optional property of the index; an array names the skip set explicitly. A skip property may sit at any position in the index and must not be listed in required. An absent property is distinct from an empty value: absence skips the index, while any present value indexes normally. On a stored type, a skip index cannot be contested or set nullSearchable: false; indexOnly types add the rules below.
indexOnly index keywords#
Added in version 5.0.0: Requires protocol version 14.
terminal and preallocated apply only to indexOnly document types, which also add rules to skipIfAbsent. See Index-Only Options for details.
Keyword |
Type |
Description |
|---|---|---|
skipIfAbsent |
boolean or array |
The skip set must hold every optional property of the index, and every optional property needs a skip index whose skip set is that property alone and that has no |
terminal |
string or array |
The property, or ordered list of up to 10 properties, whose values key each index entry - the analog of a document ID. Each component is |
preallocated |
boolean |
When true, creating a referenced document also creates this index’s trees for entries referencing it, paid by that document’s creator, so every entry insert costs the same as the first. Every index property must be either the referring property of a same-contract |
Index Constraints#
For performance and security reasons, indices have the following constraints. These constraints are subject to change over time.
Description |
Value |
|---|---|
Minimum / maximum length of index |
|
Maximum number of indices |
|
Maximum number of unique indices |
|
Maximum number of properties in a single index |
|
Maximum length of indexed string property |
|
Maximum length of indexed byte array property |
|
Maximum number of elements in an indexed typed array |
16, or 4 when the index also has a |
Usage of |
N/A |
Example
The following example (excerpt from the DPNS contract’s preorder document) creates an index on saltedDomainHash that also enforces uniqueness across all documents of that type:
"indices": [
{
"name": "saltedHash",
"properties": [
{ "saltedDomainHash": "asc" }
],
"unique": true
}
],
Full Document Syntax#
This example syntax shows the structure of a document object including the most commonly used optional properties.
It is not exhaustive. The aggregate index flags, and the keywords added at protocol version 14 - indexOnly, the ranked and time-range index flags, the indexOnly index keywords, and the refersTo and requiredSince property keywords - are documented in their own sections above. For the authoritative set, see the document meta-schema.
Document schema
{
"<document name a>": {
"documentsKeepHistory": true|false,
"documentsMutable": true|false,
"canBeDeleted": true|false|"onlyWhenConsumed",
"transferable": 0|1,
"tradeMode": 0|1,
"creationRestrictionMode": 0|1|2,
"requiresIdentityEncryptionBoundedKey": 0|1|2,
"requiresIdentityDecryptionBoundedKey": 0|1|2,
"signatureSecurityLevelRequirement": 1|2|3,
"type": "object",
"properties": {
"<property name b>": {
"type": "<property data type>",
"position": "<number>"
},
"<property name c>": {
"type": "<property data type>",
"position": "<number>"
},
},
"indices": [
{
"name": "<index name>",
"properties": [
{ "<property name c>": "asc" },
],
"unique": true|false,
"nullSearchable": true|false,
"contested": {
"fieldMatches": [
{
"field": "<property name c>",
"regexPattern": "<regex>"
}
],
"resolution": 0
}
},
],
"required": [
"<field name c>"
],
"transient": [
"<field name b>"
]
"additionalProperties": false
},
"<document name x>": {
"type": "object",
"properties": {
"<property name y>": {
"type": "<property data type>",
"position": "<number>"
},
"<property name z>": {
"type": "<property data type>",
"position": "<number>"
},
},
"additionalProperties": false
},
}
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. Unless otherwise noted, these constraints are defined in the platform’s JSON Schema rules (e.g. rs-dpp document meta schema).
Keyword#
Keyword |
Constraint |
|---|---|
|
Restricted - cannot be used (defined in DPP logic) |
|
Restricted - cannot be used (defined in DPP logic) |
|
|
|
|
|
Internal references only - the value must begin with |
|
Disabled for data contracts |
|
Not supported. Use |
|
Not supported. Schema-based dependencies are not available in document schemas; use |
|
Must be a byte array ( |
|
Not supported. |
|
Restricted - cannot be used for data contracts |
|
Accept only RE2 compatible regular expressions (defined in DPP logic) |
Data Size#
Note: These constraints are defined in the Dash Platform Protocol logic (not in JSON Schema).
A state transition is limited to a maximum size of 20 KiB (max_state_transition_size). Oversized transitions are rejected.
An individual document field value is limited to 5 KiB (max_field_value_size).
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