ajcwebdev
Blog post cover art for A First Look at Dashmate

A First Look at Dashmate

Published:

A beginner friendly tutorial for running a local version of Dash Platform with Dashmate. Includes creating an identity, data contract, and documents.

Outline

Overview

Dashmate is a software tool designed to simplify the process of setting up and managing Dash masternodes. Key features include:

  1. Easy setup: It automates much of the complex process of setting up a Dash masternode.
  2. Management: Dashmate provides a user-friendly interface for monitoring and managing masternodes.
  3. Updates: It helps users keep their masternodes up-to-date with the latest software versions.
  4. Configuration: The tool allows for easy configuration of masternode settings.
  5. Multiple node support: Users can manage multiple masternodes from a single interface.
  6. Security: Dashmate implements best practices for securing masternodes.

By simplifying these technical aspects, Dashmate makes it more accessible for individuals to participate in the Dash network.

Prerequisites

  • Node.js v20+
  • Docker (latest)
  • Dashmate installed

Install Dashmate

Terminal window
npm i -g dashmate@v1.0.0-dev.16

See all available dashmate commands with dashmate --help.

Setup and Start Dashmate Local Network

To avoid problems with variable network speeds and other challenges related to interacting with testnet, Dashmate makes it (mostly) easy to create a local development network. The network is only accessible to your computer since the nodes are exclusively running locally on your machine.

Setup Group of Local Nodes

Local networks can be set up with Dashmate via the dashmate setup local command. This command sets up a full local Dash network environment on your machine for development purposes by creating a group of nodes that simulate a small network running the actual Dash network.

Terminal window
dashmate setup local

Other setup commands can configure nodes to connect to existing Dash networks including dashmate setup mainnet or dashmate setup testnet. The local preset creates an isolated, self-contained network on your machine whereas mainnet and testnet presets connect to public Dash networks. With the local setup, developers have more control over the network parameters and can easily reset or modify the environment.

The local setup includes features that are not typically part of mainnet or testnet setups:

  • Can create multiple nodes as part of a group while other setups typically configure a single node.
  • Generates configs, mines some test Dash (tDash), registers masternodes, and populates nodes with data required for local development.
  • Allows setting a miner interval between blocks using the -m or --miner-interval flag.

All commands related to interacting or manipulating your group of nodes will start with dashmate group .... Every time you reset the network, the entire transaction history of the chain is also dropped.

Start Local Group and Check Network Status

Run the group of nodes with dashmate group start.

Terminal window
dashmate group start

Check the status of your network with dashmate group status.

Terminal window
dashmate group status

You can also check everything by hand by using docker ps.

  • Check to see if everything is in the running state.
  • If something is not running, check the logs via docker logs.

Always ensure the health of your local network before you start. Laptops will be more prone to chain halts and network crashes since many include automatic sleep timers that reduce computing resources for power conservation.

Stop or Restart Group

Various commands exist to provide different levels of “resetting” your node group. These range from simply stopping to completely wiping and starting over. Choosing the appropriate command will be based on how much you want to reset including whether you want to preserve configurations. Here’s a high level description of each method, followed by a more detailed explanation.

  • dashmate group:stop --force only stops the nodes.
  • dashmate group restart restarts the nodes without changing data or configuration.
  • dashmate group reset --force resets the data but keeps configurations.
  • dashmate group reset --hard --force performs a complete reset, removing both data and configurations.

dashmate group:stop --force forcefully stops all nodes in the current group. --force ensures the stop operation is carried out even if services are still running or have any issues.

Terminal window
dashmate group:stop --force

Use Case: Quickly shut down all nodes in a group regardless of their current state.

dashmate group restart restarts all nodes in the current group, essentially equivalent to stopping the group and then starting it again.

Terminal window
dashmate group restart

Use Case: Apply configuration changes or refresh the state of all nodes in the group.

Reset Group

dashmate group reset --force resets all nodes in the current group, forcefully removing all data associated with them. The --force flag ensures the reset happens even if services are running. All services are stopped, all data for each node in the group is removed, and configuration files are kept intact.

Terminal window
dashmate group reset --force

Use Case: Start fresh with your node data while keeping your existing configuration.

dashmate group reset --hard --force is the most aggressive reset command. The --hard flag in addition to --force means it will stop all services forcefully by removing all data for each node in the group plus removing and resetting all configuration files.

Terminal window
dashmate group reset --hard --force

Use Case: You want a completely clean state for your group as if it had never been set up. To use the group, the setup process must be run again.

Setup JavaScript Project

Create a new Node.js project and set type to module for ESM imports and exports.

Terminal window
mkdir dashmate-example
cd dashmate-example
npm init -y
npm pkg set type="module"
npm i dash@4.0.0-dev.16

Set scripts:

Terminal window
npm pkg set \
'scripts.createWallet=node --no-warnings createWallet.js' \
'scripts.registerIdentity=node --env-file=.env --no-warnings registerIdentity.js' \
'scripts.createContract=node --env-file=.env --no-warnings createContract.js' \
'scripts.submitDocument=node --env-file=.env --no-warnings submitDocument.js' \
'scripts.getDocument=node --env-file=.env --no-warnings getDocument.js'

Create Wallet

Create a function for generating a new wallet and a function for getting the next unused address.

Terminal window
echo > createWallet.js

createWallet creates a new wallet and console logs your new mnemonic and address.

createWallet.js
import Dash from "dash"
const client = new Dash.Client({
network: "local",
dapiAddress: [ "http://127.0.0.1:3001" ],
wallet: {
mnemonic: null,
offlineMode: true
},
})
async function createWallet() {
try {
const account = await client.getWalletAccount()
const mnemonic = client.wallet.exportWallet()
const { address } = account.getUnusedAddress()
console.log("WALLET_ADDRESS=" + `"${address}"`)
console.log("MNEMONIC=" + `"${mnemonic}"`)
} catch (error) {
console.error('Something went wrong:\n', error)
} finally {
client.disconnect()
}
}
createWallet()
Terminal window
npm run createWallet

Create Client File and Set Environment Variables

Add the following to .env with the mnemonic and wallet address from the previous command.

Terminal window
NETWORK="local"
DAPI_ADDRESS_URL="http://127.0.0.1:3001"
WALLET_ADDRESS=""
MNEMONIC=""

Create a client.js file for initializing and exporting a reusable Dash client.

Terminal window
echo > client.js

Import Dash and set the network, dapiAddress, and MNEMONIC options.

client.js
import Dash from "dash"
const { NETWORK, DAPI_ADDRESS_URL, MNEMONIC } = process.env
export const client = new Dash.Client({
network: NETWORK,
dapiAddress: [ DAPI_ADDRESS_URL ],
wallet: {
mnemonic: MNEMONIC,
},
})

This connects to the DAPI endpoint of the local Dash network and syncs up your wallet.

Mint Local tDash with Wallet Command

Lets mint some coins with our new wallet address. To do that, you must stop your network (dashmate group:stop --force), issue the dashmate wallet mint command with your address, and start your network again (dashmate group start):

Terminal window
dashmate group:stop --force
dashmate wallet mint --config local_seed --address=yVyQx9D9pAsmtv1H3yd2Y8jFQpi1WkCpCF 50
dashmate group start

Output:

✔ Generate 10 dash to address
✔ Start Core
✔ Create a new address
› Address: yYFFb4MQmRoxx3DitnKHV6iYN1aVGqfJja
Private key: cT9vMyFtCzAsnSnMq9WemG7S3mfiTDoY4LW8QJRmFqUKPr8uen2S
✔ Generate ≈10 dash to address yYFFb4MQmRoxx3DitnKHV6iYN1aVGqfJja
› Generated 10.2652105 dash
✔ Wait for balance to confirm
✔ Stop Core

Register Identity

Registering an identity creates a unique account in the Dash Platform network. You must register an identity before you can use any Dash Platform features.

Terminal window
echo > registerIdentity.js
registerIdentity.js
import { client } from "./client.js"
async function registerIdentity() {
try {
const identity = await client.platform.identities.register()
console.log(`IDENTITY_ID="${identity.getId().toString()}"`)
} catch (error) {
console.error('Something went wrong:\n', error)
} finally {
client.disconnect()
}
}
registerIdentity()
Terminal window
npm run registerIdentity

Create Data Contract

A Data Contract is a data type definition for your application that is like a schema for a database application that lets you store and query data.

Terminal window
echo > createContract.js

The contractDefinitions includes an object type with one property, a message set to type string.

createContract.js
import { client } from "./client.js"
const { IDENTITY_ID } = process.env
const createContract = async () => {
try {
const identity = await client.platform.identities.get(IDENTITY_ID)
const contractDefinitions = {
note: {
type: 'object',
properties: {
message: { type: "string", position: 0 }
},
additionalProperties: false
}
}
const contract = await client.platform.contracts.create(contractDefinitions, identity)
await client.platform.contracts.publish(contract, identity)
console.log("\nCONTRACT_ID=" + `"${contract.toJSON().id}"`)
console.log('\nContract registered:\n\n', contract.toJSON())
} catch (e) {
console.error('Something went wrong:\n', e)
} finally {
client.disconnect()
}
}
createContract()
Terminal window
npm run createContract

Once the schema has been created and we have our data contract, we can being storing our data in the chain. We can write and read data in the designed object format.

Submit and Query Documents

To insert documents in the data contract, we must define it in our Dash.Client options:

client.js
import Dash from "dash"
const { NETWORK, DAPI_ADDRESS_URL, MNEMONIC, CONTRACT_ID } = process.env
export const client = new Dash.Client({
network: NETWORK,
dapiAddress: [ DAPI_ADDRESS_URL ],
wallet: {
mnemonic: MNEMONIC,
},
apps: {
helloWorld: {
contractId: CONTRACT_ID,
},
},
})

Create a submitDocument.js file.

Terminal window
echo > submitDocument.js

To ensure your identity has the necessary credits to create the document, we’ll top up our identity before broadcasting our state change.

submitDocument.js
import { client } from "./client.js"
const { IDENTITY_ID } = process.env
const submitDocument = async () => {
try {
await client.platform.identities.topUp(IDENTITY_ID, 100000000)
const identity = await client.platform.identities.get(IDENTITY_ID)
const helloWorldDocument = await client.platform.documents.create(
'helloWorld.note',
identity,
{ message: 'Hello from Dash local network'}
)
await client.platform.documents.broadcast(
{
create: [helloWorldDocument],
replace: [],
delete: [],
},
identity
)
console.log(`DOCUMENT_ID="${helloWorldDocument.toJSON().$id}"`)
console.log(helloWorldDocument.toJSON())
} catch (e) {
console.error('Something went wrong:\n', e)
} finally {
client.disconnect()
}
}
submitDocument()
Terminal window
npm run submitDocument

Our data is now in Dash Platform’s global state and using GroveDB we can make queries and retrieve our data. Create a getDocument.js file.

Terminal window
echo > getDocument.js

Use the helloWorld.note query syntax to get all documents.

getDocuments.js
import { client } from "./client.js"
const getDocuments = async () => {
try {
const documents = await client.platform.documents.get(
'helloWorld.note',
{ limit: null }
)
documents.forEach(n => console.log(n.toJSON().message))
} catch (e) {
console.error('Something went wrong:\n', e)
} finally {
client.disconnect()
}
}
getDocuments()
Terminal window
npm run getDocument