```{eval-rst} .. _protocol-ref-document: ``` # Document ## Document Overview Once a [data contract](./data-contract.md) has been created, data can be stored by submitting documents that comply with the document structure specified in the contract. Each document consists of one or more fields and the indices necessary to support querying. Documents are [created](#document-create-transition), [replaced](#document-replace-transition), [deleted](#document-delete-transition), [transferred](#document-transfer-transition), [purchased](#document-purchase-transition), or have their [price updated](#document-update-price-transition) by submitting them to the platform in a [batch state transition](./state-transition.md#batch). On a [moderated contract](./data-contract.md#contract-moderation), the contract's moderators act on documents with the [contract user moderation](./data-contract.md#contract-user-moderation) state transition rather than with document transitions. ## Document State Transition Details All document transitions include the [document base transition fields](#document-base-transition). Some document transitions (e.g., [document create](#document-create-transition)) require additional fields to provide their functionality. ### Document Base Transition The following fields are included in all document transitions. Note that `$action` is present in the JSON wire format as a type discriminator (see [Document Transition Action](#document-transition-action)) but is not a stored field in the base transition structure — it is derived from the transition variant type. | Field | Type | Size | Description| | ----- | ---- | ---- | ---------- | | [$id](#document-id) | array | 32 bytes | The [document ID](#document-id) | | $identityContractNonce | unsigned integer | 64 bits | Identity contract nonce | | $type | string | 1-64 characters | Name of a document type found in the data contract associated with the `dataContractId`| | $dataContractId | array | 32 bytes | Data contract ID [generated](../protocol-ref/data-contract.md#data-contract-id) from the data contract's `ownerId` and `identity nonce` | | [$tokenPaymentInfo](#token-payment-info) | object | Varies | (Optional, V1+) Token-based fee payment information for this transition | | [$actionFeeAgreement](#action-fee-agreement) | object | Varies | (Optional, V2+) **Added in 5.0.0.** The [action fees](./data-contract-document.md#action-fees) the transition agrees to pay. Required when the document type charges a fee for the action. | Each document transition must comply with the document base transition defined in rs-dpp ([v1](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_base_transition/v1/mod.rs#L40-L58), [v2](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_base_transition/v2/mod.rs#L30-L55)). Starting with protocol version 14, the v2 base transition is the default; it is not accepted under earlier protocol versions. :::{versionchanged} 5.0.0 On a contract that declares [moderation](./data-contract.md#contract-moderation), document transitions from a banned or suspended identity are rejected. Delete and index-only delete transitions are still allowed, and so is a replace on a document type that declares [`retractedWhen`](./data-contract-document.md#document-configuration) when the document it writes meets that condition. A transfer to a barred identity or a purchase from a barred seller is rejected. Token transitions are not affected. ::: #### Document id The document `$id` is created by double sha256 hashing the document's `dataContractId`, `ownerId`, `type`, and `entropy`, and from protocol version 14 also a domain tag and the create transition's `$identityContractNonce`, as shown in [rs-dpp](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/document/generate_document_id.rs). :::{versionchanged} 5.0.0 Because an identity contract nonce is used at most once, a document ID can be produced at most once, so a deleted document cannot be recreated under the same ID. The ID is only known once the nonce of the create transition is chosen. A create transition whose ID was derived without the nonce is rejected. ```rust // From the Rust reference implementation (rs-dpp) // generate_document_id.rs (protocol version 14 and later) const DOCUMENT_ID_V1_DOMAIN_TAG: &[u8] = b"dash:document-id:v1"; pub fn generate_document_id_v1( contract_id: &Identifier, owner_id: &Identifier, document_type_name: &str, entropy: &[u8], identity_contract_nonce: IdentityNonce, ) -> Identifier { let mut buf: Vec = Vec::with_capacity( DOCUMENT_ID_V1_DOMAIN_TAG.len() + 64 + document_type_name.len() + entropy.len() + 8, ); buf.extend_from_slice(DOCUMENT_ID_V1_DOMAIN_TAG); buf.extend_from_slice(contract_id.as_slice()); buf.extend_from_slice(owner_id.as_slice()); buf.extend_from_slice(document_type_name.as_bytes()); buf.extend_from_slice(entropy); buf.extend_from_slice(&identity_contract_nonce.to_be_bytes()); Identifier::from(hash_double(&buf)) } ``` ::: Protocol version 13 and earlier use the original derivation: ```rust // From the Rust reference implementation (rs-dpp) // generate_document_id.rs pub fn generate_document_id_v0( contract_id: &Identifier, owner_id: &Identifier, document_type_name: &str, entropy: &[u8], ) -> Identifier { let mut buf: Vec = vec![]; buf.extend_from_slice(&contract_id.to_buffer()); buf.extend_from_slice(&owner_id.to_buffer()); buf.extend_from_slice(document_type_name.as_bytes()); buf.extend_from_slice(entropy); Identifier::from_bytes(&hash_double_to_vec(&buf)).unwrap() } ``` #### Token Payment Info When a document type requires token payment (configured via [`tokenCost`](./data-contract-document.md#token-costs) in the data contract), the `$tokenPaymentInfo` object specifies which token to use and the cost limits the client is willing to accept. The object is defined in [rs-dpp](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/tokens/token_payment_info/v0/mod.rs#L34-L55). | Field | Type | Size | Description | | - | - | - | - | | paymentTokenContractId | array | 32 bytes | (Optional) Identifier of the contract containing the payment token. Defaults to the current contract if omitted. | | tokenContractPosition | unsigned integer | 16 bits | Position of the token within the contract to use for payment | | minimumTokenCost | unsigned integer | 64 bits | (Optional) Minimum acceptable token cost. Not typically set by clients. | | maximumTokenCost | unsigned integer | 64 bits | (Optional) Maximum token cost the client is willing to pay. Recommended when the contract allows prices to be changed, to protect against unexpected cost increases. | | gasFeesPaidBy | integer | 8 bits | Who the document owner asks to pay the gas fees for the operation:
`0` - Document owner (default)
`1` - Contract owner
`2` - Prefer contract owner (falls back to the document owner if the contract owner's balance is insufficient) | | shieldedPayment | object | Varies | (V1 only) **Added in 5.0.0.** Spend bundle that pays the token cost from the token's [shielded pool](./shielded-pool.md) instead of the document owner's token balance. The document owner still pays the credit fees. See the [V1 implementation in rs-dpp](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/tokens/token_payment_info/v1/mod.rs#L142-L157). | :::{versionchanged} 5.0.0 Before protocol version 14, `gasFeesPaidBy` was carried but ignored and the signer always paid. From protocol version 14, the payer is resolved from the value the document type's [`tokenCost`](./data-contract-document.md#token-costs) offers and the value the transition requests ([`GasFeesPaidBy::resolve`](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/tokens/gas_fees_paid_by.rs#L51-L83)): - A request for the document owner (`0`) is always accepted, and the document owner pays. - A request to prefer the contract owner (`2`) is accepted by every offer. The contract owner pays only if the offer is `1` or `2` and its balance covers the fee; otherwise the document owner pays. - A request for the contract owner (`1`) is accepted only when the offer is `1`. If the contract owner's balance does not cover the fee, the batch is rejected. A request the offer does not allow is rejected, and so is a batch that names two different payers. A sponsoring contract owner pays the fee without the `userFeeIncrease`, which the signer pays. A transition that fails is always paid for by its signer. ::: A document type's `tokenCost` entry may set `optional: true`. A transition for that action may then omit `$tokenPaymentInfo`, pay no tokens, and pay the gas itself in credits. #### Action Fee Agreement :::{versionadded} 5.0.0 ::: When a document type charges an [action fee](./data-contract-document.md#action-fees) for the action, the transition's `$actionFeeAgreement` states the fee the signer saw declared, so a contract whose fees change cannot make an already signed transition pay them. The object is defined in [rs-dpp](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/data_contract/document_type/action_fees/agreement/v0/mod.rs#L22-L34). | Field | Type | Size | Description | | - | - | - | - | | $formatVersion | string | | Agreement format version (`"0"`) | | owner | unsigned integer | 64 bits | Owner part of the fee, in credits, exactly as the document type declares it (before any fee multiplier) | | moderators | unsigned integer | 64 bits | Moderators part of the fee, in credits, exactly as the document type declares it (before any fee multiplier). On an elected contract with a seated team, it may instead be the declared part reduced to the seated charter's `moderatorsShare` (rounded down). | | feeMultiplier | object | Varies | Required when the fee is priced with the fee multiplier, and omitted for fixed pricing:
`knownPermille` - Fee multiplier (permille) the signer priced the fee with
`increaseTolerancePercent` (16 bits) - How far, in percent, the multiplier of the executing epoch may exceed `knownPermille`. A lower multiplier is always accepted. | A transition for an action that charges a fee is rejected without an agreement, with an agreement to other amounts or pricing, when the epoch's fee multiplier exceeds the tolerance, or with a reduced moderators part that does not match the seated charter's share. An agreement on an action that charges no fee is ignored. #### Entropy Generation Dash Platform uses the following entropy generator found in [rs-dpp](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/util/entropy_generator.rs#L9-L14): ```rust // From the Rust reference implementation (rs-dpp) // entropy_generator.rs fn generate(&self) -> anyhow::Result<[u8; 32]> { let mut buffer = [0u8; 32]; getrandom::getrandom(&mut buffer) .map_err(|e| anyhow::anyhow!(format!("generating entropy failed: {}", e)))?; Ok(buffer) } ``` #### Document Transition Action Document transition actions indicate what operation platform should perform with the provided transition data. Documents provide CRUD functionality, ownership transfer, and NFT features as [defined in rs-dpp](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_transition_action_type.rs#L6-L15). The Action column is the enum index. In the JSON form, `$action` carries the camelCase name instead: `create`, `replace`, `delete`, `transfer`, `purchase`, `updatePrice`, or `indexOnlyDelete`. | Action | Name | Description | | :-: | - | - | | 0 | [Create](#document-create-transition) | Create a new document with the provided data | | 1 | [Replace](#document-replace-transition) | Replace an existing document with the provided data | | 2 | [Delete](#document-delete-transition) | Delete the referenced document | | 3 | [Transfer](#document-transfer-transition) | Transfer the referenced document to a new owner | | 4 | [Purchase](#document-purchase-transition) | Purchase the referenced document | | 5 | [Update price](#document-update-price-transition) | Update the price for the document | | 6 | IgnoreWhileBumpingRevision | Internal action type used to bypass revision bump | | 7 | [Index-only delete](#document-index-only-delete-transition) | Delete an [indexOnly](../reference/data-contracts.md#indexonly-document-types) document by its property values. Only valid for document types with `indexOnly` set (protocol version 14+). JSON `$action` value: `indexOnlyDelete`. | ### Document Create Transition The document create transition extends the [base transition](#document-base-transition) to include the following additional fields: | Field | Type | Size | Description | | - | - | - | - | | $entropy | array | 32 bytes | Entropy used in creating the [document ID](#document-id). Generated as [shown here](#entropy-generation). | | data | object | Varies | Document data being submitted. | | $prefundedVotingBalance | array | 2 elements | (Optional) Contested index name and amount of credits for unique index conflict resolution voting (e.g., [premium DPNS name](../explanations/dpns.md#conflict-resolution)). Required when the document resolves to a contested index. Starting in protocol version 14:
- The named index must be the contested index resolved for the submitted document values, and the field is rejected when the document resolves to no contested index.
- The amount is the most the contender is willing to pay. The contender is charged the fund to join the contest, which doubles once the contest holds 250 contenders and again for every 50 more, and keeps the rest. A smaller amount is rejected, and the identity must hold the amount it states.
- A contest holds at most [1,000](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-platform-version/src/version/system_limits/v4.rs#L177) contenders; the next one is rejected.
Before protocol version 14, the amount had to equal the contest's fund. | Each document create transition must comply with the structure defined in [rs-dpp](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_create_transition/v0/mod.rs#L70-L104) (in addition to the [document base transition](#document-base-transition) that is required for all document transitions). ::: {note} The document create transition data field must include all [required document properties](./data-contract-document.md#required-properties) specified in the data contract. ::: The following example document create transition and subsequent table demonstrate how the document transition base, document create transition, and data contract document definitions are assembled into a complete transition for inclusion in a [state transition](#document-overview): ```json { "$action": "create", "$dataContractId": "5wpZAEWndYcTeuwZpkmSa8s49cHXU5q2DhdibesxFSu8", "$id": "6oCKUeLVgjr7VZCyn1LdGbrepqKLmoabaff5WQqyTKYP", "$type": "note", "$identityContractNonce": 1, "$entropy": "yfo6LnZfJ5koT2YUwtd8PdJa8SXzfQMVDz", "message": "Tutorial Test @ Mon, 27 Apr 2020 20:23:35 GMT" } ``` | Field | Required By | | - | - | | $dataContractId | Document [base transition](#document-base-transition) | | $id | Document [base transition](#document-base-transition) | | $type | Document [base transition](#document-base-transition) | | $identityContractNonce | Document [base transition](#document-base-transition) | | $entropy | Document [create transition](#document-create-transition) | | message | Data Contract (the `message` document defined in the referenced data contract -`5wpZAEWndYcTeuwZpkmSa8s49cHXU5q2DhdibesxFSu8`) | ### Document Replace Transition The document replace transition extends the [base transition](#document-base-transition) to include the following additional fields: | Field | Type | Size | Description | | - | - | - | - | | $revision | unsigned integer | 64 bits | Document revision (=> 1) | | data | object | Varies | Document data being updated | Each document replace transition must comply with the structure defined in [rs-dpp](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_replace_transition/v0/mod.rs#L40-L47) (in addition to the [document base transition](#document-base-transition) that is required for all document transitions). ::: {note} The document replace transition data field must include all [required document properties](./data-contract-document.md#required-properties) specified in the data contract. ::: The following example document replace transition and subsequent table demonstrate how the document transition base, document replace transition, and data contract document definitions are assembled into a complete transition for inclusion in a [state transition](#document-overview): ```json { "$action": "replace", "$dataContractId": "5wpZAEWndYcTeuwZpkmSa8s49cHXU5q2DhdibesxFSu8", "$id": "6oCKUeLVgjr7VZCyn1LdGbrepqKLmoabaff5WQqyTKYP", "$type": "note", "$identityContractNonce": 1, "$revision": 1, "message": "Tutorial Test @ Mon, 27 Apr 2020 20:23:35 GMT" } ``` | Field | Required By | | - | - | | $dataContractId | Document [base transition](#document-base-transition) | | $id | Document [base transition](#document-base-transition) | | $type | Document [base transition](#document-base-transition) | | $identityContractNonce | Document [base transition](#document-base-transition) | | $revision | Document revision | | message | Data Contract (the `message` document defined in the referenced data contract -`5wpZAEWndYcTeuwZpkmSa8s49cHXU5q2DhdibesxFSu8`) | ### Document Delete Transition The document delete transition only requires the fields found in the [base document transition](#document-base-transition). See the [implementation in rs-dpp](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_delete_transition/v0/mod.rs#L24-L27) for details. The owner can delete a document only if its document type sets [`canBeDeleted`](./data-contract-document.md#document-removal) to `true`. Starting in protocol version 14, the stored document must also meet the type's `deleteConstraints` rules. ### Expired Documents :::{versionadded} 5.0.0 ::: A document of a type with a [`ttl`](./data-contract-document.md#document-removal) expires once block time reaches its `$createdAt` plus the `ttl`. From then on, replace, transfer, purchase, and update price transitions on it, and a moderator's restore or field change, are rejected, even before the platform has removed it ([rs-drive-abci](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-drive-abci/src/execution/validation/state_transition/common/validate_document_not_expired.rs#L31-L43)). Its owner may still delete it if the document type allows deletion. ### Document Transfer Transition The document transfer transition allows a document owner to transfer document ownership directly to another identity without making it available for purchase. This transition extends the [base transition](#document-base-transition) to include the following additional fields: | Field | Type | Size | Description | | - | - | - | - | | $revision | unsigned integer | 64 bits | Document revision (=> 1) | | recipientOwnerId | array of bytes | 32 bytes | Identifier of the recipient (new owner). See the [NFT page](../explanations/nft.md#transfer-and-trade) for more details. | Each document transfer transition must comply with the structure defined in [rs-dpp](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_transfer_transition/v0/mod.rs#L34-L41) (in addition to the [document base transition](#document-base-transition) that is required for all document transitions). ### Document Purchase Transition The document purchase transition allows an identity to purchase a document previously made available for sale by the current document owner. This transition extends the [document base transition](#document-base-transition) to include the following additional fields: | Field | Type | Size | Description | | - | - | - | - | | $revision | unsigned integer | 64 bits | Document revision (=> 1) | | price | unsigned integer | 64 bits | Number of credits being offered for the purchase. See the [NFT page](../explanations/nft.md#transfer-and-trade) for more details. | Each document purchase transition must comply with the structure defined in [rs-dpp](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_purchase_transition/v0/mod.rs#L24-L31) (in addition to the [document base transition](#document-base-transition) that is required for all document transitions). ### Document Update Price Transition The document update price transition allows a document owner to set or update the minimum price they will accept in exchange for transferring document ownership to another party. This transition extends the [document base transition](#document-base-transition) to include the following additional fields: | Field | Type | Size | Description | | - | - | - | - | | $revision | unsigned integer | 64 bits | Document revision (=> 1) | | $price | unsigned integer | 64 bits | Updated price for the document. Can only be set by the current document owner. See the [NFT page](../explanations/nft.md#transfer-and-trade) for more details. | Each document update price transition must comply with the structure defined in [rs-dpp](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_update_price_transition/v0/mod.rs#L28-L35) (in addition to the [document base transition](#document-base-transition) that is required for all document transitions). ### Document Index-Only Delete Transition :::{versionadded} 5.0.0 ::: The document index-only delete transition deletes a document of an [indexOnly](../reference/data-contracts.md#indexonly-document-types) document type. These documents have no stored row to look up by id, so the transition carries the document's property values instead. Platform recomputes every index entry from those values and the signer's identity as owner, then removes the entries. In JSON, the document's property values are flattened into the transition object alongside the [document base transition](#document-base-transition) fields; there is no enclosing `data` property. Internally, rs-dpp collects the property values in a `data` map. The supplied values must match those used when the document was created. ```json { "$action": "indexOnlyDelete", "$dataContractId": "5wpZAEWndYcTeuwZpkmSa8s49cHXU5q2DhdibesxFSu8", "$id": "6oCKUeLVgjr7VZCyn1LdGbrepqKLmoabaff5WQqyTKYP", "$identityContractNonce": 1, "$type": "like", "postId": "BwW4XJHcVsfRbqdMUK5hWaz2WYLxVjVQFzXyWj6YV2R", "hashtag": "dash" } ``` The transition has no `$revision` or `$entropy` field. It is rejected for document types without `indexOnly`; those are deleted with the [delete transition](#document-delete-transition). Each document index-only delete transition must comply with the structure defined in [rs-dpp](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_index_only_delete_transition/v0/mod.rs#L37-L45) (in addition to the [document base transition](#document-base-transition) that is required for all document transitions). ## Document Object The document object represents the data provided by the platform in response to a query. Responses consist of an array of these objects containing the following fields as defined in the Rust reference client ([rs-dpp](https://github.com/dashpay/platform/blob/v5.0.1/packages/rs-dpp/src/document/v0/mod.rs#L37-L137)): | Property | Type | Required | Description | | - | - | - | - | | $id | array | Yes | The [document ID](#document-id) (32 bytes)| | $type | string | Yes | Document type defined in the referenced contract (1-64 characters). Supplied by the query context; not a stored field of the `DocumentV0` struct. | | $revision | unsigned integer (64 bits) | No | Document revision (=>1) if the document is mutable | | $dataContractId | array | Yes | Data contract ID [generated](../protocol-ref/data-contract.md#data-contract-id) from the data contract's `ownerId` and `identity nonce` (32 bytes). Supplied by the query context; not a stored field of the `DocumentV0` struct. | | $ownerId | array | Yes | [Identity](../protocol-ref/identity.md) of the user submitting the document (32 bytes) | | $createdAt | unsigned integer (64 bits) | No | Time (in milliseconds) at document creation, if required by the document type schema | | $updatedAt | unsigned integer (64 bits) | No | Last document update time in milliseconds, if required by the document type schema | |$transferredAt | unsigned integer (64 bits) | No | Last transferred time in milliseconds, if required by the document type schema | | $createdAt
BlockHeight | unsigned integer (64 bits) | No | Block height at document creation, if required by the schema | | $updatedAt
BlockHeight | unsigned integer (64 bits) | No | Block height at the document's last update, if required by the schema | | $transferredAt
BlockHeight | unsigned integer (64 bits) | No | Block height when document was last transferred, if required by the schema | | $createdAt
CoreBlockHeight | unsigned integer (32 bits) | No | Core block height at document creation, if required by the schema | | $updatedAt
CoreBlockHeight | unsigned integer (32 bits) | No |Core block height at the document's last update, if required by the schema | | $transferredAt
CoreBlockHeight | unsigned integer (32 bits) | No |Core block height when document was last transferred, if required by the schema | | $creatorId | array | No | Identity of the document creator (32 bytes), if required by the document type schema | | $moderatedAt | unsigned integer (64 bits) | No | **Added in 5.0.0.** Time (in milliseconds) a moderator last wrote the properties the document type lists in [`moderatorAbilities.changeFields`](./data-contract-document.md#moderator-abilities). Absent until a moderator writes them. | | $moderatedBy | array | No | **Added in 5.0.0.** [Identity](../protocol-ref/identity.md) of the moderator that last wrote those properties (32 bytes). Set together with `$moderatedAt`. | | $contractVersion | unsigned integer (32 bits) | No | Data contract version the document's stored bytes conform to. Set by platform on create and replace and kept on transfer and purchase. Present when the document type uses `requiredSince`; absent for documents stored before this field existed. | ### Example Document Object ```json { "$id": "4mWnFcDDzCpeLExJqE8v7pfN4EERC8NE2xn4hw3VKriU", "$type": "note", "$dataContractId": "63au7XVDt8aHtPrsYKoHx2bnRTSenwH62pDN1BQ5n5m9", "$ownerId": "7TkaE5uhG3T9AhyEkAvYCqZvRH4pyBibhjuSYPReNfME", "$revision": 1, "message": "Tutorial Test @ Mon, 26 Oct 2020 15:54:35 GMT", "$createdAt": 1603727675072, "$updatedAt": 1603727675072 } ```