```{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. Document types also decide who may remove their documents and whether documents expire; see [Document Delete](../explanations/platform-protocol-document.md#document-delete). 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 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. ### Contract Moderation A data contract can declare moderation when it is registered. Moderation gives an application a consensus-enforced way to keep abusive identities out and to take down content, rather than relying on client-side blocklists that bind only the clients that keep them. A moderated contract can keep any of three lists of identities: * **Banlist** - a banned identity cannot submit document transitions for the contract until it is unbanned. * **Suspension list** - a suspended identity is barred in the same way until a block time set by the moderator. * **Warning list** - a warning records a reason without barring anything, giving the identity and its moderators a visible step short of a suspension. A banned or suspended identity can still delete its own documents under the document type's normal deletion rules. If the document type defines a retracted form (`retractedWhen`), the identity can also replace a document with that form. Token transitions are not affected. Transferring a document to a barred identity, or buying one from a barred seller, is refused. A contract's moderators are its owner alone, its owner together with up to 16 identities named by the contract, or a team elected by masternodes. To get elected, candidate teams submit charters to the moderation charters system data contract, and masternodes vote on which team takes the contract's seat. Until a team is seated, the contract's declaration determines who moderates in the interim, if anyone. A contract can instead keep its moderated document types unusable until a team is seated. Moderators act individually. They cannot be put on the contract's lists, and other moderators cannot delete their documents. On an elected contract, that protection covers the seated team, and covers the owner only if the contract declares it. Which lists a contract keeps, and whether its moderators are elected, are fixed at registration. A contract update can change the named moderators of a contract that is not elected, including naming some for the first time. Moderation actions are submitted with a dedicated contract user moderation state transition rather than in a batch. Each document type also controls what moderators may do with its documents through its `moderatorAbilities`: * **Deletion** - moderators may delete documents of the type, optionally only within a window after each document's last modification. On a contract with an elected team, a type can also let the seated team delete documents past that window by jointly approving the deletion. * **Removal records** - by default, a moderator's deletion leaves a permanent removal record showing whose document it was, who removed it, when, and why, plus a hash of the document and any fields the type chooses to keep public. The author is not refunded the document's storage unless the type says otherwise. Within one week, a moderator who still has a copy of the deleted document can restore it exactly as it was, unless the seated team deleted it together. * **Moderator-only fields** - a type can list properties that only moderators write, such as the status of a report. Documents record the last such write in the `$moderatedAt` and `$moderatedBy` fields. These abilities are fixed when the document type is defined. Any document type, moderated or not, can also charge declared action fees, on top of normal processing fees, for actions on its documents. Each fee has an owner part that the contract owner claims and, on a moderated contract, a moderators part that the moderation team shares (see [`getContractFeePots`](../reference/dapi-endpoints-platform-endpoints.md#getcontractfeepots)). Moderation status, list entries, removal records, and the actions of an elected team can be queried and proven; see the [Contract Moderation endpoints](../reference/dapi-endpoints.md#contract-moderation). ### 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. A new property can be optional, or it can be required from the contract version that introduces it onward. Requiring it does not invalidate documents created under earlier contract 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. Apart from that exception, the rules for how a document type's documents can be removed are also fixed when the type is defined. This covers deletion rules, time to live, and moderator abilities, along with the type's `refersTo` references. 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. 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 }, "shieldedAddress": { "type": "array", "byteArray": true, "minItems": 43, "maxItems": 43, "description": "Raw Orchard receiving address: 11-byte diversifier followed by 32-byte diversified transmission key. Clients validate before payment; wallets should use a dedicated tip account.", "position": 7 } }, "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).