Creating an Application
Cartesi CLI simplifies creating applications on Cartesi. To create a new application, run:
cartesi create <application-name> --template <language>
For example, create a Javascript project.
cartesi create new-app --template javascript
This command creates a new-app directory with essential files for your application development.
-
Dockerfile: Contains configurations to build a complete Cartesi machine with your app's dependencies. Your backend code will run in this environment. -
README.md: A markdown file with basic information and instructions about your application. -
src/index.js: A javascript file with template backend code that serves as your application's entry point. -
package.json: A list of dependencies required for your application along with the name, version and description of your application.
Cartesi CLI has templates for the following languages – cpp, cpp-low-level, go, java, javascript, lua, python, ruby, rust, and typescript.
We have high-level framework and alternative templates that simplify development and enhances input management, providing a smoother and more efficient experience. For Go use Rollmelette, for Rust use Crabrolls, for Python use python-Cartesi and for Typescript/Javascrips use Deroll. Visit this page to learn more about these and other available tools.
Implementing your application Logic
After creating your application, you can begin building your application by adding your logic to the index.js file. This file serves as the entry point of your application. While your project can include multiple files and directories, the default application file should remain the entry point of your application.
It’s important not to modify or remove existing code in index.js unless you fully understand its purpose, as doing so may prevent your application from functioning correctly. Instead, you are encouraged to extend the file by adding your own logic and implementations alongside the default code.
The default application template includes two primary functions; handle_advance and handle_inspect. These act as entry points for different types of operations within your application.
handle_advance function
The handle_advance function is the entry point for state modifying logic, you can think of this like handling "write" requests in traditional web context. It is intended to carry out computations, updates, and other logic that changes the state of the application. Where appropriate, it can emit outputs such as notices, vouchers, or reports.
handle_inspect function
On the other hand, the handle_inspect function serves as the entry point for read only operations, similar to "read" requests in a typical web context. This function should be implemented to accept user input, perform any necessary lookups or calculations based on the current state, and return the results by emitting a report. It's important to understand that handle_inspect is designed strictly for reading the application's state, it should not perform any modifications.
Implementing Outputs
If your application needs to emit Outputs like; notices, vouchers, or reports, make sure to implement the corresponding logic within your codebase to properly handle these outputs. You can check out the respective pages for Notice, Vouchers or Report for better understanding of what they are and how to implement them.
Below is a sample application that has been modified to include the logic to simply receive an input from a user in both inspect and advance route then, emits a notice, voucher and a report. For your application you'll need to include your personal logic and also emit outputs when necessary:
- JavaScript
- Python
- Rust
import { stringToHex, encodeFunctionData, erc20Abi, hexToString, zeroHash } from "viem";
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));
const sender = data["metadata"]["msg_sender"];
const payload = hexToString(data.payload);
const erc20Token = "0x784f0c076CC55EAD0a585a9A13e57c467c91Dc3a"; // Sample ERC20 token address
await emitNotice(payload);
await emitReport(payload);
const call = encodeFunctionData({
abi: erc20Abi,
functionName: "transfer",
args: [sender, BigInt(10)],
});
let voucher = {
destination: erc20Token,
payload: call,
value: zeroHash,
};
await emitVoucher(voucher);
return "accept";
}
async function handle_inspect(data) {
console.log("Received inspect request data " + JSON.stringify(data));
const payload = data.payload;
await emitReport(payload);
return "accept";
}
const emitNotice = async (inputPayload) => {
let hexPayload = stringToHex(inputPayload);
try {
await fetch(rollup_server + "/notice", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ payload: hexPayload }),
});
} catch (error) {
//Do something when there is an error
}
}
const emitVoucher = async (voucher) => {
try {
await fetch(rollup_server + "/voucher", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(voucher),
});
} catch (error) {
//Do something when there is an error
}
};
const emitReport = async (payload) => {
let hexPayload = stringToHex(payload);
try {
await fetch(rollup_server + "/report", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ payload: hexPayload }),
});
} catch (error) {
//Do something when there is an error
}
};
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"]);
}
}
})();
from os import environ
import logging
import requests
import binascii
import json
from eth_utils import function_signature_to_4byte_selector
from eth_abi import encode
logging.basicConfig(level="INFO")
logger = logging.getLogger(__name__)
rollup_server = environ["ROLLUP_HTTP_SERVER_URL"]
logger.info(f"HTTP rollup_server url is {rollup_server}")
def handle_advance(data):
logger.info(f"Received advance request data {data}")
sender = data["metadata"]["msg_sender"]
app_contract = data["metadata"]["app_contract"]
payload = data["payload"]
erc20Token = "0x784f0c076CC55EAD0a585a9A13e57c467c91Dc3a"; # Sample ERC20 token address
emitNotice(payload);
emitReport(payload);
voucher = structure_voucher(
"transferFrom(address,uint256)",
erc20Token,
["address", "uint256"],
[sender, 10],
)
emitVoucher(voucher);
return "accept"
def handle_inspect(data):
logger.info(f"Received inspect request data {data}")
return "accept"
def structure_voucher(function_signature, destination, types, values, value=0) -> dict:
selector = function_signature_to_4byte_selector(function_signature)
encoded_args = encode(types, values)
payload = "0x" + (selector + encoded_args).hex()
return {
"destination": destination,
"payload": payload,
"value": hex(value)
}
def string_to_hex(s: str) -> str:
return "0x" + binascii.hexlify(s.encode("utf-8")).decode()
def hex_to_string(hexstr: str) -> str:
if not isinstance(hexstr, str):
return ""
if hexstr.startswith("0x"):
hexstr = hexstr[2:]
if hexstr == "":
return ""
try:
return binascii.unhexlify(hexstr).decode("utf-8")
except UnicodeDecodeError:
return "0x" + hexstr
def emitReport(payload: str):
hex_payload = string_to_hex(payload)
try:
response = requests.post(
f"{rollup_server}/report",
json={"payload": hex_payload},
headers={"Content-Type": "application/json"},
timeout=5,
)
logger.info(f"emit_report → status {response.status_code}")
except requests.RequestException as error:
logger.error("Error emitting report: %s", error)
def emitVoucher(voucher: dict):
try:
response = requests.post(
f"{rollup_server}/voucher",
json= voucher,
headers={"Content-Type": "application/json"},
timeout=5,
)
logger.info(f"emit_voucher → status {response.status_code}")
except requests.RequestException as error:
logger.error("Error emitting voucher: %s", error)
def emitNotice(payload: str):
hex_payload = string_to_hex(payload)
try:
response = requests.post(
f"{rollup_server}/notice",
json={"payload": hex_payload},
headers={"Content-Type": "application/json"},
timeout=5,
)
logger.info(f"emit_notice → status {response.status_code}")
except requests.RequestException as error:
logger.error("Error emitting notice: %s", error)
handlers = {
"advance_state": handle_advance,
"inspect_state": handle_inspect,
}
finish = {"status": "accept"}
while True:
logger.info("Sending finish")
response = requests.post(rollup_server + "/finish", json=finish)
logger.info(f"Received finish status {response.status_code}")
if response.status_code == 202:
logger.info("No pending rollup request, trying again")
else:
rollup_request = response.json()
data = rollup_request["data"]
handler = handlers[rollup_request["request_type"]]
finish["status"] = handler(rollup_request["data"])
use json::{object, JsonValue};
use std::env;
use ethers_core::abi::{encode, Token};
use ethers_core::utils::id;
use hex;
use serde::Serialize;
fn structure_voucher(
function_signature: &str,
destination: &str,
args: Vec<Token>,
value: u128,
) -> JsonValue {
let selector = &id(function_signature)[..4];
let encoded_args = encode(&args);
let mut payload_bytes = Vec::new();
payload_bytes.extend_from_slice(selector);
payload_bytes.extend_from_slice(&encoded_args);
let payload = format!("0x{}", hex::encode(payload_bytes));
let response = object! {
"destination" => format!("{}", destination),
"payload" => format!("{}", payload),
"value" => format!("0x{}", hex::encode(value.to_be_bytes())),
};
return response;
}
pub async fn handle_advance(
_client: &hyper::Client<hyper::client::HttpConnector>,
_server_addr: &str,
request: JsonValue,
) -> Result<&'static str, Box<dyn std::error::Error>> {
println!("Received advance request data {}", &request);
let payload = request["data"]["payload"]
.as_str()
.ok_or("Missing payload")?;
let sender = request["data"]["metadata"]["msg_sender"]
.as_str()
.ok_or("Missing msg_sender in metadata")?
.to_string();
const erc_20_token: &str = "0x784f0c076CC55EAD0a585a9A13e57c467c91Dc3a"; // Sample ERC20 token address
emit_notice(payload.to_string()).await;
emit_report(payload.to_string()).await;
let amount = 10;
let args = vec![
Token::Address(sender.parse().unwrap()),
Token::Uint(amount.into()),
];
let function_sig = "transfer(address,uint256)";
let voucher = structure_voucher(function_sig, &erc_20_token, args, 0);
emit_voucher(voucher).await;
Ok("accept")
}
pub async fn handle_inspect(
_client: &hyper::Client<hyper::client::HttpConnector>,
_server_addr: &str,
request: JsonValue,
) -> Result<&'static str, Box<dyn std::error::Error>> {
println!("Received inspect request data {}", &request);
let _payload = request["data"]["payload"]
.as_str()
.ok_or("Missing payload")?;
// TODO: add application logic here
Ok("accept")
}
async fn emit_report( payload: String) -> Option<bool> {
let hex_string = {
let s = payload.strip_prefix("0x").unwrap_or(payload.as_str());
hex::encode(s.as_bytes())
};
let server_addr = env::var("ROLLUP_HTTP_SERVER_URL").expect("ROLLUP_HTTP_SERVER_URL not set");
let client = hyper::Client::new();
let response = object! {
"payload" => format!("0x{}", hex_string),
};
let request = hyper::Request::builder()
.method(hyper::Method::POST)
.header(hyper::header::CONTENT_TYPE, "application/json")
.uri(format!("{}/report", server_addr))
.body(hyper::Body::from(response.dump()))
.ok()?;
let response = client.request(request).await;
match response {
Ok(_) => {
println!("Report generation successful");
return Some(true);
}
Err(e) => {
println!("Report request failed {}", e);
None
}
}
}
async fn emit_notice( payload: String) -> Option<bool> {
let hex_string = {
let s = payload.strip_prefix("0x").unwrap_or(payload.as_str());
hex::encode(s.as_bytes())
};
let server_addr = env::var("ROLLUP_HTTP_SERVER_URL").expect("ROLLUP_HTTP_SERVER_URL not set");
let client = hyper::Client::new();
let response = object! {
"payload" => format!("0x{}", hex_string),
};
let request = hyper::Request::builder()
.method(hyper::Method::POST)
.header(hyper::header::CONTENT_TYPE, "application/json")
.uri(format!("{}/notice", server_addr))
.body(hyper::Body::from(response.dump()))
.ok()?;
let response = client.request(request).await;
match response {
Ok(_) => {
println!("Notice generation successful");
return Some(true);
}
Err(e) => {
println!("Notice request failed {}", e);
None
}
}
}
async fn emit_voucher( voucher: JsonValue) -> Option<bool> {
let server_addr = env::var("ROLLUP_HTTP_SERVER_URL").expect("ROLLUP_HTTP_SERVER_URL not set");
let client = hyper::Client::new();
let request = hyper::Request::builder()
.method(hyper::Method::POST)
.header(hyper::header::CONTENT_TYPE, "application/json")
.uri(format!("{}/voucher", server_addr))
.body(hyper::Body::from(voucher.dump()))
.ok()?;
let response = client.request(request).await;
match response {
Ok(_) => {
println!("Voucher generation successful");
return Some(true);
}
Err(e) => {
println!("Voucher request failed {}", e);
None
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = hyper::Client::new();
let server_addr = env::var("ROLLUP_HTTP_SERVER_URL")?;
let mut status = "accept";
loop {
println!("Sending finish");
let response = object! {"status" => status};
let request = hyper::Request::builder()
.method(hyper::Method::POST)
.header(hyper::header::CONTENT_TYPE, "application/json")
.uri(format!("{}/finish", &server_addr))
.body(hyper::Body::from(response.dump()))?;
let response = client.request(request).await?;
println!("Received finish status {}", response.status());
if response.status() == hyper::StatusCode::ACCEPTED {
println!("No pending rollup request, trying again");
} else {
let body = hyper::body::to_bytes(response).await?;
let utf = std::str::from_utf8(&body)?;
let req = json::parse(utf)?;
let request_type = req["request_type"]
.as_str()
.ok_or("request_type is not a string")?;
status = match request_type {
"advance_state" => handle_advance(&client, &server_addr[..], req).await?,
"inspect_state" => handle_inspect(&client, &server_addr[..], req).await?,
&_ => {
eprintln!("Unknown request type");
"reject"
}
};
}
}
}