Asset Shards
Apps typically must have some representation of “Assets” on-chain. An Asset is an abstract concept of a single entity which has a set of data fields associated with it. An Asset is a non-fungible item, each one has a unique identity and possibly a value. Assets also typically have complex logic attached to them, such as having an embedded owner, permissions on who can update or view them, and more. Attaching this complex logic directly into the data on the Mercata blockchain is what makes smart contracts so powerful. It enables the "smart" part of the contract and is why smart contracts are typically called "programmable data", since the data is able to be altered and accessed in a definite, deterministic manner.
An asset is something like:
- A Car
- A Property Deed
- A Commodity
Asset Shards
In Mercata Dapps, we use smart contracts to hold the data of the asset, and encode all necessary business logic. In order to allow individual assets to have their own privacy, we create each asset on a unique Privacy Shard just for that asset. The asset’s contract is itself the Governance Contract of that shard, so the Asset’s logic governs the members of the shard. Hence each shard is referred to as an “Asset Shard”. This way when an asset is shared with another party, only that asset is shared, not every one in the application.
This diagram shows the shard architecture for a typical Dapp that deploys with a Private Dapp Realm and uses Asset Shards.
Asset Shard contracts are coded like standard smart contracts, except that they have the added functionality of being able to encode network-level visibility and access logic into themselves directly. Let's take Asset ownership transfer as an example. When the asset is initially created, only the owner’s node has visibility into the Asset Shard, since that is the default visibility when a shard is created. At a later point in time, the owner transfers the asset to a user from another organization. In the asset smart contract, the transferOwnership
function can add the new owner's organization's node to the shard, and even remove the current owner’s membership.
Without the use of asset shards, every asset contract would live on the Private Dapp Realm, and be visible to all members of the Dapp. This would prevent the app from having assets privately held and transferrable amongst users.
We do not put any more data (contracts) on an asset shard than what is strictly related to that unique asset. Therefore there is usually a 1:1 relation between the number of assets in your app and number of asset shards. If your asset has related sub-entities that have the same visibility requirements, then they may exist on the same Asset’s shard. Examples of this are things like a shipment and a receipt for the shipment. A function on the Shipment
contract itself would trigger the creation of a new Receipt
contract on the same shard.
Sample “Car” Asset Contract:
contract Car {
string public color;
uint public weight;
uint public length;
uint public width;
string public manufacturer;
address public owner;
// reserved events in SolidVM used to add/remove members from a shard
event OrganizationAdded(string org);
event OrganizationRemoved(string org);
constructor(
string _color,
uint _weight,
uint _length,
uint _width,
string _manufacturer
) {
color = _color;
weight = _weight;
length = _length;
width = _width;
manufacturer = _manufacturer;
owner = tx.origin;
}
function transferOwnership(address _newOwner) {
mapping(string => string) newCert = getUserCert(_newOwner);
emit OrganizationAdded(newCert["organization"]);
emit OrganizationRemoved(getUserCert(owner)["organization"]);
owner = _newOwner;
}
}
Sample “Shipment” and "Receipt" Asset Contracts:
contract Receipt {
string public shipmentId;
uint public receiptDate;
string public details;
constructor(string _shipmentId, string _details) {
shipmentId = _shipmentId;
details = _details;
receiptDate = block.timestamp;
}
}
contract Shipment {
string public id;
address public to;
address public from;
string public description;
string public itemType;
uint public itemQuantity;
address public receipt;
// reserved events in SolidVM used to add/remove members from a shard
event OrganizationAdded(string org);
event OrganizationRemoved(string org);
constructor(
string _id,
string _to,
string _description,
string _itemType,
uint _itemQuantity
) {
id = _id;
to = _to;
from = tx.origin;
description = _description;
itemType = _itemType;
itemQuantity = _itemQuantity;
}
function deliver(string _receiptDetails) {
require(tx.origin == from, "Shipment sender must deliver the item");
mapping(string => string) toCert = getUserCert(to);
emit OrganizationAdded(to["organization"]);
emit OrganizationRemoved(getUserCert(from)["organization"]);
Receipt rec = new Receipt(id, _receiptDetails);
receipt = address(rec);
}
}
When creating the Asset Shard from your application’s server, always make sure to set the Asset Shards “parentChain” to the Shard ID of your already-deployed Private Dapp Realm. It is strongly recommended to use Code Pointers for the Asset contract’s source code. Also refer to the Filtering App Data documentation for additional best-practices on creating assets in a deployed Dapp Realm.
Example JS code to create a new Car
Asset Shard: (some helper functions not shown/included in the Asset Framework)
import carJs from './car'
async function createCar(user, args, options) {
const userAddress = await rest.getKey(user, options);
const deploy = getYamlFile(`${config.configDirPath}/${config.deployFilename}`);
const myCert = await certificateJs.getCertificateMe(user)
const chainArgs = {
// use a code pointer to the code collection of the Private Dapp Realm
codePtr: {
account: `${deploy.dapp.contract.address}:${deploy.dapp.contract.appChainId}`,
name: carJs.contractName,
},
// set the parent shard of this asset shard
parentChain: deploy.dapp.contract.appChainId,
args: util.usc(args),
// add my own org by default
members: [
{
orgName: myCert.organization,
orgUnit: myCert.organizationalUnit || '',
commonName: '',
access: true,
}
],
balances: [
{
address: userAddress,
balance: 100000000000000000000000000000,
},
{
address: deploy.dapp.contract.address,
balance: 100000000000000000000000000000,
},
{
address: constants.governanceAddress,
balance: 100000000000000000000000000000,
},
],
metadata: {
history: carJs.contractName,
// use SolidVM for this contract
VM: 'SolidVM',
},
name: carJs.contractName,
label: `Car-${util.uid()}-chain`,
};
const contractArgs = {
name: carJs.contractName,
}
const chainId = await rest.createChain(user, chainArgs, contractArgs, options);
return chainId
}
Accessing Asset Details
Once you have Assets Shards on the STRATO Mercata Network, you can easily read their data using the STRATO Cirrus API.
Multiple Assets
To access multiple assets of the same type at once, you can query the Asset's entire table by simply querying the following endpoint:
GET https://<strato_url>/cirrus/search/<DappCreatorOrg>-<DappName>-<AssetName>
You can also search for all assets that match a given filter. For example, we can search for all Shipments that were sent to a particular user:
GET https://<strato_url>/cirrus/search/<DappCreatorOrg>-<DappName>-Shipment?to=eq.<userAddress>
Or search for shipments with less than 5 items in them:
GET https://<strato_url>/cirrus/search/<DappCreatorOrg>-<DappName>-Shipment?itemQuantity=lt.5
Applications can also enable text-based partial-value search by using the LIKE
/ILIKE
SQL filter operator, which matches values that contain the query:
GET https://<strato_url>/cirrus/search/<DappCreatorOrg>-<DappName>-Shipment?description=like.banana
This would allows users to search for Shipments with a description containing the word "banana", but the whole description does not neccessarily have to be just the word "banana".
Single Asset
You can add query parameters to the URL that directly translate into SQL filters. So if you are looking for an asset with a particular ID or data field, you can filter the rows based the desired value. Since assets are created as their own contracts on Private Shards, they are each guaranteed to have unique shard IDs, but all have the same address (0x100
), since they are the Governance contract of their shard (Other contracts created on this shard will have unique addresses). Therefore STRATO Mercata guarantees that each asset created has its own unique identifier in the form of the Asset's shard ID. If your application requires another method of uniquely identifying assets, it is up to your own application code to ensure uniqueness.
Query by Asset Shard ID
GET https://<strato_url>/cirrus/search/<DappCreatorOrg>-<DappName>-<AssetName>?chainId=eq.<asset-shardId>
Query Shipment by ID
GET https://<strato_url>/cirrus/search/<DappCreatorOrg>-<DappName>-Shipment?id=eq.<shipment-id>