Getting Started
Overview
This tutorial will guide you on how to set up a Machine using the mintBlue SDK. You will set up a new project directory, install the SDK, and create a small demo Machine tracking the plantation of trees to offset carbon emissions. Let's get green 🌲
Prerequisites
- ✅ Understand the Machine Key Concepts
- ✅ Create an SDK Access Token (see mintBlue Quick Start Guide)
- ✅ Create a new project and copy your project ID
If you don't want to copy code, you can run the example explained in this tutorial by cloning the mintBlue SDK and running the example.
git clone https://gitlab.com/mintBlue/sdk.git
cd sdk
npm install
npm run build
cd examples/machine
Make sure to replace the sdkToken
and project_id
variables in the main.ts
file with your own SDK Access Token and project ID. Then, run the example with the following command:
npm run example-cli
You can now interact with the example.
Step 1: Create your project directory
Open your terminal and create a directory called forest-app
. After you create the project directory, navigate to the directory by running the following command:
mkdir forest-app && cd forest-app
Initialize a Node.js project in this new directory by running the following command:
npm init -y
Install the mintBlue SDK with NPM.
npm install @mintblue/sdk
Next, let's create two files. The first file main.ts
holds the Machine logic while the second file forest.ts
holds the application logic.
touch main.ts && touch forest.ts
And finally, let's add some skeleton code to our main.js
file. Make sure to replace the placeholder <YOUR-SDK-TOKEN>
with your SDK Access Token and <YOUR-PROJECT-ID>
with your project ID.
import { Machine, MemoryRecorder, MintblueReader, MintblueWriter } from "@mintblue/sdk";
import { Forest } from "./forest.js";
async function main() {
const sdkToken = "<YOUR-SDK-TOKEN>";
const project_id = "<YOUR-PROJECT-ID>";
}
main();
Step 2: Create the forest application logic
The App represents a class that contains the logic for your application interacting with a blockchain. Each App maintains a state and includes one or multiple functions that modify the state.
It's time to implement the business logic for our tree-planting application. Open your forest.js
file, and import the App
object from the mintBlue SDK to create our application logic.
import { App } from "@mintblue/sdk";
Now, we can add the Forest
class that implements the SDK App
class. It expects an AppState
called state
. The state object contains our planted trees and an estimation of the carbon we've offset.
import { App } from "@mintblue/sdk";
// The interface for the state of the Forest app.
interface ForestState {
trees: { location: string; info?: Record<string, any> }[];
estimatedCarbonOffset: number;
}
export class Forest implements App {
state: ForestState = {
trees: [],
estimatedCarbonOffset: 0,
};
}
The plantTrees
function expects a location and the amount of trees we've planted. Notice the _ctx
context argument, a mandatory argument containing the application context. If a public function modifies the state, it requires the _ctx
argument as the first argument.
import { App } from "@mintblue/sdk";
// The interface for the state of the Forest app.
interface ForestState {
trees: { location: string; info?: Record<string, any> }[];
estimatedCarbonOffset: number;
}
export class Forest implements App {
state: ForestState = {
trees: [],
estimatedCarbonOffset: 0,
};
plantTrees(_ctx: Context, location: string, amount: number) {
for (let i = 0; i < amount; i++) {
this.state.trees.push({
location: location,
});
}
this.state.estimatedCarbonOffset = this._estimateCarbonOffset();
}
private _estimateCarbonOffset() {
return this.state.trees.length * 42;
}
}
When you plant a new tree, we automatically update the estimated carbon offset in the state with the result of a private function. This function doesn't require the _ctx
because it doesn't directly modify the state.
Step 3: Create a new Forest Machine
The forest application logic is ready to create our forest Machine
.
The Forest
class has already been imported. We must create a new MemoryRecorder and our blockchain Reader
and Writer
. Besides that, we tell the machine
to emit application events with the same name as our app function and send the latest snapshot.
import { Machine, MemoryRecorder, MintblueReader, MintblueWriter } from "@mintblue/sdk";
import { Forest } from "./forest.js";
async function main() {
const sdkToken = "<YOUR-SDK-TOKEN>";
const project_id = "<YOUR-PROJECT-ID>";
const recorder = new MemoryRecorder();
const machine = new Machine(
Forest,
await MintblueReader.create(sdkToken, project_id),
await MintblueWriter.create(sdkToken, project_id),
recorder,
{ emitAppEvents: true }
);
const app = machine.getInstance();
}
main();
And finally, the getInstance()
method lets us get an instance of our machine that we can use in the next step to interact with. The app
object represents a proxied version of the application logic where our plantTrees()
method is wrapped to write actions to the action storage automatically. In this example, the action storage is a blockchain, but it can also be an alternative storage solution like an S3 bucket.
When we call a function on the app object, like plantTrees()
, it won't execute it. It will write the function name and arguments to the blockchain.
When the MintblueReader
receives an event from the mintBlue platform, the plantTrees()
function is executed, and the application state is updated.
📚 If you want to learn more about this software design pattern, it's called Command-Query Responsibility Segregation (CQRS) in Event Sourcing.
Step 4: Interact with the Forest App
Let's have some fun with our Forest application 🥳 We would like to add an event handler to listen for freshly planted trees, plant a new tree in Brussels, and start dispatching events from the Reader to update our application state.
Listen for "plantTrees" events
First, let's listen for plantTrees
evens. You can add a new event listener to the machine object using the .on
keyword and provide it with the function name you want to listen for. Make sure the function exists in your AppLogic, like our plantTrees
function.
To verify our state has been updated, let's log the application state by accessing the state
property of the app
object.
machine.on("plantTrees", (snapshot) => {
console.log("A tree was planted");
console.log(`Location: ${snapshot.action.args[0]}`);
console.log(app.state);
});
Plant three new trees in Brussels
Now, let's plant a new tree by calling our application instance. Remember, we are not executing the function in the AppLogic. We are sending the function name and arguments to the mintBlue platform to be recorded on the blockchain.
console.log("Planting 🌲");
await app.plantTrees("Brussels", 3);
console.log(app.state);
Let's log the application state here. You'll find an empty state because the state is only updated when our Reader receives an event.
{ "trees": [], "estimatedCarbonOffset": 0 }
Therefore, let's start our Reader
in the next step.
Start dispatching Reader events
Let's start dispatching events by calling the start()
function on the machine object. It instructs the Reader
to begin dispatching events, like our plantTrees
events.
machine.start();
When this function is called, we expect the event listener to print the text A tree was planted
with its location and update the state by executing our plantTrees
function. This will update the state. You should see the following state, including three planted trees in Brussels.
{
"trees": [
{ "location": "Brussels" },
{ "location": "Brussels" },
{ "location": "Brussels" }
],
"estimatedCarbonOffset": 126
}
Below, you can find the full code implementation.
Code Check ✅
- main.ts
- forest.js
import { Machine, MemoryRecorder, MintblueReader, MintblueWriter } from "@mintblue/sdk";
import { Forest } from "./forest.js";
async function main() {
const sdkToken = "<YOUR-SDK-TOKEN>";
const project_id = "<YOUR-PROJECT-ID>";
const recorder = new MemoryRecorder();
const machine = new Machine(
Forest,
await MintblueReader.create(sdkToken, project_id),
await MintblueWriter.create(sdkToken, project_id),
recorder,
{ emitAppEvents: true }
);
const app = machine.getInstance();
machine.on("plantTrees", (snapshot) => {
console.log("A tree was planted");
console.log(`Location: ${snapshot.action.args[0]}`);
console.log(app.state);
});
console.log("Planting 🌲");
await app.plantTrees("Brussels", 3);
console.log(app.state);
machine.start();
}
main();
import { App } from "@mintblue/sdk";
/**
* Represents a Forest application that keeps track of trees planted
* and their estimated carbon offset.
*/
export class Forest extends App {
/**
* Represents the current state of the forest, containing information
* about the trees and the estimated carbon offset.
*/
state = {
trees: [],
estimatedCarbonOffset: 0,
};
/**
* Plant trees.
* @param _ctx - The application context.
* @param location - The location where the trees are planted.
* @param amount - The number of trees to plant.
*/
plantTrees(_ctx, location, amount) {
for (let i = 0; i < amount; i++) {
this.state.trees.push({
location: location,
});
}
this.state.estimatedCarbonOffset = this._estimateCarbonOffset();
}
/**
* Private function to calculate the estimated carbon offset based
* on the number of trees in the forest.
*/
_estimateCarbonOffset() {
return this.state.trees.length * 42;
}
}