Skip to main content

Build a Calculator Application

In this tutorial, we will build a simple Calculator application to illustrate how requests are sent and processed within Cartesi Rollups Infrastructure.

We provide JavaScript, Python, Rust, Go, and C++ implementations so you can use your preferred backend language.

Set up your environment

Install these to set up your environment for quick building:

Create the backend application

To create a calculator backend, run the template command for your language:

cartesi create calculator --template javascript

This creates a calculator/ directory with all required files. Your backend entry point depends on language (src/index.js, dapp.py, src/main.rs, main.go, or src/main.cpp).

Review the default backend template

Before implementing the calculator logic, review the default rollup request loop generated by the templates:

const rollup_server = process.env.ROLLUP_HTTP_SERVER_URL;
console.log("HTTP rollup_server url is " + rollup_server);

async function handle_advance(data) {
console.log("Received advance request data " + JSON.stringify(data));
return "accept";
}

async function handle_inspect(data) {
console.log("Received inspect request data " + JSON.stringify(data));
return "accept";
}

var handlers = {
advance_state: handle_advance,
inspect_state: handle_inspect,
};

var finish = { status: "accept" };

(async () => {
while (true) {
const finish_req = await fetch(rollup_server + "/finish", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ status: "accept" }),
});

console.log("Received finish status " + finish_req.status);

if (finish_req.status == 202) {
console.log("No pending rollup request, trying again");
} else {
const rollup_req = await finish_req.json();
var handler = handlers[rollup_req["request_type"]];
finish["status"] = await handler(rollup_req["data"]);
}
}
})();

Build the backend application

For this tutorial, we parse math expressions from advance payloads and emit calculation results as notices. Install the minimal dependencies for your language:

npm add expr-eval

For example, an advance request to the backend with payload “1+2” should emit a notice with the response “3”.

Implement the application logic

Copy the snippet for your language and replace the contents of your local backend entry point file:

import { Parser } from "expr-eval";

const rollup_server = process.env.ROLLUP_HTTP_SERVER_URL;
console.log("HTTP rollup_server url is " + rollup_server);

const parser = new Parser();

function hex2str(hex) {
const normalized = hex.startsWith("0x") ? hex.slice(2) : hex;
return Buffer.from(normalized, "hex").toString("utf8");
}

function str2hex(text) {
return "0x" + Buffer.from(text, "utf8").toString("hex");
}

async function emitNotice(payload) {
await fetch(rollup_server + "/notice", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ payload }),
});
}

async function emitReport(payload) {
await fetch(rollup_server + "/report", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ payload }),
});
}

async function handle_advance(data) {
console.log("Received advance request data " + JSON.stringify(data));
try {
const expression = hex2str(data.payload);
const result = parser.evaluate(expression);
await emitNotice(str2hex(String(result)));
return "accept";
} catch (error) {
await emitReport(str2hex(`Error processing input: ${error}`));
return "reject";
}
}

async function handle_inspect(data) {
console.log("Received inspect request data " + JSON.stringify(data));
await emitReport(data.payload);
return "accept";
}

const handlers = {
advance_state: handle_advance,
inspect_state: handle_inspect,
};

const finish = { status: "accept" };

(async () => {
while (true) {
const finish_req = await fetch(rollup_server + "/finish", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status: finish.status }),
});

if (finish_req.status === 202) {
console.log("No pending rollup request, trying again");
continue;
}

const rollup_req = await finish_req.json();
const handler = handlers[rollup_req.request_type];
finish.status = await handler(rollup_req.data);
}
})();

With Docker running, “build your backend” application by running:

cartesi build

“Building” in this context installs the libraries in the requirements.txt, compiles your application into RISC-V architecture, and consequently builds a Cartesi machine that contains your backend application.

The anvil node can now run your application.

To run your application, enter the command:

cartesi run

Sending inputs with the CLI

We can send inputs to your application with a custom JavaScript frontend, Cast, or Cartesi CLI.

To send a string encoded input to your application, run the below command:

cartesi send "1+2"

Example: Send 1+2 as an input to the application.

(base) user@user-MacBook-Pro calculator % cartesi send "1 + 2"
(node:64729) ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
✔ Input sent: 0x1866ab903f8fa5bd712fc2d322b8c0ba119bb31d30885a06e0fe6005ff079ff2

Retrieving outputs from the application

The cartesi send generic sends a request to the calculator application to process the computation (1 + 2), this is computed and a payload (notice) containing the result is sent to the Rollup Server's /notice endpoint.

querying notices

Notice payloads will be returned in hexadecimal format; developers will need to decode these to convert them into plain text.

We can query these notices using the JSON-RPC server running on http://127.0.0.1:6751/rpc or with a custom frontend client.

You can retrieve all notices sent to the rollup server by executing this request on a terminal:

curl -X POST http://127.0.0.1:6751/rpc \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "cartesi_listOutputs",
"params": {
"application": "calculator",
"limit": 10,
"offset": 0
},
"id": 1
}'

Alternatively you can make a post request to the JsonRPC server like below:

const response = await fetch("http://127.0.0.1:6751/rpc", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
method: "cartesi_listOutputs",
params: {
application: "calculator",
limit: 10,
offset: 0
},
id: 1
})
});

const result = await response.json();

const outputs = result?.result?.data ?? [];

for (const output of outputs) {
const decoded = output.decoded_data;
console.log("Output payload:", decoded);
}

You can also query a notice based on its input index.

Congratulations, you have successfully built a dApp on Cartesi Rollups!

Repo Link

You can access the complete project implementation here!

We use cookies to ensure that we give you the best experience on our website. By using the website, you agree to the use of cookies.