Data Contract

Class Name DataContract
Extends BaseContract
Source data-contract.ts
Examples data-contract.spec.ts

The DataContract is a secured data storage contract for single properties and lists. If created on its own, DataContracts cannot do very much. They rely on their authority to check which entries or lists can be used.

The following functions support the encryptionContext argument:

If this argument is set, the data key in the data contracts sharing is encrypted by using a context key instead of the communication key between owner and contract member. This allows to omit key exchanges between contract owner and members and therefore enables the owner to write content to the smart contract, that can be used by a group of identities/accounts, which only needs to hold the context key. So the encryptionContext can be used to address a group of accounts/identities instead of single account/identity.

For more information about DataContracts purpose and their authorities see Data Contract in the evan.network wiki.


constructor

new DataContract(options);

Creates a new DataContract instance.

Parameters

  1. options - DataContractOptions: options for DataContract constructor.

Returns

DataContract instance

Example

const dataContract = new DataContract({
  cryptoProvider,
  description,
  dfs,
  executor,
  loader,
  nameResolver,
  sharing,
  web3,
});

create

dataContract.create(factoryName, accountId[, businessCenterDomain, contractDescription, allowConsumerInvite, sharingsHash]);

Create and initialize new contract.

Parameters

  1. factoryName - string: contract factory name, used for ENS lookup; if the factory name contains periods, it is threaded as an absolute ENS domain and used as such, if not it will be used as ${factoryName}.factory.${businessCenterDomain}
  2. accountId - string: owner(identity/account) of the new contract and transaction executor
  3. businessCenterDomain - string (optional): ENS domain name of the business center
  4. contractDescription - string|any (optional): bytes32 hash of DBCP description or a schema object
  5. allowConsumerInvite - bool (optional): true if consumers are allowed to invite other consumer
  6. sharingsHash - string (optional): existing sharing to add, defaults to null

Returns

Promise returns any: contract instance

Example

Let’s say, we want to create a DataContract for a business center at the domain “samplebc.evan” and this business center has a DataContractFactory named “testdatacontract”. We want to have two users working in our DataContract, so we get these sample values:

const factoryName = 'testdatacontract';
const businessCenterDomain = 'samplebc.evan';
const accounts = [
  '0x0000000000000000000000000000000000000001',
  '0x0000000000000000000000000000000000000002',
];

Now create a contract with:

const contract = await dataContract.create(factoryName, accounts[0], businessCenterDomain);

Okay, that does not provide a description for the contract. Let’s add a description to the process. The definition is a DBCP contract definition and is stored in an Envelope (see Encryption):

const definition: Envelope = {
  "public": {
    "name": "Data Contract Sample",
    "description": "reiterance oxynitrate sat alternize acurative",
    "version": "0.1.0",
    "author": "evan GmbH",
    "dataSchema": {
      "list_settable_by_member": {
        "$id": "list_settable_by_member_schema",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "foo": { "type": "string" },
          "bar": { "type": "integer" }
        }
      },
      "entry_settable_by_member": {
        "$id": "entry_settable_by_member_schema",
        "type": "integer",
      }
    }
  }
};
definition.cryptoInfo = cryptoProvider.getCryptorByCryptoAlgo('aes').getCryptoInfo(accounts[0]);
const contract = await dataContract.create('testdatacontract', accounts[0], businessCenterDomain, definition);

Now we have a DataContract with a description. This contract is now able to be understood by other components, that understand the dbcp. And on top of that, we provided data schemas for the two properties list_settable_by_member and entry_settable_by_member (written for ajv). This means, that when someone adds or sets entries to or in those properties, the incoming data is validated before actually encrypting and storing it.

To allow other users to work on the contract, they have to be invited with:

await dataContract.inviteToContract(businessCenterDomain, contract.options.address, accounts[0], accounts[1]);

Now the user accounts[1] can use functions from the contract, but to actually store data, the user needs access to the data key for the DataContract. This can be done via updating the contracts sharing:

const blockNr = await web3.eth.getBlockNumber();
const contentKey = await sharing.getKey(contract.options.address, accounts[0], '*', blockNr);
await sharing.addSharing(contract.options.address, accounts[0], accounts[1], '*', blockNr, contentKey);

Now the contract has been created, has a sharing and another user has been granted access to it. Variable names from this section will be used in the rest of the document as example values.


createSharing

dataContract.createSharing(accountId);

Create initial sharing for contract.

Parameters

  1. accountId - string: identity or account which is owner of the new contract

Returns

Promise returns any: sharing info with { contentKey, hashKey, sharings, sharingsHash, }

Example

const sharing = await dataContract.createSharing(profileReceiver);

= Entries =

setEntry

dataContract.setEntry(contract, entryName, value, accountId[, dfsStorage, encryptedHashes, encryption);

Set entry for a key.

Parameters

  1. contract - any|string: contract or contractId
  2. entryName - string: entry name
  3. value - any: value to set
  4. accountId - string: identity or account
  5. dfsStorage - Function (optional): store values in dfs, defaults to true
  6. encryptedHashes - boolean (optional): encrypt hashes from values, defaults to true
  7. encryption - string (optional): encryption algorithm to use, defaults to defaultCryptoAlgo (set in constructor)
  8. encryptionContext - string (optional): plain text name of an encryption context, defaults to accountId

Returns

Promise returns void: resolved when done

Example

const sampleValue = 123;
await dataContract.setEntry(contract, 'entry_settable_by_owner', sampleValue, accounts[0]);

Entries are automatically encrypted before setting it in the contract. If you want to use values as is, without encrypting them, you can add them in raw mode, which sets them as bytes32 values:

const sampleValue = '0x000000000000000000000000000000000000007b';
await dataContract.setEntry(contract, 'entry_settable_by_owner', sampleValue, accounts[0], true);

getEntry

dataContract.getEntry(contract, entryName, accountId[, dfsStorage, encryptedHashes]);

Return entry from contract.

Parameters

  1. contract - any|string: contract or contractId
  2. entryName - string: entry name
  3. accountId - string: identity or account
  4. dfsStorage - Function (optional): store values in dfs, defaults to true
  5. encryptedHashes - boolean (optional): decrypt hashes from values, defaults to true

Returns

Promise returns any: entry

Example

Entries can be retrieved with:

const retrieved = await dataContract.getEntry(contract, 'entry_settable_by_owner', accounts[0]);

Raw values can be retrieved in the same way:

const retrieved = await dataContract.getEntry(contract, 'entry_settable_by_owner', accounts[0], true);

= List Entries =

addListEntries

dataContract.addListEntries(contract, listName, values, accountId[, dfsStorage, encryptedHashes, encryption]);

Add list entries to lists.

List entries support the raw mode as well. To use raw values, pass true in the same way as wehn using the entries functions.

List entries can be added in bulk, so the value argument is an array with values. This array can be arbitrarily large up to a certain degree. Values are inserted on the blockchain side and adding very large arrays this way may take more gas during the contract transaction, than may fit into a single transaction. If this is the case, values can be added in chunks (multiple transactions).

Parameters

  1. contract - any|string: contract or contractId
  2. listName - string: name of the list in the data contract
  3. values - any[]: values to add
  4. accountId - string: identity or account
  5. dfsStorage - string (optional): store values in dfs, defaults to true
  6. encryptedHashes - boolean (optional): encrypt hashes from values, defaults to true
  7. encryption - string (optional): encryption algorithm to use, defaults to defaultCryptoAlgo (set in constructor)
  8. encryptionContext - string (optional): plain text name of an encryption context, defaults to accountId

Returns

Promise returns void: resolved when done

Example

const sampleValue = {
  foo: 'sample',
  bar: 123,
};
await dataContract.addListEntries(contract, 'list_settable_by_member', [sampleValue], accounts[0]);

When using lists similar to tagging list entries with metadata, entries can be added in multiple lists at once by passing an array of list names:

const sampleValue = {
  foo: 'sample',
  bar: 123,
};
await dataContract.addListEntries(contract, ['list_1', 'list_2'], [sampleValue], accounts[0]);

getListEntryCount

dataContract.getListEntryCount(contract, listName, index, accountId[, dfsStorage, encryptedHashes]);

Return number of entries in the list. Does not try to actually fetch and decrypt values, but just returns the count.

Parameters

  1. contract - any|string: contract or contractId
  2. listName - string: name of the list in the data contract

Returns

Promise returns number: list entry count

Example

await dataContract.getListEntryCount(contract, 'list_settable_by_member');

getListEntries

dataContract.getListEntries(contract, listName, accountId[, dfsStorage, encryptedHashes, count, offset, reverse]);

Return list entries from contract. Note, that in the current implementation, this function retrieves the entries one at a time and may take a longer time when querying large lists, so be aware of that, when you retrieve lists with many entries.

Parameters

  1. contract - any|string: contract or contractId
  2. listName - string: name of the list in the data contract
  3. accountId - string: identity or account
  4. dfsStorage - string (optional): store values in dfs, defaults to true
  5. encryptedHashes - boolean (optional): decrypt hashes from values, defaults to true
  6. count - number (optional): number of elements to retrieve, defaults to 10
  7. offset - number (optional): skip this many items when retrieving, defaults to 0
  8. reverse - boolean (optional): retrieve items in reverse order, defaults to false

Returns

Promise returns any[]: list entries

Example

await dataContract.getListEntries(contract, 'list_settable_by_member', accounts[0]));

getListEntry

dataContract.getListEntry(contract, listName, index, accountId[, dfsStorage, encryptedHashes]);

Return a single list entry from contract.

Parameters

  1. contract - any|string: contract or contractId
  2. listName - string: name of the list in the data contract
  3. index - number: list entry id to retrieve
  4. accountId - string: identity or account
  5. dfsStorage - string (optional): store values in dfs, defaults to true
  6. encryptedHashes - boolean (optional): decrypt hashes from values, defaults to true

Returns

Promise returns any: list entry

Example

const itemIndex = 0;
await dataContract.getListEntry(contract, 'list_settable_by_member', itemIndex, accounts[0]));

removeListEntry

dataContract.removeListEntry(contract, listName, entryIndex, accountId);

Remove list entry from list.

This will reposition last list entry into emptied slot.

Attention

If the data contract was created by the container by the DigitalTwin API you need to grant the permissions for removing list entries manually. To do this you can use the RightsAndRoles API. The following example shows how to grant permissions to delete list entries from list exampleList to group 0.

// make sure, you have required the enums from rights-and-roles.ts
import { ModificationType, PropertyType } from '@evan.network/api-blockchain-core';
const contract = '0x0000000000000000000000000000000000000123';
const contractOwner = '0x0000000000000000000000000000000000000001';
await rightsAndRoles.setOperationPermission(
  contract,                   // contract to be updated
  contractOwner,              // identity or account, that can change permissions
  0,                          // role id, uint8 value
  'exampleList',              // name of the object
  PropertyType.ListEntry,     // what type of element is modified
  ModificationType.Remove,    // type of the modification
  true,                       // grant this capability
);

Parameters

  1. contract - any|string: contract or contractId
  2. listName - string: name of the list in the data contract
  3. index - number: index of the entry to remove from list
  4. accountId - string: identity or account

Returns

Promise returns void: resolved when done

Example

const listName = 'list_removable_by_owner'
const itemIndexInList = 1;
await dataContract.removeListEntry(contract, listNameF, itemIndexInList, accounts[0]);

moveListEntry

dataContract.moveListEntry(contract, listNameFrom, entryIndex, listNamesTo, accountId);

Move one list entry to one or more lists.

Note, that moving items requires the executing account to have remove permissions on the list listNameFrom. If this isn’t the case, the transaction will not be exetured and not updates will be made.

Parameters

  1. contract - any|string: contract or contractId
  2. listNameFrom - string: origin list
  3. index - number: index of the entry to move in the origin list
  4. listNamesTo - string: lists to move data into
  5. accountId - string: identity or account

Returns

Promise returns void: resolved when done

Example

const listNameFrom = 'list_removable_by_owner';
const listNameTo = 'list_settable_by_member';
const itemIndexInFromList = 1;
await dataContract.moveListEntry(contract, listNameFrom, itemIndexInFromList, [listNameTo], accounts[0]);

= Mappings =

setMappingValue

dataContract.setMappingValue(contract, mappingName, entryName, value, accountId[, dfsStorage, encryptedHashes, encryption]);

Set entry for a key in a mapping. Mappings are basically dictionaries in data contracts. They are a single permittable entry, that allows to set any keys to it. This can be used for properties, that should be extended during the contracts life as needed, but without the need to update its permission settings.

Parameters

  1. contract - any|string: contract or contractId
  2. mappingName - string: name of a data contracts mapping property
  3. entryName - string: entry name (property in the mapping)
  4. value - any: value to add
  5. accountId - string: identity or account
  6. dfsStorage - string (optional): store values in dfs, defaults to true
  7. encryptedHashes - boolean (optional): encrypt hashes from values, defaults to true
  8. encryption - string (optional): encryption algorithm to use, defaults to defaultCryptoAlgo (set in constructor)
  9. encryptionContext - string (optional): plain text name of an encryption context, defaults to accountId

Returns

Promise returns void: resolved when done

Example

await dataContract.setMappingValue(
  contract,
  'mapping_settable_by_owner',
  'sampleKey',
  'sampleValue',
  accounts[0],
  storeInDfs,
);

getMappingValue

dataContract.getMappingValue(contract, listName, index, accountId[, dfsStorage, encryptedHashes]);

Return a value from a mapping. Looks up a single key from a mapping and returns its value.

Parameters

  1. contract - any|string: contract or contractId
  2. mappingName - string: name of a data contracts mapping property
  3. entryName - string: entry name (property in the mapping)
  4. accountId - string: identity or account
  5. dfsStorage - string (optional): store values in dfs, defaults to true
  6. encryptedHashes - boolean (optional): encrypt hashes from values, defaults to true
  7. encryption - string (optional): encryption algorithm to use, defaults to defaultCryptoAlgo (set in constructor)

Returns

Promise returns any: mappings value for given key

Example

const value = await dataContract.getMappingValue(
  contract,
  'mapping_settable_by_owner',
  'sampleKey',
  accounts[0],
  storeInDfs,
);

= Encryption =

encrypt

dataContract.encrypt(toEncrypt, contract, accountId, propertyName, block[, encryption]);

Encrypt incoming envelope.

Parameters

  1. toEncrypt - Envelope: envelope with data to encrypt
  2. contract - any: contract instance or contract id
  3. accountId - string: identity or account
  4. propertyName - string: property in contract, the data is encrypted for
  5. block - block: block the data belongs to
  6. encryption - string: encryption name, defaults to defaultCryptoAlgo (set in constructor)

Returns

Promise returns string: encrypted envelope or hash as string

Example

const data = {
  public: {
    foo: 'example',
  },
  private: {
    bar: 123,
  },
  cryptoInfo: cryptor.getCryptoInfo(nameResolver.soliditySha3(accounts[0])),
};
const encrypted = await dataContract.encrypt(data, contract, accounts[0], 'list_settable_by_member', 12345);

decrypt

dataContract.decrypt(toDecrypt, contract, accountId, propertyName, block[, encryption]);

Decrypt input envelope return decrypted envelope.

Parameters

  1. toDecrypt - string: data to decrypt
  2. contract - any: contract instance or contract id
  3. accountId - string: identity or account that decrypts the data
  4. propertyName - string: property in contract that is decrypted

Returns

Promise returns Envelope: decrypted envelope

Example

const encrypted = await dataContract.decrypt(encrypted, contract, accounts[0], 'list_settable_by_member');

encryptHash

dataContract.encryptHash(toEncrypt, contract, accountId);

Encrypt incoming hash. This function is used to encrypt DFS file hashes, uses AES ECB for encryption.

Parameters

  1. toEncrypt - Envelope: hash to encrypt
  2. contract - any: contract instance or contract id
  3. accountId - string: encrypting identity or account

Returns

Promise returns string: hash as string

Example

const hash = '0x1111111111111111111111111111111111111111111111111111111111111111';
const encrypted = await dataContract.encryptHash(hash, contract, accounts[0]);

decryptHash

dataContract.encrypt(toEncrypttoDecrypt, contract, accountId, propertyName, block[, encryption]);

Decrypt input hash, return decrypted hash. This function is used to decrypt encrypted DFS file hashes, uses AES ECB for decryption.

Parameters

  1. toDecrypt - Envelope: hash to decrypt
  2. contract - any: contract instance or contract id
  3. accountId - string: identity or account that decrypts the hash

Returns

Promise returns string: decrypted hash

Example

const encryptedHash = '0x2222222222222222222222222222222222222222222222222222222222222222';
const encrypted = await dataContract.decryptHash(encryptedHash, contract, accounts[0]);

clearSharings

dataContract.clearSharings();

Clears cached sharing information of this contract Can be used when sharings have been updated externally and new sharings should be fetched.

Example

dataContract.clearSharings();

unpinFileHash

dataContract.unpinFileHash(hash);

Removes a file hash from the DFS.

Parameters

  1. hash - string: reference to the DFS file, which can be either an IPFS or a bytes32 hash

Returns

Promise returns void: resolved when done

Example

const descriptionHash = await this.options.executor.executeContractCall(
    myDataContract,
    'contractDescription',
  );
await this.options.dataContract.unpinFileHash(descriptionHash);

getDfsContent

dataContract.getDfsContent(hash);

Gets a file’s content from the DFS.

Parameters

  1. hash - string: reference to the DFS file, which can be either an IPFS or a bytes32 hash

Returns

Promise returns Buffer: the content of the retrieved file

Example

const descriptionHash = await this.options.executor.executeContractCall(
    myDataContract,
    'contractDescription',
  );
const descriptionContent = (await this.options.dataContract.getDfsContent(descriptionHash)).toString('binary');
const description = JSON.parse(
    descriptionContent,
);