Tutorial: Car Sales Management
Build a decentralised car listing and sales application on Dash Platform. By the end you will have a data contract for vehicle listings, the ability to create/query/update listings, and a purchase flow using document transfers.
How this works in practice: Data contracts are deployed once using a Node.js script with a developer identity. After deployment, your browser app uses the published contract ID to create, query, and update documents. Steps 1-2 below are run from Node.js; steps 3 onward can run in either Node.js or the browser.
What you will learn
- Designing a data contract with multiple document types
- Publishing a contract to testnet from a Node.js deployment script
- Creating, querying, and updating documents (Node.js or browser)
- Using document pricing and purchase for a sales flow
Prerequisites
npm install @dashevo/evo-sdk
You need a funded testnet identity. See the Getting Started chapter for setup.
Step 1: Design the data contract
A car sales contract needs two document types: listings (vehicles for sale) and reviews (buyer reviews of sellers).
const carSalesSchema = {
listing: {
type: 'object',
properties: {
make: { type: 'string', maxLength: 63, position: 0 },
model: { type: 'string', maxLength: 63, position: 1 },
year: { type: 'integer', minimum: 1900, maximum: 2100, position: 2 },
mileageKm: { type: 'integer', minimum: 0, position: 3 },
priceUsd: { type: 'integer', minimum: 0, position: 4 },
description: { type: 'string', maxLength: 1024, position: 5 },
imageUrl: { type: 'string', maxLength: 512, format: 'uri', position: 6 },
status: { type: 'string', enum: ['available', 'pending', 'sold'], position: 7 },
},
required: ['make', 'model', 'year', 'priceUsd', 'status'],
additionalProperties: false,
},
review: {
type: 'object',
properties: {
sellerId: { type: 'string', maxLength: 44, position: 0 },
listingId: { type: 'string', maxLength: 44, position: 1 },
rating: { type: 'integer', minimum: 1, maximum: 5, position: 2 },
comment: { type: 'string', maxLength: 512, position: 3 },
},
required: ['sellerId', 'rating'],
additionalProperties: false,
},
};
Step 2: Connect and publish the contract
import { EvoSDK, DataContract, Document, Identifier, IdentitySigner } from '@dashevo/evo-sdk';
const sdk = EvoSDK.testnetTrusted();
await sdk.connect();
// Your identity credentials
const identityId = 'YOUR_IDENTITY_ID';
const privateKeyWif = 'YOUR_PRIVATE_KEY_WIF';
const signingKeyIndex = 0;
// Set up signing
const identity = await sdk.identities.fetch(identityId);
const identityKey = identity.publicKeys[signingKeyIndex];
const signer = new IdentitySigner();
signer.addKeyFromWif(privateKeyWif);
// Publish the data contract
const nonce = await sdk.identities.nonce(identityId);
const dataContract = new DataContract({
ownerId: new Identifier(identityId),
identityNonce: nonce + 1n,
schemas: carSalesSchema,
});
const contract = await sdk.contracts.publish({ dataContract, identityKey, signer });
const contractId = contract.id.toString();
console.log('Contract published:', contractId);
Save the contractId — you will need it for all subsequent operations.
Step 3: Create a listing
const doc = new Document({
documentTypeName: 'listing',
dataContractId: new Identifier(contractId),
ownerId: new Identifier(identityId),
properties: {
make: 'Toyota',
model: 'Camry',
year: 2021,
mileageKm: 45000,
priceUsd: 22500,
description: 'Well-maintained, single owner, full service history.',
status: 'available',
},
});
await sdk.documents.create({ document: doc, identityKey, signer });
console.log('Listing created!');
Step 4: Query listings
// Fetch all available listings
const results = await sdk.documents.query({
dataContractId: contractId,
documentTypeName: 'listing',
where: [['status', '==', 'available']],
orderBy: [['priceUsd', 'asc']],
limit: 20,
});
for (const [id, doc] of results) {
if (!doc) continue;
const data = doc.properties as Record<string, unknown>;
console.log(`${data.year} ${data.make} ${data.model} — $${data.priceUsd}`);
console.log(` ID: ${id}`);
}
Search by make
const toyotas = await sdk.documents.query({
dataContractId: contractId,
documentTypeName: 'listing',
where: [
['make', '==', 'Toyota'],
['status', '==', 'available'],
],
limit: 10,
});
Step 5: Update a listing
Mark a listing as sold:
const listingId = 'THE_LISTING_DOCUMENT_ID';
// Fetch the existing document, modify it, and bump the revision
const existing = await sdk.documents.get(contractId, 'listing', listingId);
existing.properties = { ...existing.properties, status: 'sold' };
existing.revision = (existing.revision ?? 0n) + 1n;
await sdk.documents.replace({ document: existing, identityKey, signer });
console.log('Listing marked as sold');
Step 6: Leave a review
// Set up buyer signing
const buyerIdentity = await sdk.identities.fetch(buyerIdentityId);
const buyerKey = buyerIdentity.publicKeys[0];
const buyerSigner = new IdentitySigner();
buyerSigner.addKeyFromWif(buyerKeyWif);
const reviewDoc = new Document({
documentTypeName: 'review',
dataContractId: new Identifier(contractId),
ownerId: new Identifier(buyerIdentityId),
properties: {
sellerId: 'SELLER_IDENTITY_ID',
listingId: 'THE_LISTING_DOCUMENT_ID',
rating: 5,
comment: 'Great seller, car was exactly as described!',
},
});
await sdk.documents.create({ document: reviewDoc, identityKey: buyerKey, signer: buyerSigner });
Query reviews for a seller
const reviews = await sdk.documents.query({
dataContractId: contractId,
documentTypeName: 'review',
where: [['sellerId', '==', 'SELLER_IDENTITY_ID']],
orderBy: [['rating', 'desc']],
limit: 50,
});
let totalRating = 0;
let count = 0;
for (const [, doc] of reviews) {
if (!doc) continue;
const props = doc.properties as Record<string, unknown>;
totalRating += props.rating as number;
count++;
}
console.log(`Average rating: ${(totalRating / count).toFixed(1)} (${count} reviews)`);
Next steps
- Add indexes to the contract schema for efficient queries on
make,year, andpriceUsd - Add a
locationfield and query by region - Use document pricing (
sdk.documents.setPrice/sdk.documents.purchase) to let buyers pay for premium listing details - Integrate with a frontend framework (React, Vue, etc.) for a full web app