Query outputs
In Cartesi Rollups, outputs are essential in interacting with the blockchain. The direct output types are notices, reports and vouchers.
The JSON-RPC server unifies Notice and Vouchers into a single query, therefore a single request returns all Notices and Vouchers generated by the application.
Notices: Off-chain events
A notice is a verifiable data declaration that attests to off-chain events or conditions and is accompanied by proof. Unlike vouchers, notices do not trigger direct interactions with other smart contracts.
They serve as a means for application to notify the blockchain about particular events.
How Notices Work
-
The application backend creates a notice containing relevant off-chain data.
-
The notice is submitted to the Rollup Server as evidence of the off-chain event.
-
Notices are validated on-chain using the
validateOutput()function of theApplicationcontract.
Send a notice
Let's examine how an Application has its Advance request calculating and returning the first five multiples of a given number.
We will send the output to the rollup server as a notice.
- JavaScript
- Python
- Rust
- Go
- C++
function calculateMultiples(num) {
let multiples = "";
for (let i = 1; i <= 5; i++) {
multiples += (num * i).toString();
if (i < 5) {
multiples += ", ";
}
}
return multiples;
}
async function handle_advance(data) {
console.log("Received advance request data " + JSON.stringify(data));
const numberHex = data["payload"];
const number = parseInt(viem.hexToString(numberHex));
try {
const multiples = calculateMultiples(number);
console.log(`Adding notice with value ${multiples}`);
const hexresult = viem.stringToHex(multiples);
await fetch(rollup_server + "/notice", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ payload: hexresult }),
});
} catch (error) {
//Do something when there is an error
}
return "accept";
}
def hex2str(hex):
"""
Decodes a hex string into a regular string
"""
return bytes.fromhex(hex[2:]).decode("utf-8")
def str2hex(str):
"""
Encodes a string as a hex string
"""
return "0x" + str.encode("utf-8").hex()
def calculate_multiples(num):
multiples = ""
for i in range(1, 6):
multiples += str(num * i)
if i < 5:
multiples += ", "
return multiples
def handle_advance(data):
logger.info(f"Received advance request data {data}")
status = "accept"
try:
input = hex2str(data["payload"])
logger.info(f"Received input: {input}")
# Evaluates expression
multiples = calculate_multiples(int(input))
# Emits notice with result of calculation
logger.info(f"Adding notice with payload: '{multiples}'")
response = requests.post(
rollup_server + "/notice", json={"payload": str2hex(str(multiples))}
)
logger.info(
f"Received notice status {response.status_code} body {response.content}"
)
except Exception as e:
# Emits report with error message here
return status
fn calculate_multiples(num: i64) -> String {
let mut multiples = String::new();
for i in 1..=5 {
multiples.push_str(&(num * i).to_string());
if i < 5 {
multiples.push_str(", ");
}
}
multiples
}
fn hex_to_string(hex: &str) -> Result<String, Box<dyn std::error::Error>> {
let hexstr = hex.strip_prefix("0x").unwrap_or(hex);
let bytes = hex::decode(hexstr).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
let s = String::from_utf8(bytes).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
Ok(s)
}
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
}
}
}
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 number: i64 = hex_to_string(payload)?.parse()?;
let multiples = calculate_multiples(number);
println!("ADDING NOTICE WITH VALUE {}", multiples);
emit_notice(multiples).await;
Ok("accept")
}
package main
import (
"dapp/rollups"
"fmt"
"strconv"
"strings"
)
// calculateMultiples returns the first 5 multiples as: "n, 2n, 3n, 4n, 5n".
func calculateMultiples(num int64) string {
parts := make([]string, 0, 5)
for i := int64(1); i <= 5; i++ {
parts = append(parts, strconv.FormatInt(num*i, 10))
}
return strings.Join(parts, ", ")
}
// HandleAdvance parses an integer input and emits a notice with its first 5 multiples.
func HandleAdvance(data *rollups.AdvanceResponse) error {
// Payload is hex-encoded text, expected to contain a decimal integer (example: "7").
input, err := rollups.Hex2Str(data.Payload)
if err != nil {
return fmt.Errorf("HandleAdvance: invalid payload encoding: %w", err)
}
input = strings.TrimSpace(input)
value, err := strconv.ParseInt(input, 10, 64)
if err != nil {
return fmt.Errorf("HandleAdvance: invalid integer payload %q: %w", input, err)
}
// Compute result and publish as a notice payload.
result := calculateMultiples(value)
notice := rollups.NoticeRequest{Payload: rollups.Str2Hex(result)}
if _, err = rollups.SendNotice(¬ice); err != nil {
return fmt.Errorf("HandleAdvance: failed sending notice: %w", err)
}
return nil
}
#include <sstream>
#include <string>
#include "3rdparty/cpp-httplib/httplib.h"
#include "3rdparty/picojson/picojson.h"
// Build a comma-separated string with the first 5 multiples of num.
std::string calculate_multiples(long long num)
{
std::ostringstream result;
for (int i = 1; i <= 5; ++i)
{
result << (num * i);
if (i < 5)
{
result << ", ";
}
}
return result.str();
}
std::string handle_advance(httplib::Client &cli, picojson::value data)
{
std::cout << "Received advance request data " << data << std::endl;
try
{
// Use your template helper to decode hex payload into UTF-8 text.
// Example helper in Cartesi templates: hex_to_string("0x32") -> "2"
const std::string payload_hex = data.get("payload").get<std::string>();
const std::string input = hex_to_string(payload_hex);
// 2) Parse number, compute multiples.
const long long value = std::stoll(input);
const std::string multiples = calculate_multiples(value);
std::cout << "Adding notice with value " << multiples << std::endl;
// 3) Build and emit notice to the rollup server.
picojson::object notice;
// Use your template helper to encode UTF-8 text into hex.
// Example helper: string_to_hex("2, 4, 6, 8, 10")
notice["payload"] = picojson::value(string_to_hex(multiples));
const std::string body = picojson::value(notice).serialize();
auto response = cli.Post("/notice", body, "application/json");
if (!response || response->status >= 400)
{
std::cerr << "Failed to send notice" << std::endl;
}
}
catch (const std::exception &e)
{
std::cerr << "Error in handle_advance: " << e.what() << std::endl;
}
return "accept";
}
// ... rest of the Cartesi app code (finish loop, routing, and startup)
For example, sending an input payload of “2” to the application using Cast or cartesi send generic will log:
Received finish status 200
Received advance request data {"metadata":{"chain_id":31337,"app_contract":"0xef34611773387750985673f94067ea22db406f72","msg_sender":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","block_number":188,"block_timestamp":1741291197,"prev_randao":"0xa8b0097138c68949870aabe7aa4dc62a91b6dd0bc2b4582eac2be9eaee280032","input_index":0},"payload":"0x32"}
Adding notice with values 2, 4, 6, 8, 10
Vouchers: On-chain actions
A voucher in Cartesi Rollups is a mechanism for executing on-chain actions from an application.
Think of it as a digital authorization ticket that enables an application to perform specific actions on the blockchain, such as transferring assets or approving transactions.