```{eval-rst} .. _explanations-data-contract: ``` # Data Contract ## Overview As described briefly in the [Dash Platform Protocol explanation](../explanations/platform-protocol.md#data-contract), Dash Platform uses data contracts to define the schema (structure) of data it stores. Therefore, an application must first register a data contract before using the platform to store its data. Then, when the application attempts to store or change data, the request will only succeed if the new data matches the data contract's schema. The first two data contracts are the [DashPay wallet](https://www.dash.org/dashpay/) and [Dash Platform Name Service (DPNS)](../explanations/dpns.md). The concept of the social, username-based DashPay wallet served as the catalyst for development of the platform, with DPNS providing the mechanism to support usernames. ```{eval-rst} .. raw:: html
``` ## Details ### Ownership Data contracts are owned by the [identity](../explanations/identity.md) that registers them. Each identity may be used to create multiple data contracts and data contract updates can only be made by the identity that owns the contract. ### Structure Each data contract must define several fields. When using the [reference implementation](https://github.com/dashpay/platform/tree/master/packages/rs-dpp) of the Dash Platform Protocol, some of these fields are automatically set to a default value and do not have to be explicitly provided. These include: * The platform protocol schema it uses * A contract ID (generated from a hash of the data contract's owner identity and the identity nonce) * One or more [documents](../explanations/platform-protocol-document.md) * Optional [tokens](../explanations/tokens.md) with their own configuration, distribution, and authorization rules * Optional groups (sets of identities with assigned power) used to jointly authorize privileged contract actions such as token minting, burning, or configuration changes * Optional keywords used to surface the contract through discovery features, plus an optional contract description Each document type's schema also determines how applications can query and relate its documents. Contracts declare indexes for efficient queries and can opt into capabilities such as aggregation, ranking, time-range selection, and relationships to other Platform objects. They can also define index-only document types for compact, immutable relationship data such as likes, follows, or memberships. Because these choices affect how data is stored and queried, they should be planned during contract design. See [Query Capabilities](./query.md), [Document Indices](../reference/data-contracts.md#document-indices), and [Platform-specific property keywords](../reference/data-contracts.md#platform-specific-property-keywords). For a practical example, see the [DashPay contract](#example-contract). ### Contract Groups From protocol version 14, contract groups organize contracts, document types, and tokens into an identity-owned collection. They can scope [authentication keys](../explanations/identity.md#keys) to those members. These are separate from [token groups](../explanations/tokens.md#groups), whose member identities jointly authorize token actions. When registering a contract, the creation transition can also register a contract group, optionally with admins, and declare memberships for the new contract, its document types, or its tokens in groups the creating identity owns or administers. ### Registration Once a [Dash Platform Protocol](../explanations/platform-protocol.md) compliant data contract has been defined, it may be registered on the platform. Registration is completed by submitting a state transition containing the data contract to [DAPI](../explanations/dapi.md). The drawing below illustrates the steps an application developer follows to complete registration. ```{eval-rst} .. figure:: ../../img/data-contract.svg :class: no-scaled-link :align: center :width: 80% :alt: Data Contract Registration Data Contract Registration ``` ### Updates Existing data contracts can be updated by their owner in backwards-compatible ways, unless they were registered as read-only. Read-only status is permanent and can only be set at registration. See [Contract Configuration](../reference/data-contracts.md#contract-configuration) for details. Updates are applied by submitting a data contract update state transition and are validated to preserve compatibility with previously stored documents. Permitted changes include: * Adding new document types, including their indices * Adding new properties to existing document types. New properties are normally optional; from protocol version 14 a new property may be marked as required starting with the contract version that introduces it, without invalidating documents created under earlier versions * Adding new tokens to the contract * Adding new groups to the contract * Updating contract keywords and description Restricted changes include modifications that would break existing stored documents - for example, removing or renaming existing properties, changing their types, making an existing property required, or altering the index definitions of an existing document type. Whether a document type records the history of its transfers, sales, and price changes is also fixed when the document type is created and cannot be turned on or off by a later contract update. One narrow exception exists: a document type that keeps revision history cannot allow deletion, and an existing history-keeping type that was registered with deletion allowed may be updated to forbid it. A contract update cannot remove or modify an existing token or group. Changing an existing token's configuration is done with a [token configuration update transition](../explanations/tokens.md#configuration-updates), governed by that token's own change control rules, and existing groups are immutable once the contract is registered. Optional contract revision history storage allows contracts to retain a record of their revisions that can be retrieved and verified. Identity key access rules also allow an encryption or decryption key to be bound to a specific contract or document type for more granular key management. From protocol version 14, non-master authentication keys can also be bound to a contract, document type, or contract group, restricting them to batch transitions within those bounds. See [Identity keys](../explanations/identity.md#keys). :::{note} For more detailed information, see the [Platform Protocol Reference - Data Contract](../protocol-ref/data-contract.md) page. ::: ## Example Contract The [DashPay contract](https://github.com/dashpay/platform/blob/master/packages/dashpay-contract/schema/v2/dashpay.schema.json) is included below for reference. It defines a `contactRequest` document, a `profile` document, and a `contactInfo` document. Each of these documents then defines the properties and indices they require: :::{dropdown} DashPay contract ```json { "profile": { "type": "object", "indices": [ { "name": "ownerId", "properties": [ { "$ownerId": "asc" } ], "unique": true }, { "name": "ownerIdAndUpdatedAt", "properties": [ { "$ownerId": "asc" }, { "$updatedAt": "asc" } ] } ], "properties": { "avatarUrl": { "type": "string", "format": "uri", "minLength": 1, "maxLength": 2048, "position": 0 }, "avatarHash": { "type": "array", "byteArray": true, "minItems": 32, "maxItems": 32, "description": "SHA256 hash of the bytes of the image specified by avatarUrl", "position": 1 }, "avatarFingerprint": { "type": "array", "byteArray": true, "minItems": 8, "maxItems": 8, "description": "dHash the image specified by avatarUrl", "position": 2 }, "publicMessage": { "type": "string", "minLength": 1, "maxLength": 140, "position": 3 }, "displayName": { "type": "string", "minLength": 1, "maxLength": 25, "position": 4 }, "corePaymentAddress": { "type": "array", "byteArray": true, "minItems": 21, "maxItems": 21, "description": "Core chain address in storage form (type byte 0x00 P2PKH / 0x01 P2SH followed by the 20-byte HASH160, i.e. RIPEMD160 of SHA256, of the public key or redeem script) for public payments. The type byte is consensus-enforced by a data trigger; clients render the address as Base58Check for the network they are on. Payments to it are publicly linkable to this profile.", "position": 5 }, "platformPaymentAddress": { "type": "array", "byteArray": true, "minItems": 21, "maxItems": 21, "description": "Platform address in storage form (type byte 0x00 P2PKH / 0x01 P2SH followed by the 20-byte HASH160, i.e. RIPEMD160 of SHA256, of the public key or redeem script) for public payments. The type byte is consensus-enforced by a data trigger.", "position": 6 } }, "minProperties": 1, "dependentRequired": { "avatarUrl": ["avatarHash", "avatarFingerprint"], "avatarHash": ["avatarUrl", "avatarFingerprint"], "avatarFingerprint": ["avatarUrl", "avatarHash"] }, "required": [ "$createdAt", "$updatedAt" ], "additionalProperties": false }, "contactInfo": { "type": "object", "indices": [ { "name": "ownerIdAndKeys", "properties": [ { "$ownerId": "asc" }, { "rootEncryptionKeyIndex": "asc" }, { "derivationEncryptionKeyIndex": "asc" } ], "unique": true }, { "name": "ownerIdAndUpdatedAt", "properties": [ { "$ownerId": "asc" }, { "$updatedAt": "asc" } ] } ], "properties": { "encToUserId": { "type": "array", "byteArray": true, "minItems": 32, "maxItems": 32, "position": 0 }, "rootEncryptionKeyIndex": { "type": "integer", "minimum": 0, "position": 1 }, "derivationEncryptionKeyIndex": { "type": "integer", "minimum": 0, "position": 2 }, "privateData": { "type": "array", "byteArray": true, "minItems": 48, "maxItems": 2048, "position": 3, "description": "This is the encrypted values of aliasName + note + displayHidden encoded as an array in cbor" } }, "required": [ "$createdAt", "$updatedAt", "encToUserId", "privateData", "rootEncryptionKeyIndex", "derivationEncryptionKeyIndex" ], "additionalProperties": false }, "contactRequest": { "documentsMutable": false, "canBeDeleted": false, "requiresIdentityEncryptionBoundedKey": 2, "requiresIdentityDecryptionBoundedKey": 2, "type": "object", "indices": [ { "name": "ownerIdUserIdAndAccountRef", "properties": [ { "$ownerId": "asc" }, { "toUserId": "asc" }, { "accountReference": "asc" } ], "unique": true }, { "name": "ownerIdUserId", "properties": [ { "$ownerId": "asc" }, { "toUserId": "asc" } ] }, { "name": "userIdCreatedAt", "properties": [ { "toUserId": "asc" }, { "$createdAt": "asc" } ] }, { "name": "ownerIdCreatedAt", "properties": [ { "$ownerId": "asc" }, { "$createdAt": "asc" } ] } ], "properties": { "toUserId": { "type": "array", "byteArray": true, "minItems": 32, "maxItems": 32, "position": 0, "contentMediaType": "application/x.dash.dpp.identifier" }, "encryptedPublicKey": { "type": "array", "byteArray": true, "minItems": 96, "maxItems": 96, "position": 1 }, "senderKeyIndex": { "type": "integer", "minimum": 0, "position": 2 }, "recipientKeyIndex": { "type": "integer", "minimum": 0, "position": 3 }, "accountReference": { "type": "integer", "minimum": 0, "position": 4 }, "encryptedAccountLabel": { "type": "array", "byteArray": true, "minItems": 48, "maxItems": 80, "position": 5 }, "autoAcceptProof": { "type": "array", "byteArray": true, "minItems": 38, "maxItems": 102, "position": 6 } }, "required": [ "$createdAt", "$createdAtCoreBlockHeight", "toUserId", "encryptedPublicKey", "senderKeyIndex", "recipientKeyIndex", "accountReference" ], "additionalProperties": false } } ``` This is a visualization of the JSON data contract as UML class diagram for better understanding of the structure: ```{eval-rst} .. figure:: ./img/dashpay-uml.png :class: no-scaled-link :align: center :width: 90% :alt: Dashpay Contract Diagram Dashpay Contract Diagram ``` View [a full-size copy of this diagram](./img/dashpay-uml.png).