Update a data contract#

It is possible to update existing data contracts in certain backwards-compatible ways. This includes:

  • Adding new documents

  • Adding new optional properties to existing documents

  • Adding non-unique indices for properties added in the update.

In this tutorial we will update an existing data contract.

Prerequisites#

Code#

The following examples demonstrate updating an existing contract to add a new property to an existing document. The second example shows how to update a contract that has contract history enabled:

import { setupDashClient } from '../setupDashClient.mjs';

const { sdk, keyManager } = await setupDashClient();
const { identityKey, signer } = await keyManager.getAuth();

// Edit these values for your environment
// Your contract ID from the Register a Data Contract tutorial
const DATA_CONTRACT_ID =
  process.env.DATA_CONTRACT_ID || 'YOUR_DATA_CONTRACT_ID';
const DOCUMENT_TYPE = 'note';

if (!DATA_CONTRACT_ID || DATA_CONTRACT_ID === 'YOUR_DATA_CONTRACT_ID') {
  throw new Error(
    'Set DATA_CONTRACT_ID (env var or in code) to your contract ID from the Register a Data Contract tutorial',
  );
}

try {
  const existingContract = await sdk.contracts.fetch(DATA_CONTRACT_ID);

  // Increment the contract version
  existingContract.version += 1;

  // Clone schemas, then add a new "author" property to the DOCUMENT_TYPE schema
  const updatedSchemas = structuredClone(existingContract.schemas);
  updatedSchemas[DOCUMENT_TYPE].properties.author = {
    type: 'string',
    position: 1,
  };

  // Apply the updated schemas (enable full validation)
  existingContract.setSchemas(updatedSchemas, undefined, true, undefined);

  // Submit the update
  await sdk.contracts.update({
    dataContract: existingContract,
    identityKey,
    signer,
  });

  console.log('Contract updated:\n', existingContract.toJSON());
} catch (e) {
  console.error('Something went wrong:\n', e.message);
}

Note: This code includes a workaround for a known bug (dashpay/platform#3165) where sdk.contracts.fetch() returns undefined for contracts with keepsHistory: true. Instead, it uses sdk.contracts.getHistory() to retrieve the contract and takes the latest revision. It also calls setConfig() to enable history tracking on the contract before submitting the update.

import { setupDashClient } from '../setupDashClient.mjs';

const { sdk, keyManager } = await setupDashClient();
const { identityKey, signer } = await keyManager.getAuth();

// Edit these values for your environment
// Your contract ID from the Register a Data Contract tutorial
const DATA_CONTRACT_ID =
  process.env.DATA_CONTRACT_ID || 'YOUR_DATA_CONTRACT_ID';
const DOCUMENT_TYPE = 'note';

if (!DATA_CONTRACT_ID || DATA_CONTRACT_ID === 'YOUR_DATA_CONTRACT_ID') {
  throw new Error(
    'Set DATA_CONTRACT_ID (env var or in code) to your contract ID from the Register a Data Contract tutorial',
  );
}

try {
  // Workaround: sdk.contracts.fetch() returns undefined for contracts with
  // keepsHistory: true due to a proof verification bug (dashpay/platform#3165).
  // Use getHistory() and take the latest version instead.
  // Note: for contracts with many revisions, history results may be paginated
  // and the last entry here may not be the true latest version.
  const history = await sdk.contracts.getHistory({
    dataContractId: DATA_CONTRACT_ID,
  });

  let existingContract;
  for (const [, contract] of history) {
    existingContract = contract;
  }

  if (!existingContract) {
    throw new Error(`Contract ${DATA_CONTRACT_ID} not found`);
  }

  // Increment the contract version
  existingContract.version += 1;

  // Clone schemas, then add a new "author" property to the DOCUMENT_TYPE schema
  const updatedSchemas = structuredClone(existingContract.schemas);
  updatedSchemas[DOCUMENT_TYPE].properties.author = {
    type: 'string',
    position: 1,
  };

  // Apply the updated schemas (enable full validation)
  existingContract.setSchemas(updatedSchemas, undefined, true, undefined);

  // Enable storing of contract history
  existingContract.setConfig({
    canBeDeleted: false,
    readonly: false,
    keepsHistory: true,
    documentsKeepHistoryContractDefault: false,
    documentsMutableContractDefault: true,
  });

  // Submit the update
  await sdk.contracts.update({
    dataContract: existingContract,
    identityKey,
    signer,
  });

  console.log('Contract updated:\n', existingContract.toJSON());
} catch (e) {
  console.error('Something went wrong:\n', e.message);
}

Note

Please refer to the data contract reference page for more comprehensive details related to contracts and documents.

What’s Happening#

After we initialize the client, we get the auth key signer from the key manager. We then fetch the existing contract and modify the contract’s document schema (adding an author property to the note document schema).

We clone the existing schemas, add the new property, then apply them with setSchemas(). After incrementing the contract version, we call sdk.contracts.update() with the modified contract and signing credentials to submit the update to the network.