From d30ddb8104c7ac228e098c72c1927308f12591d6 Mon Sep 17 00:00:00 2001 From: Shaheen Date: Wed, 18 Feb 2026 20:55:25 +0530 Subject: [PATCH 1/6] added code snippets for go and cpp --- .../version-2.0/development/asset-handling.md | 442 ++++++++++++++++++ .../development/creating-an-application.md | 18 + .../version-2.0/development/query-outputs.md | 37 ++ .../development/send-inputs-and-assets.md | 17 + .../snippets/implementing_outputs_cpp.md | 123 +++++ .../snippets/implementing_outputs_go.md | 150 ++++++ .../snippets/request_handling_cpp.md | 53 +++ .../snippets/request_handling_go.md | 92 ++++ .../snippets/sending_notice_cpp.md | 62 +++ .../development/snippets/sending_notice_go.md | 43 ++ .../snippets/sending_report_cpp.md | 36 ++ .../development/snippets/sending_report_go.md | 25 + 12 files changed, 1098 insertions(+) create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/implementing_outputs_cpp.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/implementing_outputs_go.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_cpp.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_go.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/sending_notice_cpp.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/sending_notice_go.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/sending_report_cpp.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/sending_report_go.md diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md b/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md index cc859b62..1acf57b4 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md +++ b/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md @@ -40,6 +40,134 @@ Deposit input payloads are always specified as packed ABI-encoded parameters, as | ERC-1155 (single) | | | | ERC-1155 (batch) | | | +Refer to the functions provided below to understand how to handle asset deposits. These functions when called inside the `handle_advance` function of your application will help you decode the payload for the asset type being deposited. + +For example, to decode an ERC-20 deposit payload, you can use the following code snippets: + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + +

+```javascript
+function decodeErc20Deposit(payloadHex) {
+  if (typeof payloadHex !== "string") {
+    throw new TypeError("payload must be a hex string");
+  }
+
+  const payload = payloadHex.startsWith("0x") ? payloadHex.slice(2) : payloadHex;
+  const raw = Buffer.from(payload, "hex");
+
+  // token(20) + sender(20) + amount(32) = 72 bytes
+  if (raw.length < 72) {
+    throw new Error("invalid ERC-20 deposit payload");
+  }
+
+  const token = `0x${raw.subarray(0, 20).toString("hex")}`.toLowerCase().trim();
+  const sender = `0x${raw.subarray(20, 40).toString("hex")}`.toLowerCase().trim();
+  const amount = BigInt(`0x${raw.subarray(40, 72).toString("hex")}`);
+  const exec_layer_data = raw.subarray(72);
+
+  return {
+    token,
+    sender,
+    amount,
+    exec_layer_data,
+  };
+}
+```
+
+
+ + +

+```python
+def decode_erc20_deposit(payload_hex):
+    """
+    Decode a Cartesi Rollups ERC-20 deposit payload.
+    """
+    # Accept both prefixed and non-prefixed hex payloads.
+    payload = payload_hex[2:] if payload_hex.startswith("0x") else payload_hex
+    raw = bytes.fromhex(payload)
+
+    # Minimum size is token(20) + sender(20) + amount(32) = 72 bytes.
+    if len(raw) < 72:
+        raise ValueError("invalid ERC-20 deposit payload")
+
+    token = ("0x" + raw[0:20].hex()).lower().strip()
+    sender = ("0x" + raw[20:40].hex()).lower().strip()
+    amount = int.from_bytes(raw[40:72], byteorder="big")
+    exec_layer_data = raw[72:]
+
+    return {
+        "token": token,
+        "sender": sender,
+        "amount": amount,
+        "exec_layer_data": exec_layer_data,
+    }
+```
+
+
+ + +

+```rust
+use num_bigint::BigUint;
+
+#[derive(Debug)]
+pub struct Erc20Deposit {
+    pub token: String,
+    pub sender: String,
+    pub amount: BigUint,
+    pub exec_layer_data: Vec,
+}
+
+pub fn decode_erc20_deposit(payload_hex: &str) -> Result {
+    let payload = payload_hex.strip_prefix("0x").unwrap_or(payload_hex);
+    let mut raw = Vec::with_capacity(payload.len() / 2);
+    for i in (0..payload.len()).step_by(2) {
+        let byte = payload
+            .get(i..i + 2)
+            .ok_or("invalid hex payload length")?
+            .to_string();
+        raw.push(u8::from_str_radix(&byte, 16).map_err(|_| "invalid hex payload")?);
+    }
+
+    if raw.len() < 72 {
+        return Err("invalid ERC-20 deposit payload".to_string());
+    }
+
+    let token = format!(
+        "0x{}",
+        raw[0..20]
+            .iter()
+            .map(|b| format!("{:02x}", b))
+            .collect::()
+    );
+    let sender = format!(
+        "0x{}",
+        raw[20..40]
+            .iter()
+            .map(|b| format!("{:02x}", b))
+            .collect::()
+    );
+    let amount = BigUint::from_bytes_be(&raw[40..72]);
+    let exec_layer_data = raw[72..].to_vec();
+
+    Ok(Erc20Deposit {
+        token,
+        sender,
+        amount,
+        exec_layer_data,
+    })
+}
+```
+
+
+ +
+ ## Withdrawing assets Users can deposit assets to a Cartesi Application, but only the Application can initiate withdrawals. When a withdrawal request is made, it’s processed and interpreted off-chain by the Cartesi Machine running the application’s code. Subsequently, the Cartesi Machine creates a voucher containing the necessary instructions for withdrawal, which is executable when an epoch has settled. @@ -54,6 +182,9 @@ Next, the off-chain machine uses the address of the application on the base laye Below is a sample JavaScript code with the implementations to transfer tokens to whoever calls the application, notice that the `const call` variable is an encoded function data containing the token contract ABI, function name and also arguments like recipient and amount, while the actual `voucher` structure itself contains a destination (erc20 token contract where the transfer execution should occur), the payload (encoded function data in `call`) and finally a value field which is initialized to `0` meaning no Ether is intended to be sent alongside this transfer request. + + +

 ```javascript
 import { stringToHex, encodeFunctionData, erc20Abi, hexToString, zeroHash } from "viem";
 
@@ -97,6 +228,164 @@ const emitVoucher = async (voucher) => {
   }
 };
 ```
+
+
+ + +

+```python
+import json
+import logging
+import os
+
+import requests
+
+logging.basicConfig(level="INFO")
+logger = logging.getLogger(__name__)
+
+ROLLUP_HTTP_SERVER_URL = os.environ["ROLLUP_HTTP_SERVER_URL"]
+TOKEN_ADDRESS = "0x5138f529b77b4e0a7c84b77e79c4335d31938fed"
+
+
+def payload_hex_to_bytes(payload):
+    if payload.startswith("0x"):
+        payload = payload[2:]
+    return bytes.fromhex(payload)
+
+
+def encode_erc20_transfer(recipient, amount):
+    recipient = recipient.lower().strip()
+    if recipient.startswith("0x"):
+        recipient = recipient[2:]
+
+    selector = bytes.fromhex("a9059cbb")  # transfer(address,uint256)
+    recipient_word = bytes.fromhex("00" * 12 + recipient)
+    amount_word = int(amount).to_bytes(32, byteorder="big")
+    return "0x" + (selector + recipient_word + amount_word).hex()
+
+
+def emit_transfer_voucher(token_address, recipient, amount):
+    voucher = {
+        "destination": token_address.lower().strip(),
+        "payload": encode_erc20_transfer(recipient, amount),
+    }
+    response = requests.post(ROLLUP_HTTP_SERVER_URL + "/voucher", json=voucher)
+    return response.status_code
+
+
+def handle_advance(data):
+    command_raw = payload_hex_to_bytes(data["payload"]).decode("utf-8")
+    command = json.loads(command_raw)
+
+    amount = int(command["amount"])
+    recipient = command.get("recipient") or data["metadata"]["msg_sender"]
+
+    status = emit_transfer_voucher(TOKEN_ADDRESS, recipient, amount)
+    logger.info("Voucher POST status=%s", status)
+    return "accept"
+```
+
+
+ + +

+```rust
+use json::{object, JsonValue};
+use num_bigint::BigUint;
+
+pub const TOKEN_ADDRESS: &str = "0x5138f529b77b4e0a7c84b77e79c4335d31938fed";
+
+fn payload_hex_to_bytes(payload: &str) -> Result, String> {
+    let payload = payload.strip_prefix("0x").unwrap_or(payload);
+    let mut out = Vec::with_capacity(payload.len() / 2);
+    for i in (0..payload.len()).step_by(2) {
+        let byte = payload
+            .get(i..i + 2)
+            .ok_or("invalid hex payload length")?
+            .to_string();
+        out.push(u8::from_str_radix(&byte, 16).map_err(|_| "invalid hex payload")?);
+    }
+    Ok(out)
+}
+
+fn encode_erc20_transfer(recipient: &str, amount: &BigUint) -> Result {
+    let recipient = recipient
+        .trim()
+        .to_lowercase()
+        .strip_prefix("0x")
+        .unwrap_or(recipient)
+        .to_string();
+    let recipient_bytes = payload_hex_to_bytes(&recipient)?;
+
+    let mut data = Vec::with_capacity(68);
+    data.extend_from_slice(&[0xa9, 0x05, 0x9c, 0xbb]); // transfer(address,uint256)
+    data.extend_from_slice(&[0u8; 12]); // left-pad address to 32 bytes
+    data.extend_from_slice(&recipient_bytes);
+
+    let amount_bytes = amount.to_bytes_be();
+    let amount_padding = 32usize.saturating_sub(amount_bytes.len());
+    data.extend(std::iter::repeat_n(0u8, amount_padding));
+    data.extend_from_slice(&amount_bytes);
+
+    Ok(format!(
+        "0x{}",
+        data.iter().map(|b| format!("{:02x}", b)).collect::()
+    ))
+}
+
+pub async fn emit_transfer_voucher(
+    client: &hyper::Client,
+    server_addr: &str,
+    token_address: &str,
+    recipient: &str,
+    amount: &BigUint,
+) -> Result> {
+    let voucher = object! {
+        "destination" => token_address.trim().to_lowercase(),
+        "payload" => encode_erc20_transfer(recipient, amount)?,
+    };
+
+    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()))?;
+
+    let response = client.request(request).await?;
+    Ok(response.status())
+}
+
+pub async fn handle_advance(
+    client: &hyper::Client,
+    server_addr: &str,
+    request: JsonValue,
+) -> Result<&'static str, Box> {
+    let command_raw = std::str::from_utf8(&payload_hex_to_bytes(
+        request["data"]["payload"].as_str().ok_or("Missing payload")?,
+    )?)?;
+    let command = json::parse(command_raw)?;
+
+    let amount = if let Some(v) = command["amount"].as_str() {
+        BigUint::parse_bytes(v.as_bytes(), 10).ok_or("invalid amount")?
+    } else if let Some(v) = command["amount"].as_u64() {
+        BigUint::from(v)
+    } else {
+        return Err("missing amount".into());
+    };
+
+    let recipient = command["recipient"]
+        .as_str()
+        .or_else(|| request["data"]["metadata"]["msg_sender"].as_str())
+        .ok_or("missing recipient")?;
+
+    let status = emit_transfer_voucher(client, server_addr, TOKEN_ADDRESS, recipient, &amount).await?;
+    println!("Voucher POST status={}", status);
+    Ok("accept")
+}
+```
+
+
+
### Withdrawing Ether @@ -104,6 +393,9 @@ To execute Ether withdrawal it is important to emit a voucher with the necessary Below is another sample JavaScript code, this time the voucher structure has been modified to send ether to an address instead of calling a function in a smart contract, notice there is no `encodedFunctionData`, so the payload section is initialized to zeroHash. + + +

 ```javascript
 import { stringToHex, encodeFunctionData, erc20Abi, hexToString, zeroHash, parseEther } from "viem";
 
@@ -141,6 +433,156 @@ const emitVoucher = async (voucher) => {
   }
 };
 ```
+
+
+ + +

+```python
+import json
+import logging
+import os
+
+import requests
+
+logging.basicConfig(level="INFO")
+logger = logging.getLogger(__name__)
+
+ROLLUP_HTTP_SERVER_URL = os.environ["ROLLUP_HTTP_SERVER_URL"]
+ZERO_HASH = "0x" + ("00" * 32)
+
+
+def payload_hex_to_bytes(payload):
+    if payload.startswith("0x"):
+        payload = payload[2:]
+    return bytes.fromhex(payload)
+
+
+def emit_ether_voucher(recipient, amount_wei):
+    """
+    Emit an Ether transfer voucher.
+
+    Per Cartesi docs for Ether transfer:
+    - destination: recipient address
+    - payload: zero hash (no function call)
+    - value: Ether amount as hex (without 0x prefix)
+    """
+    recipient = recipient.lower().strip()
+    value_hex = hex(int(amount_wei))[2:]
+
+    voucher = {
+        "destination": recipient,
+        "payload": ZERO_HASH,
+        "value": value_hex,
+    }
+    response = requests.post(ROLLUP_HTTP_SERVER_URL + "/voucher", json=voucher)
+    return response.status_code
+
+
+def handle_advance(data):
+    """
+    Expected payload JSON (hex-encoded UTF-8):
+    {"amount_wei":"1000000000000000","recipient":"0x...optional"}
+    """
+    command_raw = payload_hex_to_bytes(data["payload"]).decode("utf-8")
+    command = json.loads(command_raw)
+
+    amount_wei = int(command["amount_wei"])
+    recipient = command.get("recipient") or data["metadata"]["msg_sender"]
+
+    status = emit_ether_voucher(recipient, amount_wei)
+    logger.info("Ether voucher POST status=%s", status)
+    return "accept"
+```
+
+
+ + +

+```rust 
+use json::{object, JsonValue};
+use num_bigint::BigUint;
+
+fn payload_hex_to_bytes(payload: &str) -> Result, String> {
+    let payload = payload.strip_prefix("0x").unwrap_or(payload);
+    let mut out = Vec::with_capacity(payload.len() / 2);
+    for i in (0..payload.len()).step_by(2) {
+        let byte = payload
+            .get(i..i + 2)
+            .ok_or("invalid hex payload length")?
+            .to_string();
+        out.push(u8::from_str_radix(&byte, 16).map_err(|_| "invalid hex payload")?);
+    }
+    Ok(out)
+}
+
+fn encode_ether_value(amount: &BigUint) -> String {
+    let bytes = amount.to_bytes_be();
+    let mut padded = vec![0u8; 32usize.saturating_sub(bytes.len())];
+    padded.extend_from_slice(&bytes);
+    format!(
+        "0x{}",
+        padded
+            .iter()
+            .map(|b| format!("{:02x}", b))
+            .collect::()
+    )
+}
+
+pub async fn emit_ether_transfer_voucher(
+    client: &hyper::Client,
+    server_addr: &str,
+    recipient: &str,
+    amount: &BigUint,
+) -> Result> {
+    let voucher = object! {
+        "destination" => recipient.trim().to_lowercase(),
+        "payload" => "0x",
+        "value" => encode_ether_value(amount),
+    };
+
+    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()))?;
+
+    let response = client.request(request).await?;
+    Ok(response.status())
+}
+
+pub async fn handle_advance(
+    client: &hyper::Client,
+    server_addr: &str,
+    request: JsonValue,
+) -> Result<&'static str, Box> {
+    let command_raw = std::str::from_utf8(&payload_hex_to_bytes(
+        request["data"]["payload"].as_str().ok_or("Missing payload")?,
+    )?)?;
+    let command = json::parse(command_raw)?;
+
+    let amount = if let Some(v) = command["amount"].as_str() {
+        BigUint::parse_bytes(v.as_bytes(), 10).ok_or("invalid amount")?
+    } else if let Some(v) = command["amount"].as_u64() {
+        BigUint::from(v)
+    } else {
+        return Err("missing amount".into());
+    };
+
+    let recipient = command["recipient"]
+        .as_str()
+        .or_else(|| request["data"]["metadata"]["msg_sender"].as_str())
+        .ok_or("missing recipient")?;
+
+    let status = emit_ether_transfer_voucher(client, server_addr, recipient, &amount).await?;
+    println!("Voucher POST status={}", status);
+    Ok("accept")
+}
+```
+
+
+ +
:::note epoch length By default, Cartesi nodes close one epoch every 7200 blocks. You can manually set the epoch length to facilitate quicker asset-handling methods. diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/creating-an-application.md b/cartesi-rollups_versioned_docs/version-2.0/development/creating-an-application.md index ae169ae9..7322c317 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/development/creating-an-application.md +++ b/cartesi-rollups_versioned_docs/version-2.0/development/creating-an-application.md @@ -66,6 +66,8 @@ import TabItem from '@theme/TabItem'; import ImplementingOutputsJS from './snippets/implementing_outputs_js.md'; import ImplementingOutputsPY from './snippets/implementing_outputs_py.md'; import ImplementingOutputsRS from './snippets/implementing_outputs_rs.md'; +import ImplementingOutputsGO from './snippets/implementing_outputs_go.md'; +import ImplementingOutputsCPP from './snippets/implementing_outputs_cpp.md'; @@ -93,4 +95,20 @@ import ImplementingOutputsRS from './snippets/implementing_outputs_rs.md'; + +

+
+
+
+
+
+ + +

+
+
+
+
+
+
\ No newline at end of file diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/query-outputs.md b/cartesi-rollups_versioned_docs/version-2.0/development/query-outputs.md index 77cc7fcd..df8db12b 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/development/query-outputs.md +++ b/cartesi-rollups_versioned_docs/version-2.0/development/query-outputs.md @@ -32,6 +32,8 @@ We will send the output to the rollup server as a notice. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +import SendingNoticeGO from './snippets/sending_notice_go.md'; +import SendingNoticeCPP from './snippets/sending_notice_cpp.md'; @@ -214,6 +216,22 @@ pub async fn handle_advance( + +

+
+
+
+
+
+ + +

+
+
+
+
+
+
For example, sending an input payload of `“2”` to the application using Cast or `cartesi send generic` will log: @@ -378,6 +396,9 @@ Reports serve as stateless logs, providing read-only information without affecti Here is how you can write your application to send reports to the rollup server: +import SendingReportGO from './snippets/sending_report_go.md'; +import SendingReportCPP from './snippets/sending_report_cpp.md'; +

@@ -502,6 +523,22 @@ async fn emit_report( payload: String) -> Option {
 
+ +

+
+
+
+
+
+ + +

+
+
+
+
+
+
You can use the exposed JSON-RPC API to query all reports from an application. diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/send-inputs-and-assets.md b/cartesi-rollups_versioned_docs/version-2.0/development/send-inputs-and-assets.md index 5ae39fc9..5581ec82 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/development/send-inputs-and-assets.md +++ b/cartesi-rollups_versioned_docs/version-2.0/development/send-inputs-and-assets.md @@ -20,6 +20,8 @@ Here is a simple boilerplate application that shows the default implementation o import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +import RequestHandlingGO from './snippets/request_handling_go.md'; +import RequestHandlingCPP from './snippets/request_handling_cpp.md'; @@ -192,6 +194,21 @@ async fn main() -> Result<(), Box> { } ``` + + + + +

+
+
+
+
+
+ + +

+
+
 
 
diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/implementing_outputs_cpp.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/implementing_outputs_cpp.md new file mode 100644 index 00000000..b07c3633 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/implementing_outputs_cpp.md @@ -0,0 +1,123 @@ +```cpp +#include +#include +#include +#include + +#include "3rdparty/cpp-httplib/httplib.h" +#include "3rdparty/picojson/picojson.h" + +namespace sample_config +{ +// Sample values for voucher emission. +const std::string kSampleErc20Token = "0x0000000000000000000000000000000000000001"; +const std::string kSampleRecipient = "0x1111111111111111111111111111111111111111"; +const std::string kSampleAmount1e18Hex = "0de0b6b3a7640000"; +} // namespace sample_config + +std::string get_input_payload(const picojson::value &data) +{ + // Rollups input payload is expected at data.payload as a hex string. + if (!data.is()) + { + return "0x"; + } + + const auto &obj = data.get(); + const auto it = obj.find("payload"); + if (it == obj.end() || !it->second.is()) + { + return "0x"; + } + return it->second.get(); +} + +void post_payload(httplib::Client &cli, const std::string &endpoint, const std::string &payload) +{ + picojson::object body; + body["payload"] = picojson::value(payload); + auto res = cli.Post(endpoint.c_str(), picojson::value(body).serialize(), "application/json"); + if (res) + { + std::cout << "Sent " << endpoint << ", status " << res->status << std::endl; + } + else + { + std::cout << "Failed sending " << endpoint << std::endl; + } +} + +void post_sample_erc20_transfer_voucher(httplib::Client &cli) +{ + const std::string recipient_padded = + "000000000000000000000000" + sample_config::kSampleRecipient.substr(2); + const std::string amount_padded = + "000000000000000000000000000000000000000000000000" + sample_config::kSampleAmount1e18Hex; + // ERC-20 transfer(address,uint256) selector. + const std::string payload = "0xa9059cbb" + recipient_padded + amount_padded; + + picojson::object body; + body["destination"] = picojson::value(sample_config::kSampleErc20Token); + body["payload"] = picojson::value(payload); + auto res = cli.Post("/voucher", picojson::value(body).serialize(), "application/json"); + if (res) + { + std::cout << "Sent /voucher, status " << res->status << std::endl; + } + else + { + std::cout << "Failed sending /voucher" << std::endl; + } +} + +std::string handle_advance(httplib::Client &cli, picojson::value data) +{ + std::cout << "Received advance request data " << data << std::endl; + const auto input_payload = get_input_payload(data); + // Echo input payload to notice and report. + post_payload(cli, "/notice", input_payload); + post_payload(cli, "/report", input_payload); + post_sample_erc20_transfer_voucher(cli); + return "accept"; +} + +std::string handle_inspect(httplib::Client &cli, picojson::value data) +{ + std::cout << "Received inspect request data " << data << std::endl; + return "accept"; +} + +int main(int argc, char **argv) +{ + std::map handlers = { + {std::string("advance_state"), &handle_advance}, + {std::string("inspect_state"), &handle_inspect}, + }; + httplib::Client cli(getenv("ROLLUP_HTTP_SERVER_URL")); + cli.set_read_timeout(20, 0); + std::string status("accept"); + std::string rollup_address; + while (true) + { + std::cout << "Sending finish" << std::endl; + auto finish = std::string("{\"status\":\"") + status + std::string("\"}"); + auto r = cli.Post("/finish", finish, "application/json"); + std::cout << "Received finish status " << r.value().status << std::endl; + if (r.value().status == 202) + { + std::cout << "No pending rollup request, trying again" << std::endl; + } + else + { + picojson::value rollup_request; + picojson::parse(rollup_request, r.value().body); + picojson::value metadata = rollup_request.get("data").get("metadata"); + auto request_type = rollup_request.get("request_type").get(); + auto handler = handlers.find(request_type)->second; + auto data = rollup_request.get("data"); + status = (*handler)(cli, data); + } + } + return 0; +} +``` \ No newline at end of file diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/implementing_outputs_go.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/implementing_outputs_go.md new file mode 100644 index 00000000..acd1c037 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/implementing_outputs_go.md @@ -0,0 +1,150 @@ +```go +package main + +import ( + "dapp/rollups" + "encoding/json" + "fmt" + "io" + "log" + "math/big" + "os" + "strconv" + "strings" +) + +var ( + infolog = log.New(os.Stderr, "[ info ] ", log.Lshortfile) + errlog = log.New(os.Stderr, "[ error ] ", log.Lshortfile) +) + +const ( + tokenAddress = "0x1111111111111111111111111111111111111111" + transferReceiver = "0x2222222222222222222222222222222222222222" + transferAmountWEI = "1000000000000000000" +) + +func leftPad64(hexValue string) string { + if len(hexValue) >= 64 { + return hexValue + } + return strings.Repeat("0", 64-len(hexValue)) + hexValue +} + +// encodeERC20Transfer creates calldata for transfer(address,uint256). +func encodeERC20Transfer(receiver string, amountWei string) (string, error) { + to := strings.ToLower(strings.TrimPrefix(receiver, "0x")) + if len(to) != 40 { + return "", fmt.Errorf("invalid receiver address: %s", receiver) + } + + amount := new(big.Int) + if _, ok := amount.SetString(amountWei, 10); !ok || amount.Sign() < 0 { + return "", fmt.Errorf("invalid transfer amount: %s", amountWei) + } + + // Function selector for transfer(address,uint256) is a9059cbb. + return "0xa9059cbb" + leftPad64(to) + leftPad64(amount.Text(16)), nil +} + +func HandleAdvance(data *rollups.AdvanceResponse) error { + dataMarshal, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("HandleAdvance: failed marshaling json: %w", err) + } + infolog.Println("Received advance request data", string(dataMarshal)) + + // Publish input payload as a notice. + notice := rollups.NoticeRequest{Payload: data.Payload} + if _, err = rollups.SendNotice(¬ice); err != nil { + return fmt.Errorf("HandleAdvance: failed sending notice: %w", err) + } + + // Publish input payload as a report. + report := rollups.ReportRequest{Payload: data.Payload} + if _, err = rollups.SendReport(&report); err != nil { + return fmt.Errorf("HandleAdvance: failed sending report: %w", err) + } + + // Build an ERC-20 transfer voucher. + voucherPayload, err := encodeERC20Transfer(transferReceiver, transferAmountWEI) + if err != nil { + return fmt.Errorf("HandleAdvance: failed building voucher payload: %w", err) + } + voucher := rollups.VoucherRequest{ + Destination: tokenAddress, + Value: "0x0", + Payload: voucherPayload, + } + if _, err = rollups.SendVoucher(&voucher); err != nil { + return fmt.Errorf("HandleAdvance: failed sending voucher: %w", err) + } + + return nil +} + +func HandleInspect(data *rollups.InspectResponse) error { + dataMarshal, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("HandleInspect: failed marshaling json: %w", err) + } + infolog.Println("Received inspect request data", string(dataMarshal)) + return nil +} + +func Handler(response *rollups.FinishResponse) error { + var err error + + switch response.Type { + case "advance_state": + data := new(rollups.AdvanceResponse) + if err = json.Unmarshal(response.Data, data); err != nil { + return fmt.Errorf("Handler: Error unmarshaling advance: %w", err) + } + err = HandleAdvance(data) + case "inspect_state": + data := new(rollups.InspectResponse) + if err = json.Unmarshal(response.Data, data); err != nil { + return fmt.Errorf("Handler: Error unmarshaling inspect: %w", err) + } + err = HandleInspect(data) + } + return err +} + +func main() { + finish := rollups.FinishRequest{Status: "accept"} + + for { + infolog.Println("Sending finish") + res, err := rollups.SendFinish(&finish) + if err != nil { + errlog.Panicln("Error: error making http request: ", err) + } + infolog.Println("Received finish status ", strconv.Itoa(res.StatusCode)) + + if res.StatusCode == 202 { + infolog.Println("No pending rollup request, trying again") + } else { + + resBody, err := io.ReadAll(res.Body) + if err != nil { + errlog.Panicln("Error: could not read response body: ", err) + } + + var response rollups.FinishResponse + err = json.Unmarshal(resBody, &response) + if err != nil { + errlog.Panicln("Error: unmarshaling body:", err) + } + + finish.Status = "accept" + err = Handler(&response) + if err != nil { + errlog.Println(err) + finish.Status = "reject" + } + } + } +} +``` \ No newline at end of file diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_cpp.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_cpp.md new file mode 100644 index 00000000..c2d4dd91 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_cpp.md @@ -0,0 +1,53 @@ +```cpp +#include +#include + +#include "3rdparty/cpp-httplib/httplib.h" +#include "3rdparty/picojson/picojson.h" + +std::string handle_advance(httplib::Client &cli, picojson::value data) +{ + std::cout << "Received advance request data " << data << std::endl; + return "accept"; +} + +std::string handle_inspect(httplib::Client &cli, picojson::value data) +{ + std::cout << "Received inspect request data " << data << std::endl; + return "accept"; +} + +int main(int argc, char **argv) +{ + std::map handlers = { + {std::string("advance_state"), &handle_advance}, + {std::string("inspect_state"), &handle_inspect}, + }; + httplib::Client cli(getenv("ROLLUP_HTTP_SERVER_URL")); + cli.set_read_timeout(20, 0); + std::string status("accept"); + std::string rollup_address; + while (true) + { + std::cout << "Sending finish" << std::endl; + auto finish = std::string("{\"status\":\"") + status + std::string("\"}"); + auto r = cli.Post("/finish", finish, "application/json"); + std::cout << "Received finish status " << r.value().status << std::endl; + if (r.value().status == 202) + { + std::cout << "No pending rollup request, trying again" << std::endl; + } + else + { + picojson::value rollup_request; + picojson::parse(rollup_request, r.value().body); + picojson::value metadata = rollup_request.get("data").get("metadata"); + auto request_type = rollup_request.get("request_type").get(); + auto handler = handlers.find(request_type)->second; + auto data = rollup_request.get("data"); + status = (*handler)(cli, data); + } + } + return 0; +} +``` \ No newline at end of file diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_go.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_go.md new file mode 100644 index 00000000..c6c19992 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_go.md @@ -0,0 +1,92 @@ +```go +package main + +import ( + "dapp/rollups" + "encoding/json" + "fmt" + "io" + "log" + "os" + "strconv" +) + +var ( + infolog = log.New(os.Stderr, "[ info ] ", log.Lshortfile) + errlog = log.New(os.Stderr, "[ error ] ", log.Lshortfile) +) + +func HandleAdvance(data *rollups.AdvanceResponse) error { + dataMarshal, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("HandleAdvance: failed marshaling json: %w", err) + } + infolog.Println("Received advance request data", string(dataMarshal)) + return nil +} + +func HandleInspect(data *rollups.InspectResponse) error { + dataMarshal, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("HandleInspect: failed marshaling json: %w", err) + } + infolog.Println("Received inspect request data", string(dataMarshal)) + return nil +} + +func Handler(response *rollups.FinishResponse) error { + var err error + + switch response.Type { + case "advance_state": + data := new(rollups.AdvanceResponse) + if err = json.Unmarshal(response.Data, data); err != nil { + return fmt.Errorf("Handler: Error unmarshaling advance: %w", err) + } + err = HandleAdvance(data) + case "inspect_state": + data := new(rollups.InspectResponse) + if err = json.Unmarshal(response.Data, data); err != nil { + return fmt.Errorf("Handler: Error unmarshaling inspect: %w", err) + } + err = HandleInspect(data) + } + return err +} + +func main() { + finish := rollups.FinishRequest{Status: "accept"} + + for { + infolog.Println("Sending finish") + res, err := rollups.SendFinish(&finish) + if err != nil { + errlog.Panicln("Error: error making http request: ", err) + } + infolog.Println("Received finish status ", strconv.Itoa(res.StatusCode)) + + if res.StatusCode == 202 { + infolog.Println("No pending rollup request, trying again") + } else { + + resBody, err := io.ReadAll(res.Body) + if err != nil { + errlog.Panicln("Error: could not read response body: ", err) + } + + var response rollups.FinishResponse + err = json.Unmarshal(resBody, &response) + if err != nil { + errlog.Panicln("Error: unmarshaling body:", err) + } + + finish.Status = "accept" + err = Handler(&response) + if err != nil { + errlog.Println(err) + finish.Status = "reject" + } + } + } +} +``` \ No newline at end of file diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/sending_notice_cpp.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/sending_notice_cpp.md new file mode 100644 index 00000000..f8b2edce --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/sending_notice_cpp.md @@ -0,0 +1,62 @@ +```cpp +#include +#include + +#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(); + 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) +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/sending_notice_go.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/sending_notice_go.md new file mode 100644 index 00000000..12d22825 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/sending_notice_go.md @@ -0,0 +1,43 @@ +```go +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 +} +``` \ No newline at end of file diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/sending_report_cpp.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/sending_report_cpp.md new file mode 100644 index 00000000..8b37b6ba --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/sending_report_cpp.md @@ -0,0 +1,36 @@ +```cpp +#include +#include + +#include "3rdparty/cpp-httplib/httplib.h" +#include "3rdparty/picojson/picojson.h" + +// Minimal helper to emit a report payload. +static bool emit_report(httplib::Client &cli, const std::string &message) +{ + picojson::object report; + // Keep report payload hex-encoded using your template helper. + report["payload"] = picojson::value(string_to_hex(message)); + const std::string body = picojson::value(report).serialize(); + + auto response = cli.Post("/report", body, "application/json"); + return response && response->status < 400; +} + +std::string handle_advance(httplib::Client &cli, picojson::value data) +{ + try + { + // Example operation that may fail: decode payload. + const std::string payload_hex = data.get("payload").get(); + (void)hex_to_string(payload_hex); + } + catch (const std::exception &e) + { + emit_report(cli, std::string("Error: ") + e.what()); + return "reject"; + } + + return "accept"; +} +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/sending_report_go.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/sending_report_go.md new file mode 100644 index 00000000..5a55e5bb --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/sending_report_go.md @@ -0,0 +1,25 @@ +```go +package main + +import ( + "dapp/rollups" + "fmt" +) + +// HandleAdvance demonstrates minimal report emission on error. +func HandleAdvance(data *rollups.AdvanceResponse) error { + // Example operation that may fail: decode payload. + if _, err := rollups.Hex2Str(data.Payload); err != nil { + // Keep report payload hex-encoded. + report := rollups.ReportRequest{ + Payload: rollups.Str2Hex(fmt.Sprintf("Error: %v", err)), + } + if _, sendErr := rollups.SendReport(&report); sendErr != nil { + return fmt.Errorf("HandleAdvance: failed sending report: %w", sendErr) + } + return fmt.Errorf("HandleAdvance: rejected due to app error: %w", err) + } + + return nil +} +``` From 9d0f26a3ba9745a1174ac7ece725cf7d7428b13e Mon Sep 17 00:00:00 2001 From: Shaheen Date: Thu, 19 Feb 2026 13:18:24 +0530 Subject: [PATCH 2/6] added go and cpp snippets in api-reference --- .../api-reference/backend/exception.md | 64 +++++++ .../api-reference/backend/finish.md | 113 ++++++++++++ .../api-reference/backend/introduction.md | 167 +++--------------- .../api-reference/backend/notices.md | 57 ++++++ .../api-reference/backend/reports.md | 65 +++++++ .../api-reference/backend/vouchers.md | 67 +++++++ .../snippets/request_handling_js.md | 43 +++++ .../snippets/request_handling_py.md | 39 ++++ .../snippets/request_handling_rs.md | 69 ++++++++ 9 files changed, 537 insertions(+), 147 deletions(-) create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_js.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_py.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_rs.md diff --git a/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/exception.md b/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/exception.md index 6d060749..654fb4e8 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/exception.md +++ b/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/exception.md @@ -151,6 +151,70 @@ async fn throw_execption( payload: String) -> Option { + +

+
+```go
+func HandleAdvance(data *rollups.AdvanceResponse) error {
+	infolog.Printf("Received advance request data %+v\n", data)
+
+	// Process request. Replace with your app logic.
+	if _, err := rollups.Hex2Str(data.Payload); err != nil {
+		// Register exception payload and stop processing.
+		exception := rollups.ExceptionRequest{
+			Payload: rollups.Str2Hex(fmt.Sprintf("Error: %v", err)),
+		}
+		if _, sendErr := rollups.SendException(&exception); sendErr != nil {
+			return fmt.Errorf("HandleAdvance: failed sending exception: %w", sendErr)
+		}
+		return fmt.Errorf("HandleAdvance: fatal exception: %w", err)
+	}
+
+	return nil
+}
+```
+
+
+
+ + +

+
+```cpp
+std::string handle_advance(httplib::Client &cli, picojson::value data)
+{
+    std::cout << "Received advance request data " << data << std::endl;
+
+    try
+    {
+        // Process request. Replace with your app logic.
+        const std::string payload_hex = data.get("payload").get();
+        (void)hex_to_string(payload_hex);
+    }
+    catch (const std::exception &e)
+    {
+        // Register exception and stop processing.
+        picojson::object exception_body;
+        exception_body["payload"] = picojson::value(string_to_hex(std::string("Error: ") + e.what()));
+        auto response = cli.Post(
+            "/exception",
+            picojson::value(exception_body).serialize(),
+            "application/json"
+        );
+        if (!response || response->status >= 400)
+        {
+            std::cout << "Failed to register exception" << std::endl;
+        }
+        return "reject";
+    }
+
+    return "accept";
+}
+```
+
+
+
+
## Notes diff --git a/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/finish.md b/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/finish.md index 27a3a6a7..9480501e 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/finish.md +++ b/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/finish.md @@ -205,6 +205,119 @@ async fn main() -> Result<(), Box> { + +

+
+```go
+func HandleAdvance(data *rollups.AdvanceResponse) error {
+	// Add your state-changing logic here.
+	infolog.Printf("Received advance request data %+v\n", data)
+	return nil
+}
+
+func HandleInspect(data *rollups.InspectResponse) error {
+	// Add your read-only logic here.
+	infolog.Printf("Received inspect request data %+v\n", data)
+	return nil
+}
+
+func main() {
+	finish := rollups.FinishRequest{Status: "accept"}
+
+	for {
+		infolog.Println("Sending finish")
+		res, err := rollups.SendFinish(&finish)
+		if err != nil {
+			errlog.Panicln("Error calling /finish:", err)
+		}
+
+		if res.StatusCode == 202 {
+			infolog.Println("No pending rollup request, trying again")
+			continue
+		}
+
+		// Parse next request and dispatch to the correct handler.
+		response, err := rollups.ParseFinishResponse(res)
+		if err != nil {
+			errlog.Panicln("Error parsing finish response:", err)
+		}
+
+		finish.Status = "accept"
+		if response.Type == "advance_state" {
+			data := new(rollups.AdvanceResponse)
+			_ = json.Unmarshal(response.Data, data)
+			if err = HandleAdvance(data); err != nil {
+				finish.Status = "reject"
+			}
+		} else if response.Type == "inspect_state" {
+			data := new(rollups.InspectResponse)
+			_ = json.Unmarshal(response.Data, data)
+			_ = HandleInspect(data)
+		}
+	}
+}
+```
+
+
+
+ + +

+
+```cpp
+std::string handle_advance(httplib::Client &cli, picojson::value data)
+{
+    // Add your state-changing logic here.
+    std::cout << "Received advance request data " << data << std::endl;
+    return "accept";
+}
+
+std::string handle_inspect(httplib::Client &cli, picojson::value data)
+{
+    // Add your read-only logic here.
+    std::cout << "Received inspect request data " << data << std::endl;
+    return "accept";
+}
+
+int main(int argc, char **argv)
+{
+    std::map handlers = {
+        {"advance_state", &handle_advance},
+        {"inspect_state", &handle_inspect},
+    };
+
+    httplib::Client cli(getenv("ROLLUP_HTTP_SERVER_URL"));
+    std::string status = "accept";
+
+    while (true)
+    {
+        // Request next rollup input.
+        auto finish_body = std::string("{\"status\":\"") + status + "\"}";
+        auto response = cli.Post("/finish", finish_body, "application/json");
+        if (!response)
+        {
+            continue;
+        }
+
+        if (response->status == 202)
+        {
+            std::cout << "No pending rollup request, trying again" << std::endl;
+            continue;
+        }
+
+        // Dispatch to advance/inspect handlers.
+        picojson::value req;
+        picojson::parse(req, response->body);
+        auto request_type = req.get("request_type").get();
+        auto data = req.get("data");
+        status = handlers[request_type](cli, data);
+    }
+}
+```
+
+
+
+ ## Notes diff --git a/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/introduction.md b/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/introduction.md index d78b0734..6aae203b 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/introduction.md +++ b/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/introduction.md @@ -20,53 +20,17 @@ Here is a simple boilerplate application that handles Advance and Inspect reques import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +import RequestHandlingJS from '../../development/snippets/request_handling_js.md'; +import RequestHandlingPY from '../../development/snippets/request_handling_py.md'; +import RequestHandlingRS from '../../development/snippets/request_handling_rs.md'; +import RequestHandlingGO from '../../development/snippets/request_handling_go.md'; +import RequestHandlingCPP from '../../development/snippets/request_handling_cpp.md'; +

 
-```javascript
-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"]);
-    }
-  }
-})();
-```
+
 
 
@@ -74,122 +38,31 @@ var finish = { status: "accept" };

 
-```python
-from os import environ
-import logging
-import requests
-
-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}")
-   return "accept"
-
-def handle_inspect(data):
-   logger.info(f"Received inspect request data {data}")
-   return "accept"
+
+
+ +

 
-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"]) + +

 
-```
+
 
 
- +

 
-```rust
-use json::{object, JsonValue};
-use std::env;
-
-pub async fn handle_advance(
-    _client: &hyper::Client,
-    _server_addr: &str,
-    request: JsonValue,
-) -> Result<&'static str, Box> {
-    println!("Received advance request data {}", &request);
-    let _payload = request["data"]["payload"]
-        .as_str()
-        .ok_or("Missing payload")?;
-    // TODO: add application logic here
-    Ok("accept")
-}
-
-pub async fn handle_inspect(
-    _client: &hyper::Client,
-    _server_addr: &str,
-    request: JsonValue,
-) -> Result<&'static str, Box> {
-    println!("Received inspect request data {}", &request);
-    let _payload = request["data"]["payload"]
-        .as_str()
-        .ok_or("Missing payload")?;
-    // TODO: add application logic here
-    Ok("accept")
-}
-
-#[tokio::main]
-async fn main() -> Result<(), Box> {
-    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.clone()};
-        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"
-                }
-            };
-        }
-    }
-}
-```
+
 
 
diff --git a/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/notices.md b/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/notices.md index cf05b1f1..b08181ad 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/notices.md +++ b/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/notices.md @@ -124,6 +124,63 @@ async fn emit_notice(payload: String) -> Option { } ``` + +
+ + +

+
+```go
+func HandleAdvance(data *rollups.AdvanceResponse) error {
+	// Log incoming request for debugging.
+	infolog.Printf("Received advance request data %+v\n", data)
+
+	// Keep the same payload and forward it as a notice.
+	notice := rollups.NoticeRequest{
+		Payload: data.Payload,
+	}
+
+	if _, err := rollups.SendNotice(¬ice); err != nil {
+		return fmt.Errorf("HandleAdvance: failed sending notice: %w", err)
+	}
+
+	return nil
+}
+```
+
+
+
+ + +

+
+```cpp
+std::string handle_advance(httplib::Client &cli, picojson::value data)
+{
+    std::cout << "Received advance request data " << data << std::endl;
+
+    // Extract input payload and forward it as a notice.
+    const std::string payload = data.get("payload").get();
+    picojson::object notice;
+    notice["payload"] = picojson::value(payload);
+
+    // Send notice to the rollup server.
+    auto response = cli.Post(
+        "/notice",
+        picojson::value(notice).serialize(),
+        "application/json"
+    );
+
+    if (!response || response->status >= 400)
+    {
+        std::cout << "Failed to send notice" << std::endl;
+        return "reject";
+    }
+
+    return "accept";
+}
+```
+
 
diff --git a/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/reports.md b/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/reports.md index 6b8f4ced..34faa1ad 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/reports.md +++ b/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/reports.md @@ -119,6 +119,71 @@ pub async fn handle_advance( + +

+
+```go
+func HandleAdvance(data *rollups.AdvanceResponse) error {
+	// Log incoming request for diagnostics.
+	infolog.Printf("Received advance request data %+v\n", data)
+
+	// Example operation that may fail (replace with your app logic).
+	if _, err := rollups.Hex2Str(data.Payload); err != nil {
+		// Emit report with error details.
+		report := rollups.ReportRequest{
+			Payload: rollups.Str2Hex(fmt.Sprintf("Error: %v", err)),
+		}
+		if _, sendErr := rollups.SendReport(&report); sendErr != nil {
+			return fmt.Errorf("HandleAdvance: failed sending report: %w", sendErr)
+		}
+		return fmt.Errorf("HandleAdvance: rejected due to app error: %w", err)
+	}
+
+	return nil
+}
+```
+
+
+
+ + +

+
+```cpp
+std::string handle_advance(httplib::Client &cli, picojson::value data)
+{
+    std::cout << "Received advance request data " << data << std::endl;
+
+    try
+    {
+        // Example operation that may fail (replace with your app logic).
+        const std::string payload_hex = data.get("payload").get();
+        (void)hex_to_string(payload_hex);
+    }
+    catch (const std::exception &e)
+    {
+        // Emit report containing the error message.
+        picojson::object report;
+        report["payload"] = picojson::value(string_to_hex(std::string("Error: ") + e.what()));
+        auto response = cli.Post(
+            "/report",
+            picojson::value(report).serialize(),
+            "application/json"
+        );
+        if (!response || response->status >= 400)
+        {
+            std::cout << "Failed to send report" << std::endl;
+        }
+        return "reject";
+    }
+
+    return "accept";
+}
+```
+
+
+
+ :::note querying reports diff --git a/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/vouchers.md b/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/vouchers.md index 1c7f4b74..aecafb69 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/vouchers.md +++ b/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/vouchers.md @@ -189,6 +189,73 @@ pub async fn handle_advance( + +

+
+```go
+func HandleAdvance(data *rollups.AdvanceResponse) error {
+	// Log incoming request data.
+	infolog.Printf("Received advance request data %+v\n", data)
+
+	// Example ERC-20 transfer payload (transfer(address,uint256)).
+	// Replace with encoded calldata based on your app logic.
+	callData := "0xa9059cbb0000000000000000000000001111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000a"
+
+	voucher := rollups.VoucherRequest{
+		Destination: "0x784f0c076CC55EAD0a585a9A13e57c467c91Dc3a", // sample ERC-20 token
+		Payload:     callData,
+		Value:       "0x0",
+	}
+
+	if _, err := rollups.SendVoucher(&voucher); err != nil {
+		return fmt.Errorf("HandleAdvance: failed sending voucher: %w", err)
+	}
+
+	return nil
+}
+```
+
+
+
+ + +

+
+```cpp
+std::string handle_advance(httplib::Client &cli, picojson::value data)
+{
+    std::cout << "Received advance request data " << data << std::endl;
+
+    // Example ERC-20 transfer payload (transfer(address,uint256)).
+    // Replace with encoded calldata generated from your app logic.
+    const std::string call_data =
+        "0xa9059cbb0000000000000000000000001111111111111111111111111111111111111111"
+        "000000000000000000000000000000000000000000000000000000000000000a";
+
+    picojson::object voucher;
+    voucher["destination"] = picojson::value("0x784f0c076CC55EAD0a585a9A13e57c467c91Dc3a");
+    voucher["payload"] = picojson::value(call_data);
+    voucher["value"] = picojson::value("0x0");
+
+    auto response = cli.Post(
+        "/voucher",
+        picojson::value(voucher).serialize(),
+        "application/json"
+    );
+
+    if (!response || response->status >= 400)
+    {
+        std::cout << "Failed to send voucher" << std::endl;
+        return "reject";
+    }
+
+    return "accept";
+}
+```
+
+
+
+ :::note create a voucher diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_js.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_js.md new file mode 100644 index 00000000..5f31e2c6 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_js.md @@ -0,0 +1,43 @@ +```javascript +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"]); + } + } +})(); +``` \ No newline at end of file diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_py.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_py.md new file mode 100644 index 00000000..6e117780 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_py.md @@ -0,0 +1,39 @@ +```python +from os import environ +import logging +import requests + +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}") + return "accept" + +def handle_inspect(data): + logger.info(f"Received inspect request data {data}") + return "accept" + + +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"]) +``` \ No newline at end of file diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_rs.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_rs.md new file mode 100644 index 00000000..7fde5b2d --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/request_handling_rs.md @@ -0,0 +1,69 @@ +```rust +use json::{object, JsonValue}; +use std::env; + +pub async fn handle_advance( + _client: &hyper::Client, + _server_addr: &str, + request: JsonValue, +) -> Result<&'static str, Box> { + println!("Received advance request data {}", &request); + let _payload = request["data"]["payload"] + .as_str() + .ok_or("Missing payload")?; + // TODO: add application logic here + Ok("accept") +} + +pub async fn handle_inspect( + _client: &hyper::Client, + _server_addr: &str, + request: JsonValue, +) -> Result<&'static str, Box> { + println!("Received inspect request data {}", &request); + let _payload = request["data"]["payload"] + .as_str() + .ok_or("Missing payload")?; + // TODO: add application logic here + Ok("accept") +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + 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.clone()}; + 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" + } + }; + } + } +} +``` \ No newline at end of file From 5ba21e4e76b0fb4e26b06ffe05a6ae5975025d5c Mon Sep 17 00:00:00 2001 From: Shaheen Date: Mon, 23 Feb 2026 14:52:58 +0530 Subject: [PATCH 3/6] added go and cpp snippets to counter example and asset-handling with other improvements --- .../version-2.0/development/asset-handling.md | 536 +++--------------- .../snippets/asset_decode_erc20_cpp.md | 65 +++ .../snippets/asset_decode_erc20_go.md | 37 ++ .../snippets/asset_decode_erc20_js.md | 27 + .../snippets/asset_decode_erc20_py.md | 24 + .../snippets/asset_decode_erc20_rs.md | 51 ++ .../snippets/asset_withdraw_erc20_cpp.md | 39 ++ .../snippets/asset_withdraw_erc20_go.md | 38 ++ .../snippets/asset_withdraw_erc20_js.md | 43 ++ .../snippets/asset_withdraw_erc20_py.md | 51 ++ .../snippets/asset_withdraw_erc20_rs.md | 94 +++ .../snippets/asset_withdraw_ether_cpp.md | 37 ++ .../snippets/asset_withdraw_ether_go.md | 56 ++ .../snippets/asset_withdraw_ether_js.md | 36 ++ .../snippets/asset_withdraw_ether_py.md | 56 ++ .../snippets/asset_withdraw_ether_rs.md | 80 +++ .../snippets/implementing_outputs_cpp.md | 18 +- .../version-2.0/tutorials/counter.md | 42 +- .../tutorials/snippets/counter-cpp.md | 83 +++ .../tutorials/snippets/counter-go.md | 77 +++ 20 files changed, 1018 insertions(+), 472 deletions(-) create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_cpp.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_go.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_js.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_py.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_rs.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_cpp.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_go.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_js.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_py.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_rs.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_cpp.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_go.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_js.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_py.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_rs.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/counter-cpp.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/counter-go.md diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md b/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md index 1acf57b4..5dc1252e 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md +++ b/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md @@ -46,128 +46,61 @@ For example, to decode an ERC-20 deposit payload, you can use the following code import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +import AssetDecodeErc20JS from './snippets/asset_decode_erc20_js.md'; +import AssetDecodeErc20PY from './snippets/asset_decode_erc20_py.md'; +import AssetDecodeErc20RS from './snippets/asset_decode_erc20_rs.md'; +import AssetDecodeErc20GO from './snippets/asset_decode_erc20_go.md'; +import AssetDecodeErc20CPP from './snippets/asset_decode_erc20_cpp.md'; +import AssetWithdrawErc20JS from './snippets/asset_withdraw_erc20_js.md'; +import AssetWithdrawErc20PY from './snippets/asset_withdraw_erc20_py.md'; +import AssetWithdrawErc20RS from './snippets/asset_withdraw_erc20_rs.md'; +import AssetWithdrawErc20GO from './snippets/asset_withdraw_erc20_go.md'; +import AssetWithdrawErc20CPP from './snippets/asset_withdraw_erc20_cpp.md'; +import AssetWithdrawEtherJS from './snippets/asset_withdraw_ether_js.md'; +import AssetWithdrawEtherPY from './snippets/asset_withdraw_ether_py.md'; +import AssetWithdrawEtherRS from './snippets/asset_withdraw_ether_rs.md'; +import AssetWithdrawEtherGO from './snippets/asset_withdraw_ether_go.md'; +import AssetWithdrawEtherCPP from './snippets/asset_withdraw_ether_cpp.md';

-```javascript
-function decodeErc20Deposit(payloadHex) {
-  if (typeof payloadHex !== "string") {
-    throw new TypeError("payload must be a hex string");
-  }
-
-  const payload = payloadHex.startsWith("0x") ? payloadHex.slice(2) : payloadHex;
-  const raw = Buffer.from(payload, "hex");
-
-  // token(20) + sender(20) + amount(32) = 72 bytes
-  if (raw.length < 72) {
-    throw new Error("invalid ERC-20 deposit payload");
-  }
-
-  const token = `0x${raw.subarray(0, 20).toString("hex")}`.toLowerCase().trim();
-  const sender = `0x${raw.subarray(20, 40).toString("hex")}`.toLowerCase().trim();
-  const amount = BigInt(`0x${raw.subarray(40, 72).toString("hex")}`);
-  const exec_layer_data = raw.subarray(72);
-
-  return {
-    token,
-    sender,
-    amount,
-    exec_layer_data,
-  };
-}
-```
+
 

-```python
-def decode_erc20_deposit(payload_hex):
-    """
-    Decode a Cartesi Rollups ERC-20 deposit payload.
-    """
-    # Accept both prefixed and non-prefixed hex payloads.
-    payload = payload_hex[2:] if payload_hex.startswith("0x") else payload_hex
-    raw = bytes.fromhex(payload)
-
-    # Minimum size is token(20) + sender(20) + amount(32) = 72 bytes.
-    if len(raw) < 72:
-        raise ValueError("invalid ERC-20 deposit payload")
-
-    token = ("0x" + raw[0:20].hex()).lower().strip()
-    sender = ("0x" + raw[20:40].hex()).lower().strip()
-    amount = int.from_bytes(raw[40:72], byteorder="big")
-    exec_layer_data = raw[72:]
-
-    return {
-        "token": token,
-        "sender": sender,
-        "amount": amount,
-        "exec_layer_data": exec_layer_data,
-    }
-```
+
 

-```rust
-use num_bigint::BigUint;
-
-#[derive(Debug)]
-pub struct Erc20Deposit {
-    pub token: String,
-    pub sender: String,
-    pub amount: BigUint,
-    pub exec_layer_data: Vec,
-}
-
-pub fn decode_erc20_deposit(payload_hex: &str) -> Result {
-    let payload = payload_hex.strip_prefix("0x").unwrap_or(payload_hex);
-    let mut raw = Vec::with_capacity(payload.len() / 2);
-    for i in (0..payload.len()).step_by(2) {
-        let byte = payload
-            .get(i..i + 2)
-            .ok_or("invalid hex payload length")?
-            .to_string();
-        raw.push(u8::from_str_radix(&byte, 16).map_err(|_| "invalid hex payload")?);
-    }
-
-    if raw.len() < 72 {
-        return Err("invalid ERC-20 deposit payload".to_string());
-    }
-
-    let token = format!(
-        "0x{}",
-        raw[0..20]
-            .iter()
-            .map(|b| format!("{:02x}", b))
-            .collect::()
-    );
-    let sender = format!(
-        "0x{}",
-        raw[20..40]
-            .iter()
-            .map(|b| format!("{:02x}", b))
-            .collect::()
-    );
-    let amount = BigUint::from_bytes_be(&raw[40..72]);
-    let exec_layer_data = raw[72..].to_vec();
-
-    Ok(Erc20Deposit {
-        token,
-        sender,
-        amount,
-        exec_layer_data,
-    })
-}
-```
+
+
+
+ + +

+
+
+
+
+
+ + +

+
+
+
 
+For a full guide, see the Tutorials: [ERC-20 Token Wallet](../tutorials/erc-20-token-wallet.md) and [Utilizing test tokens in dev environment](../tutorials/Utilizing-test-tokens-in-dev-environment.md). + ## Withdrawing assets Users can deposit assets to a Cartesi Application, but only the Application can initiate withdrawals. When a withdrawal request is made, it’s processed and interpreted off-chain by the Cartesi Machine running the application’s code. Subsequently, the Cartesi Machine creates a voucher containing the necessary instructions for withdrawal, which is executable when an epoch has settled. @@ -185,208 +118,41 @@ Below is a sample JavaScript code with the implementations to transfer tokens to

-```javascript
-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
-
-    const call = encodeFunctionData({
-    abi: erc20Abi,
-    functionName: "transfer",
-    args: [sender, BigInt(10)],
-  });
-
-  let voucher = {
-    destination: erc20Token,
-    payload: call,
-    value: zeroHash,
-  };
-
-  await emitVoucher(voucher);
-  return "accept";
-}
-
-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
-  }
-};
-```
+
 

-```python
-import json
-import logging
-import os
-
-import requests
-
-logging.basicConfig(level="INFO")
-logger = logging.getLogger(__name__)
-
-ROLLUP_HTTP_SERVER_URL = os.environ["ROLLUP_HTTP_SERVER_URL"]
-TOKEN_ADDRESS = "0x5138f529b77b4e0a7c84b77e79c4335d31938fed"
-
-
-def payload_hex_to_bytes(payload):
-    if payload.startswith("0x"):
-        payload = payload[2:]
-    return bytes.fromhex(payload)
-
-
-def encode_erc20_transfer(recipient, amount):
-    recipient = recipient.lower().strip()
-    if recipient.startswith("0x"):
-        recipient = recipient[2:]
-
-    selector = bytes.fromhex("a9059cbb")  # transfer(address,uint256)
-    recipient_word = bytes.fromhex("00" * 12 + recipient)
-    amount_word = int(amount).to_bytes(32, byteorder="big")
-    return "0x" + (selector + recipient_word + amount_word).hex()
-
-
-def emit_transfer_voucher(token_address, recipient, amount):
-    voucher = {
-        "destination": token_address.lower().strip(),
-        "payload": encode_erc20_transfer(recipient, amount),
-    }
-    response = requests.post(ROLLUP_HTTP_SERVER_URL + "/voucher", json=voucher)
-    return response.status_code
+
+
+
+ +

+
+
+
-def handle_advance(data): - command_raw = payload_hex_to_bytes(data["payload"]).decode("utf-8") - command = json.loads(command_raw) + +

 
-    amount = int(command["amount"])
-    recipient = command.get("recipient") or data["metadata"]["msg_sender"]
+
 
-    status = emit_transfer_voucher(TOKEN_ADDRESS, recipient, amount)
-    logger.info("Voucher POST status=%s", status)
-    return "accept"
-```
 
- +

-```rust
-use json::{object, JsonValue};
-use num_bigint::BigUint;
-
-pub const TOKEN_ADDRESS: &str = "0x5138f529b77b4e0a7c84b77e79c4335d31938fed";
-
-fn payload_hex_to_bytes(payload: &str) -> Result, String> {
-    let payload = payload.strip_prefix("0x").unwrap_or(payload);
-    let mut out = Vec::with_capacity(payload.len() / 2);
-    for i in (0..payload.len()).step_by(2) {
-        let byte = payload
-            .get(i..i + 2)
-            .ok_or("invalid hex payload length")?
-            .to_string();
-        out.push(u8::from_str_radix(&byte, 16).map_err(|_| "invalid hex payload")?);
-    }
-    Ok(out)
-}
-
-fn encode_erc20_transfer(recipient: &str, amount: &BigUint) -> Result {
-    let recipient = recipient
-        .trim()
-        .to_lowercase()
-        .strip_prefix("0x")
-        .unwrap_or(recipient)
-        .to_string();
-    let recipient_bytes = payload_hex_to_bytes(&recipient)?;
-
-    let mut data = Vec::with_capacity(68);
-    data.extend_from_slice(&[0xa9, 0x05, 0x9c, 0xbb]); // transfer(address,uint256)
-    data.extend_from_slice(&[0u8; 12]); // left-pad address to 32 bytes
-    data.extend_from_slice(&recipient_bytes);
-
-    let amount_bytes = amount.to_bytes_be();
-    let amount_padding = 32usize.saturating_sub(amount_bytes.len());
-    data.extend(std::iter::repeat_n(0u8, amount_padding));
-    data.extend_from_slice(&amount_bytes);
-
-    Ok(format!(
-        "0x{}",
-        data.iter().map(|b| format!("{:02x}", b)).collect::()
-    ))
-}
-
-pub async fn emit_transfer_voucher(
-    client: &hyper::Client,
-    server_addr: &str,
-    token_address: &str,
-    recipient: &str,
-    amount: &BigUint,
-) -> Result> {
-    let voucher = object! {
-        "destination" => token_address.trim().to_lowercase(),
-        "payload" => encode_erc20_transfer(recipient, amount)?,
-    };
-
-    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()))?;
-
-    let response = client.request(request).await?;
-    Ok(response.status())
-}
-
-pub async fn handle_advance(
-    client: &hyper::Client,
-    server_addr: &str,
-    request: JsonValue,
-) -> Result<&'static str, Box> {
-    let command_raw = std::str::from_utf8(&payload_hex_to_bytes(
-        request["data"]["payload"].as_str().ok_or("Missing payload")?,
-    )?)?;
-    let command = json::parse(command_raw)?;
-
-    let amount = if let Some(v) = command["amount"].as_str() {
-        BigUint::parse_bytes(v.as_bytes(), 10).ok_or("invalid amount")?
-    } else if let Some(v) = command["amount"].as_u64() {
-        BigUint::from(v)
-    } else {
-        return Err("missing amount".into());
-    };
-
-    let recipient = command["recipient"]
-        .as_str()
-        .or_else(|| request["data"]["metadata"]["msg_sender"].as_str())
-        .ok_or("missing recipient")?;
-
-    let status = emit_transfer_voucher(client, server_addr, TOKEN_ADDRESS, recipient, &amount).await?;
-    println!("Voucher POST status={}", status);
-    Ok("accept")
-}
-```
+
+
+
 
+For a full guide, see the Tutorial: [ERC-20 Token Wallet](../tutorials/erc-20-token-wallet.md). + ### Withdrawing Ether To execute Ether withdrawal it is important to emit a voucher with the necessary details as regarding whom you intend to send the Ether to and also the amount to send, nevertheless since the Application contract Executes vouchers by making a [safeCall](https://github.com/cartesi/rollups-contracts/blob/cb52d00ededd2da9f8bf7757710301dccb7d536d/src/library/LibAddress.sol#L18C14-L18C22) to the destination, passing a value (Ether amount to send along with the call) and a payload (function signature to call), it's acceptable to leave the payload section empty if you do not intend to call any functions in the destination address while sending just the specified value of Ether to the destination address. If you intend to call a payable function and also send Ether along, you can add a function signature matching the payable function you intend to call to the payload field. @@ -396,194 +162,42 @@ Below is another sample JavaScript code, this time the voucher structure has bee

-```javascript
-import { stringToHex, encodeFunctionData, erc20Abi, hexToString, zeroHash, parseEther } 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);
-
-
-  let voucher = {
-    destination: sender,
-    payload: zeroHash,
-    value: numberToHex(BigInt(parseEther("1"))).slice(2),
-  };
-
-  await emitVoucher(voucher);
-  return "accept";
-}
-
-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
-  }
-};
-```
+
 

-```python
-import json
-import logging
-import os
-
-import requests
-
-logging.basicConfig(level="INFO")
-logger = logging.getLogger(__name__)
-
-ROLLUP_HTTP_SERVER_URL = os.environ["ROLLUP_HTTP_SERVER_URL"]
-ZERO_HASH = "0x" + ("00" * 32)
-
-
-def payload_hex_to_bytes(payload):
-    if payload.startswith("0x"):
-        payload = payload[2:]
-    return bytes.fromhex(payload)
-
-
-def emit_ether_voucher(recipient, amount_wei):
-    """
-    Emit an Ether transfer voucher.
-
-    Per Cartesi docs for Ether transfer:
-    - destination: recipient address
-    - payload: zero hash (no function call)
-    - value: Ether amount as hex (without 0x prefix)
-    """
-    recipient = recipient.lower().strip()
-    value_hex = hex(int(amount_wei))[2:]
-
-    voucher = {
-        "destination": recipient,
-        "payload": ZERO_HASH,
-        "value": value_hex,
-    }
-    response = requests.post(ROLLUP_HTTP_SERVER_URL + "/voucher", json=voucher)
-    return response.status_code
-
-
-def handle_advance(data):
-    """
-    Expected payload JSON (hex-encoded UTF-8):
-    {"amount_wei":"1000000000000000","recipient":"0x...optional"}
-    """
-    command_raw = payload_hex_to_bytes(data["payload"]).decode("utf-8")
-    command = json.loads(command_raw)
-
-    amount_wei = int(command["amount_wei"])
-    recipient = command.get("recipient") or data["metadata"]["msg_sender"]
-
-    status = emit_ether_voucher(recipient, amount_wei)
-    logger.info("Ether voucher POST status=%s", status)
-    return "accept"
-```
+
 

-```rust 
-use json::{object, JsonValue};
-use num_bigint::BigUint;
-
-fn payload_hex_to_bytes(payload: &str) -> Result, String> {
-    let payload = payload.strip_prefix("0x").unwrap_or(payload);
-    let mut out = Vec::with_capacity(payload.len() / 2);
-    for i in (0..payload.len()).step_by(2) {
-        let byte = payload
-            .get(i..i + 2)
-            .ok_or("invalid hex payload length")?
-            .to_string();
-        out.push(u8::from_str_radix(&byte, 16).map_err(|_| "invalid hex payload")?);
-    }
-    Ok(out)
-}
-
-fn encode_ether_value(amount: &BigUint) -> String {
-    let bytes = amount.to_bytes_be();
-    let mut padded = vec![0u8; 32usize.saturating_sub(bytes.len())];
-    padded.extend_from_slice(&bytes);
-    format!(
-        "0x{}",
-        padded
-            .iter()
-            .map(|b| format!("{:02x}", b))
-            .collect::()
-    )
-}
-
-pub async fn emit_ether_transfer_voucher(
-    client: &hyper::Client,
-    server_addr: &str,
-    recipient: &str,
-    amount: &BigUint,
-) -> Result> {
-    let voucher = object! {
-        "destination" => recipient.trim().to_lowercase(),
-        "payload" => "0x",
-        "value" => encode_ether_value(amount),
-    };
-
-    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()))?;
-
-    let response = client.request(request).await?;
-    Ok(response.status())
-}
-
-pub async fn handle_advance(
-    client: &hyper::Client,
-    server_addr: &str,
-    request: JsonValue,
-) -> Result<&'static str, Box> {
-    let command_raw = std::str::from_utf8(&payload_hex_to_bytes(
-        request["data"]["payload"].as_str().ok_or("Missing payload")?,
-    )?)?;
-    let command = json::parse(command_raw)?;
-
-    let amount = if let Some(v) = command["amount"].as_str() {
-        BigUint::parse_bytes(v.as_bytes(), 10).ok_or("invalid amount")?
-    } else if let Some(v) = command["amount"].as_u64() {
-        BigUint::from(v)
-    } else {
-        return Err("missing amount".into());
-    };
-
-    let recipient = command["recipient"]
-        .as_str()
-        .or_else(|| request["data"]["metadata"]["msg_sender"].as_str())
-        .ok_or("missing recipient")?;
-
-    let status = emit_ether_transfer_voucher(client, server_addr, recipient, &amount).await?;
-    println!("Voucher POST status={}", status);
-    Ok("accept")
-}
-```
+
+
+
+ + +

+
+
+
+
+
+ + +

+
+
+
 
+For a full guide, see the Tutorial: [Ether Wallet](../tutorials/ether-wallet.md). + :::note epoch length By default, Cartesi nodes close one epoch every 7200 blocks. You can manually set the epoch length to facilitate quicker asset-handling methods. ::: diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_cpp.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_cpp.md new file mode 100644 index 00000000..ef37aa39 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_cpp.md @@ -0,0 +1,65 @@ +```cpp +#include +#include +#include + +struct Erc20Deposit +{ + std::string token; + std::string sender; + std::string amount_hex; + std::vector exec_layer_data; +}; + +std::vector hex_to_bytes(const std::string &payload_hex) +{ + std::string payload = payload_hex; + if (payload.rfind("0x", 0) == 0) + { + payload = payload.substr(2); + } + if (payload.size() % 2 != 0) + { + throw std::runtime_error("invalid hex payload length"); + } + + std::vector out; + out.reserve(payload.size() / 2); + for (size_t i = 0; i < payload.size(); i += 2) + { + out.push_back(static_cast(std::stoul(payload.substr(i, 2), nullptr, 16))); + } + return out; +} + +std::string bytes_to_hex(const std::vector &bytes, size_t start, size_t end) +{ + static const char *kHex = "0123456789abcdef"; + std::string out = "0x"; + out.reserve((end - start) * 2 + 2); + for (size_t i = start; i < end; ++i) + { + out.push_back(kHex[(bytes[i] >> 4) & 0x0F]); + out.push_back(kHex[bytes[i] & 0x0F]); + } + return out; +} + +Erc20Deposit decode_erc20_deposit(const std::string &payload_hex) +{ + const auto raw = hex_to_bytes(payload_hex); + + // token(20) + sender(20) + amount(32) = 72 bytes + if (raw.size() < 72) + { + throw std::runtime_error("invalid ERC-20 deposit payload"); + } + + Erc20Deposit out; + out.token = bytes_to_hex(raw, 0, 20); + out.sender = bytes_to_hex(raw, 20, 40); + out.amount_hex = bytes_to_hex(raw, 40, 72); + out.exec_layer_data = std::vector(raw.begin() + 72, raw.end()); + return out; +} +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_go.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_go.md new file mode 100644 index 00000000..ee7c75d8 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_go.md @@ -0,0 +1,37 @@ +```go +package main + +import ( + "encoding/hex" + "fmt" + "math/big" + "strings" +) + +type Erc20Deposit struct { + Token string + Sender string + Amount *big.Int + ExecLayerData []byte +} + +func DecodeErc20Deposit(payloadHex string) (*Erc20Deposit, error) { + payload := strings.TrimPrefix(payloadHex, "0x") + raw, err := hex.DecodeString(payload) + if err != nil { + return nil, fmt.Errorf("invalid hex payload: %w", err) + } + + // token(20) + sender(20) + amount(32) = 72 bytes + if len(raw) < 72 { + return nil, fmt.Errorf("invalid ERC-20 deposit payload") + } + + return &Erc20Deposit{ + Token: "0x" + hex.EncodeToString(raw[0:20]), + Sender: "0x" + hex.EncodeToString(raw[20:40]), + Amount: new(big.Int).SetBytes(raw[40:72]), + ExecLayerData: raw[72:], + }, nil +} +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_js.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_js.md new file mode 100644 index 00000000..b14d4607 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_js.md @@ -0,0 +1,27 @@ +```javascript +function decodeErc20Deposit(payloadHex) { + if (typeof payloadHex !== "string") { + throw new TypeError("payload must be a hex string"); + } + + const payload = payloadHex.startsWith("0x") ? payloadHex.slice(2) : payloadHex; + const raw = Buffer.from(payload, "hex"); + + // token(20) + sender(20) + amount(32) = 72 bytes + if (raw.length < 72) { + throw new Error("invalid ERC-20 deposit payload"); + } + + const token = `0x${raw.subarray(0, 20).toString("hex")}`.toLowerCase().trim(); + const sender = `0x${raw.subarray(20, 40).toString("hex")}`.toLowerCase().trim(); + const amount = BigInt(`0x${raw.subarray(40, 72).toString("hex")}`); + const exec_layer_data = raw.subarray(72); + + return { + token, + sender, + amount, + exec_layer_data, + }; +} +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_py.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_py.md new file mode 100644 index 00000000..ac3bc0d3 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_py.md @@ -0,0 +1,24 @@ +```python +def decode_erc20_deposit(payload_hex): + """ + Decode a Cartesi Rollups ERC-20 deposit payload. + """ + payload = payload_hex[2:] if payload_hex.startswith("0x") else payload_hex + raw = bytes.fromhex(payload) + + # Minimum size is token(20) + sender(20) + amount(32) = 72 bytes. + if len(raw) < 72: + raise ValueError("invalid ERC-20 deposit payload") + + token = ("0x" + raw[0:20].hex()).lower().strip() + sender = ("0x" + raw[20:40].hex()).lower().strip() + amount = int.from_bytes(raw[40:72], byteorder="big") + exec_layer_data = raw[72:] + + return { + "token": token, + "sender": sender, + "amount": amount, + "exec_layer_data": exec_layer_data, + } +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_rs.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_rs.md new file mode 100644 index 00000000..7a71b3ae --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_decode_erc20_rs.md @@ -0,0 +1,51 @@ +```rust +use num_bigint::BigUint; + +#[derive(Debug)] +pub struct Erc20Deposit { + pub token: String, + pub sender: String, + pub amount: BigUint, + pub exec_layer_data: Vec, +} + +pub fn decode_erc20_deposit(payload_hex: &str) -> Result { + let payload = payload_hex.strip_prefix("0x").unwrap_or(payload_hex); + let mut raw = Vec::with_capacity(payload.len() / 2); + for i in (0..payload.len()).step_by(2) { + let byte = payload + .get(i..i + 2) + .ok_or("invalid hex payload length")? + .to_string(); + raw.push(u8::from_str_radix(&byte, 16).map_err(|_| "invalid hex payload")?); + } + + if raw.len() < 72 { + return Err("invalid ERC-20 deposit payload".to_string()); + } + + let token = format!( + "0x{}", + raw[0..20] + .iter() + .map(|b| format!("{:02x}", b)) + .collect::() + ); + let sender = format!( + "0x{}", + raw[20..40] + .iter() + .map(|b| format!("{:02x}", b)) + .collect::() + ); + let amount = BigUint::from_bytes_be(&raw[40..72]); + let exec_layer_data = raw[72..].to_vec(); + + Ok(Erc20Deposit { + token, + sender, + amount, + exec_layer_data, + }) +} +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_cpp.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_cpp.md new file mode 100644 index 00000000..6661bf1d --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_cpp.md @@ -0,0 +1,39 @@ +```cpp +#include +#include + +#include "3rdparty/cpp-httplib/httplib.h" +#include "3rdparty/picojson/picojson.h" + +static const std::string kErc20TokenAddress = "0x784f0c076CC55EAD0a585a9A13e57c467c91Dc3a"; + +std::string handle_advance(httplib::Client &cli, picojson::value data) +{ + std::cout << "Received advance request data " << data << std::endl; + + const std::string recipient = data.get("metadata").get("msg_sender").get(); + const std::string call_data = + std::string("0xa9059cbb") + + "000000000000000000000000" + recipient.substr(2) + + "000000000000000000000000000000000000000000000000000000000000000a"; + + picojson::object voucher; + voucher["destination"] = picojson::value(kErc20TokenAddress); + voucher["payload"] = picojson::value(call_data); + voucher["value"] = picojson::value("0x0"); + + auto response = cli.Post( + "/voucher", + picojson::value(voucher).serialize(), + "application/json" + ); + + if (!response || response->status >= 400) + { + std::cout << "Failed to send voucher" << std::endl; + return "reject"; + } + + return "accept"; +} +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_go.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_go.md new file mode 100644 index 00000000..a6060182 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_go.md @@ -0,0 +1,38 @@ +```go +package main + +import ( + "fmt" + "strings" + + "dapp/rollups" +) + +const erc20TokenAddress = "0x784f0c076CC55EAD0a585a9A13e57c467c91Dc3a" + +func encodeErc20Transfer(recipient string, amount uint64) string { + recipientHex := strings.TrimPrefix(strings.ToLower(strings.TrimSpace(recipient)), "0x") + recipientPadded := fmt.Sprintf("%064s", recipientHex) + recipientPadded = strings.ReplaceAll(recipientPadded, " ", "0") + amountPadded := fmt.Sprintf("%064x", amount) + return "0xa9059cbb" + recipientPadded + amountPadded +} + +func HandleAdvance(data *rollups.AdvanceResponse) error { + // In this example we transfer 10 tokens to the input sender. + recipient := data.Metadata.MsgSender + callData := encodeErc20Transfer(recipient, 10) + + voucher := rollups.VoucherRequest{ + Destination: erc20TokenAddress, + Payload: callData, + Value: "0x0", + } + + if _, err := rollups.SendVoucher(&voucher); err != nil { + return fmt.Errorf("HandleAdvance: failed sending voucher: %w", err) + } + + return nil +} +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_js.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_js.md new file mode 100644 index 00000000..c1af396d --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_js.md @@ -0,0 +1,43 @@ +```javascript +import { 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 + + const call = encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [sender, BigInt(10)], + }); + + const voucher = { + destination: erc20Token, + payload: call, + value: zeroHash, + }; + + await emitVoucher(voucher); + return "accept"; +} + +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. + } +}; +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_py.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_py.md new file mode 100644 index 00000000..1dc18e52 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_py.md @@ -0,0 +1,51 @@ +```python +import json +import logging +import os + +import requests + +logging.basicConfig(level="INFO") +logger = logging.getLogger(__name__) + +ROLLUP_HTTP_SERVER_URL = os.environ["ROLLUP_HTTP_SERVER_URL"] +TOKEN_ADDRESS = "0x5138f529b77b4e0a7c84b77e79c4335d31938fed" + + +def payload_hex_to_bytes(payload): + if payload.startswith("0x"): + payload = payload[2:] + return bytes.fromhex(payload) + + +def encode_erc20_transfer(recipient, amount): + recipient = recipient.lower().strip() + if recipient.startswith("0x"): + recipient = recipient[2:] + + selector = bytes.fromhex("a9059cbb") # transfer(address,uint256) + recipient_word = bytes.fromhex("00" * 12 + recipient) + amount_word = int(amount).to_bytes(32, byteorder="big") + return "0x" + (selector + recipient_word + amount_word).hex() + + +def emit_transfer_voucher(token_address, recipient, amount): + voucher = { + "destination": token_address.lower().strip(), + "payload": encode_erc20_transfer(recipient, amount), + } + response = requests.post(ROLLUP_HTTP_SERVER_URL + "/voucher", json=voucher) + return response.status_code + + +def handle_advance(data): + command_raw = payload_hex_to_bytes(data["payload"]).decode("utf-8") + command = json.loads(command_raw) + + amount = int(command["amount"]) + recipient = command.get("recipient") or data["metadata"]["msg_sender"] + + status = emit_transfer_voucher(TOKEN_ADDRESS, recipient, amount) + logger.info("Voucher POST status=%s", status) + return "accept" +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_rs.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_rs.md new file mode 100644 index 00000000..b5ac71f5 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_rs.md @@ -0,0 +1,94 @@ +```rust +use json::{object, JsonValue}; +use num_bigint::BigUint; + +pub const TOKEN_ADDRESS: &str = "0x5138f529b77b4e0a7c84b77e79c4335d31938fed"; + +fn payload_hex_to_bytes(payload: &str) -> Result, String> { + let payload = payload.strip_prefix("0x").unwrap_or(payload); + let mut out = Vec::with_capacity(payload.len() / 2); + for i in (0..payload.len()).step_by(2) { + let byte = payload + .get(i..i + 2) + .ok_or("invalid hex payload length")? + .to_string(); + out.push(u8::from_str_radix(&byte, 16).map_err(|_| "invalid hex payload")?); + } + Ok(out) +} + +fn encode_erc20_transfer(recipient: &str, amount: &BigUint) -> Result { + let recipient = recipient + .trim() + .to_lowercase() + .strip_prefix("0x") + .unwrap_or(recipient) + .to_string(); + let recipient_bytes = payload_hex_to_bytes(&recipient)?; + + let mut data = Vec::with_capacity(68); + data.extend_from_slice(&[0xa9, 0x05, 0x9c, 0xbb]); // transfer(address,uint256) + data.extend_from_slice(&[0u8; 12]); // left-pad address to 32 bytes + data.extend_from_slice(&recipient_bytes); + + let amount_bytes = amount.to_bytes_be(); + let amount_padding = 32usize.saturating_sub(amount_bytes.len()); + data.extend(std::iter::repeat_n(0u8, amount_padding)); + data.extend_from_slice(&amount_bytes); + + Ok(format!( + "0x{}", + data.iter().map(|b| format!("{:02x}", b)).collect::() + )) +} + +pub async fn emit_transfer_voucher( + client: &hyper::Client, + server_addr: &str, + token_address: &str, + recipient: &str, + amount: &BigUint, +) -> Result> { + let voucher = object! { + "destination" => token_address.trim().to_lowercase(), + "payload" => encode_erc20_transfer(recipient, amount)?, + }; + + 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()))?; + + let response = client.request(request).await?; + Ok(response.status()) +} + +pub async fn handle_advance( + client: &hyper::Client, + server_addr: &str, + request: JsonValue, +) -> Result<&'static str, Box> { + let command_raw = std::str::from_utf8(&payload_hex_to_bytes( + request["data"]["payload"].as_str().ok_or("Missing payload")?, + )?)?; + let command = json::parse(command_raw)?; + + let amount = if let Some(v) = command["amount"].as_str() { + BigUint::parse_bytes(v.as_bytes(), 10).ok_or("invalid amount")? + } else if let Some(v) = command["amount"].as_u64() { + BigUint::from(v) + } else { + return Err("missing amount".into()); + }; + + let recipient = command["recipient"] + .as_str() + .or_else(|| request["data"]["metadata"]["msg_sender"].as_str()) + .ok_or("missing recipient")?; + + let status = emit_transfer_voucher(client, server_addr, TOKEN_ADDRESS, recipient, &amount).await?; + println!("Voucher POST status={}", status); + Ok("accept") +} +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_cpp.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_cpp.md new file mode 100644 index 00000000..dca215b7 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_cpp.md @@ -0,0 +1,37 @@ +```cpp +#include +#include + +#include "3rdparty/cpp-httplib/httplib.h" +#include "3rdparty/picojson/picojson.h" + +static const std::string kZeroHash32 = + "0x0000000000000000000000000000000000000000000000000000000000000000"; + +std::string handle_advance(httplib::Client &cli, picojson::value data) +{ + std::cout << "Received advance request data " << data << std::endl; + + // Sample withdrawal: send 1 ETH (in wei) back to msg_sender. + const std::string recipient = data.get("metadata").get("msg_sender").get(); + const std::string one_eth_wei_hex = "de0b6b3a7640000"; + + picojson::object voucher; + voucher["destination"] = picojson::value(recipient); + voucher["payload"] = picojson::value(kZeroHash32); + voucher["value"] = picojson::value(one_eth_wei_hex); + + auto response = cli.Post( + "/voucher", + picojson::value(voucher).serialize(), + "application/json" + ); + if (!response || response->status >= 400) + { + std::cout << "Failed to send Ether voucher" << std::endl; + return "reject"; + } + + return "accept"; +} +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_go.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_go.md new file mode 100644 index 00000000..5be429dc --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_go.md @@ -0,0 +1,56 @@ +```go +package main + +import ( + "encoding/json" + "fmt" + "math/big" + "strings" + + "dapp/rollups" +) + +const zeroHash32 = "0x0000000000000000000000000000000000000000000000000000000000000000" + +type EtherWithdrawCommand struct { + AmountWei string `json:"amount_wei"` + Recipient string `json:"recipient"` +} + +func emitEtherVoucher(recipient string, amountWei *big.Int) error { + voucher := rollups.VoucherRequest{ + Destination: strings.ToLower(strings.TrimSpace(recipient)), + Payload: zeroHash32, + Value: fmt.Sprintf("%x", amountWei), + } + _, err := rollups.SendVoucher(&voucher) + return err +} + +func HandleAdvance(data *rollups.AdvanceResponse) error { + decoded, err := rollups.Hex2Str(data.Payload) + if err != nil { + return fmt.Errorf("HandleAdvance: failed to decode payload: %w", err) + } + + var cmd EtherWithdrawCommand + if err := json.Unmarshal([]byte(decoded), &cmd); err != nil { + return fmt.Errorf("HandleAdvance: invalid payload JSON: %w", err) + } + + amountWei, ok := new(big.Int).SetString(cmd.AmountWei, 10) + if !ok { + return fmt.Errorf("HandleAdvance: invalid amount_wei") + } + + recipient := cmd.Recipient + if recipient == "" { + recipient = data.Metadata.MsgSender + } + + if err := emitEtherVoucher(recipient, amountWei); err != nil { + return fmt.Errorf("HandleAdvance: failed sending voucher: %w", err) + } + return nil +} +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_js.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_js.md new file mode 100644 index 00000000..c101fb38 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_js.md @@ -0,0 +1,36 @@ +```javascript +import { hexToString, numberToHex, parseEther, 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 voucher = { + destination: sender, + payload: zeroHash, + value: numberToHex(BigInt(parseEther("1"))).slice(2), + }; + + await emitVoucher(voucher); + return "accept"; +} + +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. + } +}; +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_py.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_py.md new file mode 100644 index 00000000..e579388c --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_py.md @@ -0,0 +1,56 @@ +```python +import json +import logging +import os + +import requests + +logging.basicConfig(level="INFO") +logger = logging.getLogger(__name__) + +ROLLUP_HTTP_SERVER_URL = os.environ["ROLLUP_HTTP_SERVER_URL"] +ZERO_HASH = "0x" + ("00" * 32) + + +def payload_hex_to_bytes(payload): + if payload.startswith("0x"): + payload = payload[2:] + return bytes.fromhex(payload) + + +def emit_ether_voucher(recipient, amount_wei): + """ + Emit an Ether transfer voucher. + + Per Cartesi docs for Ether transfer: + - destination: recipient address + - payload: zero hash (no function call) + - value: Ether amount as hex (without 0x prefix) + """ + recipient = recipient.lower().strip() + value_hex = hex(int(amount_wei))[2:] + + voucher = { + "destination": recipient, + "payload": ZERO_HASH, + "value": value_hex, + } + response = requests.post(ROLLUP_HTTP_SERVER_URL + "/voucher", json=voucher) + return response.status_code + + +def handle_advance(data): + """ + Expected payload JSON (hex-encoded UTF-8): + {"amount_wei":"1000000000000000","recipient":"0x...optional"} + """ + command_raw = payload_hex_to_bytes(data["payload"]).decode("utf-8") + command = json.loads(command_raw) + + amount_wei = int(command["amount_wei"]) + recipient = command.get("recipient") or data["metadata"]["msg_sender"] + + status = emit_ether_voucher(recipient, amount_wei) + logger.info("Ether voucher POST status=%s", status) + return "accept" +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_rs.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_rs.md new file mode 100644 index 00000000..d5ff272a --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_ether_rs.md @@ -0,0 +1,80 @@ +```rust +use json::{object, JsonValue}; +use num_bigint::BigUint; + +fn payload_hex_to_bytes(payload: &str) -> Result, String> { + let payload = payload.strip_prefix("0x").unwrap_or(payload); + let mut out = Vec::with_capacity(payload.len() / 2); + for i in (0..payload.len()).step_by(2) { + let byte = payload + .get(i..i + 2) + .ok_or("invalid hex payload length")? + .to_string(); + out.push(u8::from_str_radix(&byte, 16).map_err(|_| "invalid hex payload")?); + } + Ok(out) +} + +fn encode_ether_value(amount: &BigUint) -> String { + let bytes = amount.to_bytes_be(); + let mut padded = vec![0u8; 32usize.saturating_sub(bytes.len())]; + padded.extend_from_slice(&bytes); + format!( + "0x{}", + padded + .iter() + .map(|b| format!("{:02x}", b)) + .collect::() + ) +} + +pub async fn emit_ether_transfer_voucher( + client: &hyper::Client, + server_addr: &str, + recipient: &str, + amount: &BigUint, +) -> Result> { + let voucher = object! { + "destination" => recipient.trim().to_lowercase(), + "payload" => "0x", + "value" => encode_ether_value(amount), + }; + + 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()))?; + + let response = client.request(request).await?; + Ok(response.status()) +} + +pub async fn handle_advance( + client: &hyper::Client, + server_addr: &str, + request: JsonValue, +) -> Result<&'static str, Box> { + let command_raw = std::str::from_utf8(&payload_hex_to_bytes( + request["data"]["payload"].as_str().ok_or("Missing payload")?, + )?)?; + let command = json::parse(command_raw)?; + + let amount = if let Some(v) = command["amount"].as_str() { + BigUint::parse_bytes(v.as_bytes(), 10).ok_or("invalid amount")? + } else if let Some(v) = command["amount"].as_u64() { + BigUint::from(v) + } else { + return Err("missing amount".into()); + }; + + let recipient = command["recipient"] + .as_str() + .or_else(|| request["data"]["metadata"]["msg_sender"].as_str()) + .ok_or("missing recipient")?; + + let status = emit_ether_transfer_voucher(client, server_addr, recipient, &amount).await?; + println!("Voucher POST status={}", status); + Ok("accept") +} +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/implementing_outputs_cpp.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/implementing_outputs_cpp.md index b07c3633..5ede64a6 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/implementing_outputs_cpp.md +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/implementing_outputs_cpp.md @@ -7,13 +7,13 @@ #include "3rdparty/cpp-httplib/httplib.h" #include "3rdparty/picojson/picojson.h" -namespace sample_config +namespace voucher_config { -// Sample values for voucher emission. -const std::string kSampleErc20Token = "0x0000000000000000000000000000000000000001"; -const std::string kSampleRecipient = "0x1111111111111111111111111111111111111111"; -const std::string kSampleAmount1e18Hex = "0de0b6b3a7640000"; -} // namespace sample_config +// Sample values used to build the voucher payload. +const std::string kVoucherTokenAddress = "0x5138f529B77B4e0a7c84B77E79c4335D31938fed"; +const std::string kVoucherRecipientAddress = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"; +const std::string kVoucherAmountWeiHex = "0de0b6b3a7640000"; +} // namespace voucher_config std::string get_input_payload(const picojson::value &data) { @@ -50,14 +50,14 @@ void post_payload(httplib::Client &cli, const std::string &endpoint, const std:: void post_sample_erc20_transfer_voucher(httplib::Client &cli) { const std::string recipient_padded = - "000000000000000000000000" + sample_config::kSampleRecipient.substr(2); + "000000000000000000000000" + voucher_config::kVoucherRecipientAddress.substr(2); const std::string amount_padded = - "000000000000000000000000000000000000000000000000" + sample_config::kSampleAmount1e18Hex; + "000000000000000000000000000000000000000000000000" + voucher_config::kVoucherAmountWeiHex; // ERC-20 transfer(address,uint256) selector. const std::string payload = "0xa9059cbb" + recipient_padded + amount_padded; picojson::object body; - body["destination"] = picojson::value(sample_config::kSampleErc20Token); + body["destination"] = picojson::value(voucher_config::kVoucherTokenAddress); body["payload"] = picojson::value(payload); auto res = cli.Post("/voucher", picojson::value(body).serialize(), "application/json"); if (res) diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/counter.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/counter.md index 803616a0..f42c87e0 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/tutorials/counter.md +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/counter.md @@ -8,7 +8,7 @@ resources: This tutorial aims to guide you through creating and interacting with a basic Cartesi application, it'll take you through setting up your dev environment, creating a project then finally running and interacting with your application locally. -We would also be providing the Rust, JavaScript, Python and Go implementation of the application, so you could choose whichever language you're more conversant with. +We provide Rust, JavaScript, Python, Go, and C++ implementations of the application, so you can choose whichever language you're more comfortable with. ## Set up your environment @@ -53,11 +53,31 @@ cartesi create counter --template python cartesi create counter --template rust ``` + + + + +

+
+```shell
+cartesi create counter --template go
+```
+
+
+
+ + +

+
+```shell
+cartesi create counter --template cpp
+```
+
 
-This command creates a directory called `counter` and depending on your selected language this directory would contain the necessary entry point file to start your application, for Python developers this would be `dapp.py` while for Rust users it would be `src/main.rs`, then finally for JavaScript users, the entry point file would be `src/index.js`. +This command creates a directory called `counter` and, depending on your selected language, this directory contains the entry point file to start your application (for example: `dapp.py` for Python, `src/main.rs` for Rust, `src/index.js` for JavaScript, `main.go` for Go, and `src/main.cpp` for C++). This entry point file contains the default template for interacting with the Cartesi Rollups HTTP Server, it also makes available, two function namely `handle_advance()` and `handle_inspect()` which process "advance / write" and "inspect / read" requests to the application. In the next section we would be updating these functions with the implementation for your application. @@ -74,6 +94,8 @@ To try it locally, copy the snippet for your language and replace the contents o import CounterJS from './snippets/counter-js.md'; import CounterPY from './snippets/counter-py.md'; import CounterRS from './snippets/counter-rs.md'; +import CounterGO from './snippets/counter-go.md'; +import CounterCPP from './snippets/counter-cpp.md'; @@ -97,6 +119,22 @@ import CounterRS from './snippets/counter-rs.md'; + + + + +

+
+
+
+
+
+ + +

+
+
+
 
diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/counter-cpp.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/counter-cpp.md new file mode 100644 index 00000000..50a2a159 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/counter-cpp.md @@ -0,0 +1,83 @@ +```cpp +#include +#include +#include + +#include "3rdparty/cpp-httplib/httplib.h" +#include "3rdparty/picojson/picojson.h" + +class Counter +{ +public: + Counter() : value_(0) {} + + int increment() + { + ++value_; + return value_; + } + + int get() const + { + return value_; + } + +private: + int value_; +}; + +Counter counter; + +std::string handle_advance(httplib::Client &cli, picojson::value data) +{ + std::cout << "Received advance request data " << data << std::endl; + const int new_value = counter.increment(); + std::cout << "Counter increment requested, new count value: " << new_value << std::endl; + return "accept"; +} + +std::string handle_inspect(httplib::Client &cli, picojson::value data) +{ + std::cout << "Received inspect request data " << data << std::endl; + std::cout << "Current counter value: " << counter.get() << std::endl; + return "accept"; +} + +int main(int argc, char **argv) +{ + std::map handlers = { + {std::string("advance_state"), &handle_advance}, + {std::string("inspect_state"), &handle_inspect}, + }; + + httplib::Client cli(getenv("ROLLUP_HTTP_SERVER_URL")); + std::string status = "accept"; + + while (true) + { + std::cout << "Sending finish" << std::endl; + auto finish = std::string("{\"status\":\"") + status + std::string("\"}"); + auto response = cli.Post("/finish", finish, "application/json"); + if (!response) + { + std::cout << "Failed to call /finish" << std::endl; + status = "reject"; + continue; + } + + std::cout << "Received finish status " << response->status << std::endl; + if (response->status == 202) + { + std::cout << "No pending rollup request, trying again" << std::endl; + status = "accept"; + continue; + } + + picojson::value rollup_request; + picojson::parse(rollup_request, response->body); + auto request_type = rollup_request.get("request_type").get(); + auto data = rollup_request.get("data"); + status = handlers.find(request_type)->second(cli, data); + } +} +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/counter-go.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/counter-go.md new file mode 100644 index 00000000..689bfa82 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/counter-go.md @@ -0,0 +1,77 @@ +```go +package main + +import ( + "encoding/json" + + "dapp/rollups" +) + +type Counter struct { + value int +} + +func (c *Counter) Increment() int { + c.value++ + return c.value +} + +func (c *Counter) Get() int { + return c.value +} + +var counter = &Counter{} + +func HandleAdvance(data *rollups.AdvanceResponse) error { + infolog.Printf("Received advance request data %+v\n", data) + newVal := counter.Increment() + infolog.Printf("Counter increment requested, new count value: %d\n", newVal) + return nil +} + +func HandleInspect(data *rollups.InspectResponse) error { + infolog.Printf("Received inspect request data %+v\n", data) + infolog.Printf("Current counter value: %d\n", counter.Get()) + return nil +} + +func main() { + finish := rollups.FinishRequest{Status: "accept"} + + for { + infolog.Println("Sending finish") + res, err := rollups.SendFinish(&finish) + if err != nil { + errlog.Panicln("Error calling /finish:", err) + } + + if res.StatusCode == 202 { + infolog.Println("No pending rollup request, trying again") + continue + } + + response, err := rollups.ParseFinishResponse(res) + if err != nil { + errlog.Panicln("Error parsing finish response:", err) + } + + finish.Status = "accept" + if response.Type == "advance_state" { + data := new(rollups.AdvanceResponse) + _ = json.Unmarshal(response.Data, data) + if err = HandleAdvance(data); err != nil { + finish.Status = "reject" + } + } else if response.Type == "inspect_state" { + data := new(rollups.InspectResponse) + _ = json.Unmarshal(response.Data, data) + if err = HandleInspect(data); err != nil { + finish.Status = "reject" + } + } else { + finish.Status = "reject" + errlog.Printf("Unknown request type: %s\n", response.Type) + } + } +} +``` From b478a51aed1f075b0124691aa54e33e0bd15e706 Mon Sep 17 00:00:00 2001 From: Shaheen Date: Wed, 25 Feb 2026 22:00:28 +0530 Subject: [PATCH 4/6] added code for tutorial examples - calculator and marketplace --- .../version-2.0/development/asset-handling.md | 6 +- .../version-2.0/tutorials/calculator.md | 356 +++++++++--------- .../version-2.0/tutorials/marketplace.md | 62 ++- .../tutorials/snippets/calculator-cpp.md | 136 +++++++ .../tutorials/snippets/calculator-go.md | 91 +++++ .../tutorials/snippets/calculator-js.md | 78 ++++ .../tutorials/snippets/calculator-py.md | 71 ++++ .../tutorials/snippets/calculator-rs.md | 102 +++++ .../tutorials/snippets/marketplace-cpp.md | 251 ++++++++++++ .../tutorials/snippets/marketplace-go.md | 215 +++++++++++ 10 files changed, 1188 insertions(+), 180 deletions(-) create mode 100644 cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-cpp.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-go.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-js.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-py.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-rs.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-cpp.md create mode 100644 cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-go.md diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md b/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md index 5dc1252e..bba2e360 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md +++ b/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md @@ -40,7 +40,7 @@ Deposit input payloads are always specified as packed ABI-encoded parameters, as | ERC-1155 (single) |
  • `address token`,
  • `address sender`,
  • `uint256 tokenId`,
  • `uint256 value`,
  • standard ABI-encoded fields...
|
  • `bytes baseLayerData`,
  • `bytes execLayerData`
| | ERC-1155 (batch) |
  • `address token`,
  • `address sender`,
  • standard ABI-encoded fields...
|
  • `uint256[] tokenIds`,
  • `uint256[] values`,
  • `bytes baseLayerData`,
  • `bytes execLayerData`
| -Refer to the functions provided below to understand how to handle asset deposits. These functions when called inside the `handle_advance` function of your application will help you decode the payload for the asset type being deposited. +Refer to the functions provided below to understand how to handle asset deposits. When called inside your application's advance handler, these helpers decode the payload for the deposited asset type. For example, to decode an ERC-20 deposit payload, you can use the following code snippets: @@ -113,7 +113,7 @@ The application’s off-chain layer often requires knowledge of its address to f Next, the off-chain machine uses the address of the application on the base layer to generate a voucher for execution at the executeOutput() function of the Application contract. This address is known to the offchain machine because it is embedded in the metadata of every input sent to the application, though the developer will need to implement extra logic fetch this address from the metadata then properly store and retrieve it when needed in situations like generating the above Voucher. -Below is a sample JavaScript code with the implementations to transfer tokens to whoever calls the application, notice that the `const call` variable is an encoded function data containing the token contract ABI, function name and also arguments like recipient and amount, while the actual `voucher` structure itself contains a destination (erc20 token contract where the transfer execution should occur), the payload (encoded function data in `call`) and finally a value field which is initialized to `0` meaning no Ether is intended to be sent alongside this transfer request. +Below are multi-language examples showing how to transfer tokens to whoever calls the application. In each case, the encoded call data contains the token contract function and arguments (for example, recipient and amount). The voucher then includes a destination (the ERC-20 token contract), a payload (the encoded call data), and a value set to zero to indicate no Ether is sent with this transfer request. @@ -157,7 +157,7 @@ For a full guide, see the Tutorial: [ERC-20 Token Wallet](../tutorials/erc-20-to To execute Ether withdrawal it is important to emit a voucher with the necessary details as regarding whom you intend to send the Ether to and also the amount to send, nevertheless since the Application contract Executes vouchers by making a [safeCall](https://github.com/cartesi/rollups-contracts/blob/cb52d00ededd2da9f8bf7757710301dccb7d536d/src/library/LibAddress.sol#L18C14-L18C22) to the destination, passing a value (Ether amount to send along with the call) and a payload (function signature to call), it's acceptable to leave the payload section empty if you do not intend to call any functions in the destination address while sending just the specified value of Ether to the destination address. If you intend to call a payable function and also send Ether along, you can add a function signature matching the payable function you intend to call to the payload field. -Below is another sample JavaScript code, this time the voucher structure has been modified to send ether to an address instead of calling a function in a smart contract, notice there is no `encodedFunctionData`, so the payload section is initialized to zeroHash. +Below are multi-language examples for Ether withdrawal. Here, the voucher sends Ether directly to an address instead of calling a token contract function, so there is no encoded function call payload. diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/calculator.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/calculator.md index 23fb6411..5daf93fd 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/tutorials/calculator.md +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/calculator.md @@ -8,11 +8,7 @@ resources: In this tutorial, we will build a simple Calculator dApp to illustrate how requests are sent and processed within Cartesi Rollups Infrastructure. -This dApp will be written in Python. - -:::note source code -The backend will be written using Python. For added flexibility, feel free to explore [the JavaScript version here](https://github.com/Mugen-Builders/calculator/tree/main/javascript). -::: +We provide JavaScript, Python, Rust, Go, and C++ implementations so you can use your preferred backend language. ## Set up your environment @@ -22,226 +18,234 @@ Install these to set up your environment for quick building: - Docker Desktop 4.x: The tool you need to run the Cartesi Machine and its dependencies. [Install Docker for your OS of choice](https://www.docker.com/products/docker-desktop/). -- Python 3.x: This is used to write your backend application logic. [Install Python3 here](https://www.python.org/downloads/). - ## Create the backend application -To create a backend application with Python, run: +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +To create a calculator backend, run the template command for your language: + + + +

+
+```shell
+cartesi create calculator --template javascript
+```
+
+
+
+ + +

 
 ```shell
 cartesi create calculator --template python
 ```
 
-This creates a `calculator/` directory with essential files for your Python development.
+
+
-- `Dockerfile`: Contains configurations to boot a complete Cartesi machine. + +

 
-- `README.md`: A markdown file.
+```shell
+cartesi create calculator --template rust
+```
 
-- `dapp.py`: A Python file with template backend code that serves as your application's endpoint.
+
+
-- `requirements.txt`: The Python dependencies required for your application. + +

 
-Let’s review the backend code in the `dapp.py` file.
+```shell
+cartesi create calculator --template go
+```
 
-```python
-from os import environ
-import logging
-import requests
+
+
-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}")
+```shell
+cartesi create calculator --template cpp
+```
 
-def handle_advance(data):
-    logger.info(f"Received advance request data {data}")
-    logger.info("Adding notice")
-    notice = {"payload": data["payload"]}
-    response = requests.post(rollup_server + "/notice", json=notice)
-    logger.info(f"Received notice status {response.status_code} body {response.content}")
-    return "accept"
+
+
+
-def handle_inspect(data): - logger.info(f"Received inspect request data {data}") - logger.info("Adding report") - report = {"payload": data["payload"]} - response = requests.post(rollup_server + "/report", json=report) - logger.info(f"Received report status {response.status_code}") - return "accept" +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`). -handlers = { - "advance_state": handle_advance, - "inspect_state": handle_inspect, -} +## Review the default backend template -finish = {"status": "accept"} +Before implementing the calculator logic, review the default rollup request loop generated by the templates: -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"] +import RequestHandlingJS from '../development/snippets/request_handling_js.md'; +import RequestHandlingPY from '../development/snippets/request_handling_py.md'; +import RequestHandlingRS from '../development/snippets/request_handling_rs.md'; +import RequestHandlingGO from '../development/snippets/request_handling_go.md'; +import RequestHandlingCPP from '../development/snippets/request_handling_cpp.md'; - handler = handlers[rollup_request["request_type"]] - finish["status"] = handler(rollup_request["data"]) -``` + + +

+
+
+
+
+
+ + +

+
+
+
+
+
+ + +

+
+
+
+
+
+ + +

 
-This Python script establishes a communication loop with the Cartesi rollup server.
+
 
-Two functions, `handle_advance` and `handle_inspect,` are defined to process “advance” and “inspect” requests.
+
+
-The script enters an infinite loop, continually listening and sending finish requests to the rollup server. + +

+
+
+
+
+
+
## Build the backend application -We will use the [Python Mathematical Expression Evaluator](https://pypi.org/project/py-expression-eval/) to parse and evaluate payloads. +For this tutorial, we parse math expressions from advance payloads and emit calculation results as notices. +Install the minimal dependencies for your language: -For example, an advance request to the backend with payload `“1+2”` will add a notice and a response of `“3”`. + + +

 
-In the requirements.txt file, paste the following code to install the libraries.
+```shell
+npm add expr-eval
+```
 
-```python
-requests == 2.31.0
-py_expression_eval == 0.3.14
+
+
+ + +

+
+```shell
+cat > requirements.txt << 'EOF'
+requests==2.32.5
+py_expression_eval==0.3.14
+EOF
 ```
 
-Import the Parser from `py_expression_eval,` the main class of the library, which contains the methods to parse, evaluate, and simplify mathematical expressions.
+
+
-```python -from py_expression_eval import Parser + +

+
+```shell
+cargo add meval hex
 ```
 
-Payloads sent from the client are hex encoded – the utility functions below will decode a hexadecimal string into a conventional string and vice versa.
+
+
-```python -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()
+```shell
+go get github.com/Knetic/govaluate
+```
 
+
+
-Update the advance request function to have a mechanism for parsing payload and sending the output as a notice to the rollup server. + +

 
-def handle_advance(data):
-    logger.info(f"Received advance request data {data}")
+```shell
+# No extra package needed for this tutorial snippet.
+# The generated C++ template already includes httplib + picojson.
+```
 
-    status = "accept"
-    try:
-        input = hex2str(data["payload"])
-        logger.info(f"Received input: {input}")
+
+
+
- # Evaluates expression - parser = Parser() - output = parser.parse(input).evaluate({}) +For example, an advance request to the backend with payload `“1+2”` should emit a notice with the response `“3”`. - # Emits notice with result of calculation - logger.info(f"Adding notice with payload: '{output}'") - response = requests.post(rollup_server + "/notice", json={"payload": str2hex(str(output))}) - logger.info(f"Received notice status {response.status_code} body {response.content}") +## Implement the application logic - except Exception as e: - status = "reject" - msg = f"Error processing data {data}\n{traceback.format_exc()}" - logger.error(msg) - response = requests.post(rollup_server + "/report", json={"payload": str2hex(msg)}) - logger.info(f"Received report status {response.status_code} body {response.content}") +Copy the snippet for your language and replace the contents of your local backend entry point file: - return status -``` +import CalculatorJS from './snippets/calculator-js.md'; +import CalculatorPY from './snippets/calculator-py.md'; +import CalculatorRS from './snippets/calculator-rs.md'; +import CalculatorGO from './snippets/calculator-go.md'; +import CalculatorCPP from './snippets/calculator-cpp.md'; -Here is the final code of the application: - -```python -from os import environ -import traceback -import logging -import requests -from py_expression_eval import Parser - -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 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 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 - parser = Parser() - output = parser.parse(input).evaluate({}) - - # Emits notice with result of calculation - logger.info(f"Adding notice with payload: '{output}'") - response = requests.post(rollup_server + "/notice", json={"payload": str2hex(str(output))}) - logger.info(f"Received notice status {response.status_code} body {response.content}") - - except Exception as e: - status = "reject" - msg = f"Error processing data {data}\n{traceback.format_exc()}" - logger.error(msg) - response = requests.post(rollup_server + "/report", json={"payload": str2hex(msg)}) - logger.info(f"Received report status {response.status_code} body {response.content}") - - return status - -def handle_inspect(data): - logger.info(f"Received inspect request data {data}") - logger.info("Adding report") - response = requests.post(rollup_server + "/report", json={"payload": data["payload"]}) - logger.info(f"Received report status {response.status_code}") - return "accept" - -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"]) -``` + +

+
+
+
+
+
+ + +

+
+
+
+
+
+ + +

+
+
+
+
+
+ + +

+
+
+
+
+
+
With Docker running, “build your backend” application by running: diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/marketplace.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/marketplace.md index f6b01be0..22240466 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/tutorials/marketplace.md +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/marketplace.md @@ -105,6 +105,26 @@ cartesi create marketplace --template python cartesi create marketplace --template rust ``` + +
+ + +

+
+```shell
+cartesi create marketplace --template go
+```
+
+
+
+ + +

+
+```shell
+cartesi create marketplace --template cpp
+```
+
 
@@ -149,17 +169,41 @@ EOF cargo add hex serde ethers-core ``` + +
+ + +

+
+```shell
+# No extra package needed for this tutorial snippet.
+# The generated Go template already includes the Rollups bindings.
+```
+
+
+
+ + +

+
+```shell
+# No extra package needed for this tutorial snippet.
+# The generated C++ template already includes httplib + picojson.
+```
+
 
## Implement the Application Logic -Based on the programming language you selected earlier, copy the appropriate code snippet, then paste in your local entry point file (`dapp.py` or `src/main.rs` or `src/index.js`), created in the setup step: +Based on the programming language you selected earlier, copy the appropriate code snippet, then paste in your local entry point file (`dapp.py`, `src/main.rs`, `src/index.js`, `main.go`, or `src/main.cpp`) created in the setup step: import MarketplaceJS from './snippets/marketplace-js.md'; import MarketplacePY from './snippets/marketplace-py.md'; import MarketplaceRS from './snippets/marketplace-rs.md'; +import MarketplaceGO from './snippets/marketplace-go.md'; +import MarketplaceCPP from './snippets/marketplace-cpp.md'; @@ -183,6 +227,22 @@ import MarketplaceRS from './snippets/marketplace-rs.md'; + + + + +

+
+
+
+
+
+ + +

+
+
+
 
diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-cpp.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-cpp.md new file mode 100644 index 00000000..3b3bf2ae --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-cpp.md @@ -0,0 +1,136 @@ +```cpp +#include +#include +#include +#include +#include +#include + +#include "3rdparty/cpp-httplib/httplib.h" +#include "3rdparty/picojson/picojson.h" + +static std::string hex_to_string(const std::string &hex) +{ + std::string raw = hex.rfind("0x", 0) == 0 ? hex.substr(2) : hex; + std::string out; + out.reserve(raw.size() / 2); + for (size_t i = 0; i + 1 < raw.size(); i += 2) + { + out.push_back(static_cast(std::stoi(raw.substr(i, 2), nullptr, 16))); + } + return out; +} + +static std::string string_to_hex(const std::string &text) +{ + std::ostringstream oss; + oss << "0x"; + for (unsigned char c : text) + { + oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(c); + } + return oss.str(); +} + +static double evaluate_expression(const std::string &expr) +{ + // Minimal parser for "number op number" expressions (e.g. 1+2, 7*3). + std::stringstream ss(expr); + double lhs = 0.0; + double rhs = 0.0; + char op = 0; + ss >> lhs >> op >> rhs; + if (ss.fail()) + { + throw std::runtime_error("invalid expression format"); + } + switch (op) + { + case '+': return lhs + rhs; + case '-': return lhs - rhs; + case '*': return lhs * rhs; + case '/': + if (rhs == 0.0) + { + throw std::runtime_error("division by zero"); + } + return lhs / rhs; + default: + throw std::runtime_error("unsupported operator"); + } +} + +static void emit_notice(httplib::Client &cli, const std::string &text) +{ + picojson::object notice; + notice["payload"] = picojson::value(string_to_hex(text)); + cli.Post("/notice", picojson::value(notice).serialize(), "application/json"); +} + +static void emit_report(httplib::Client &cli, const std::string &payload_hex) +{ + picojson::object report; + report["payload"] = picojson::value(payload_hex); + cli.Post("/report", picojson::value(report).serialize(), "application/json"); +} + +std::string handle_advance(httplib::Client &cli, picojson::value data) +{ + std::cout << "Received advance request data " << data << std::endl; + try + { + const std::string payload_hex = data.get("payload").get(); + const std::string expression = hex_to_string(payload_hex); + const double output = evaluate_expression(expression); + emit_notice(cli, std::to_string(output)); + return "accept"; + } + catch (const std::exception &e) + { + emit_report(cli, string_to_hex(std::string("Error processing input: ") + e.what())); + return "reject"; + } +} + +std::string handle_inspect(httplib::Client &cli, picojson::value data) +{ + std::cout << "Received inspect request data " << data << std::endl; + emit_report(cli, data.get("payload").get()); + return "accept"; +} + +int main(int argc, char **argv) +{ + std::map handlers = { + {"advance_state", &handle_advance}, + {"inspect_state", &handle_inspect}, + }; + + httplib::Client cli(getenv("ROLLUP_HTTP_SERVER_URL")); + std::string status = "accept"; + + while (true) + { + auto finish_body = std::string("{\"status\":\"") + status + "\"}"; + auto response = cli.Post("/finish", finish_body, "application/json"); + if (!response) + { + status = "reject"; + continue; + } + + if (response->status == 202) + { + std::cout << "No pending rollup request, trying again" << std::endl; + status = "accept"; + continue; + } + + picojson::value req; + picojson::parse(req, response->body); + auto request_type = req.get("request_type").get(); + auto data = req.get("data"); + status = handlers[request_type](cli, data); + } +} +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-go.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-go.md new file mode 100644 index 00000000..89b4f0bf --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-go.md @@ -0,0 +1,91 @@ +```go +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + + "dapp/rollups" + "github.com/Knetic/govaluate" +) + +var ( + infolog = log.New(os.Stderr, "[ info ] ", log.Lshortfile) + errlog = log.New(os.Stderr, "[ error ] ", log.Lshortfile) +) + +func HandleAdvance(data *rollups.AdvanceResponse) error { + infolog.Printf("Received advance request data %+v\n", data) + + expression, err := rollups.Hex2Str(data.Payload) + if err != nil { + report := rollups.ReportRequest{Payload: rollups.Str2Hex(fmt.Sprintf("decode error: %v", err))} + _, _ = rollups.SendReport(&report) + return err + } + + evaluable, err := govaluate.NewEvaluableExpression(expression) + if err != nil { + report := rollups.ReportRequest{Payload: rollups.Str2Hex(fmt.Sprintf("parse error: %v", err))} + _, _ = rollups.SendReport(&report) + return err + } + + result, err := evaluable.Evaluate(nil) + if err != nil { + report := rollups.ReportRequest{Payload: rollups.Str2Hex(fmt.Sprintf("evaluation error: %v", err))} + _, _ = rollups.SendReport(&report) + return err + } + + notice := rollups.NoticeRequest{Payload: rollups.Str2Hex(fmt.Sprintf("%v", result))} + _, err = rollups.SendNotice(¬ice) + return err +} + +func HandleInspect(data *rollups.InspectResponse) error { + infolog.Printf("Received inspect request data %+v\n", data) + report := rollups.ReportRequest{Payload: data.Payload} + _, err := rollups.SendReport(&report) + return err +} + +func main() { + finish := rollups.FinishRequest{Status: "accept"} + + for { + infolog.Println("Sending finish") + res, err := rollups.SendFinish(&finish) + if err != nil { + errlog.Panicln("Error calling /finish:", err) + } + + if res.StatusCode == 202 { + infolog.Println("No pending rollup request, trying again") + continue + } + + response, err := rollups.ParseFinishResponse(res) + if err != nil { + errlog.Panicln("Error parsing finish response:", err) + } + + finish.Status = "accept" + if response.Type == "advance_state" { + data := new(rollups.AdvanceResponse) + _ = json.Unmarshal(response.Data, data) + if err = HandleAdvance(data); err != nil { + finish.Status = "reject" + } + } else if response.Type == "inspect_state" { + data := new(rollups.InspectResponse) + _ = json.Unmarshal(response.Data, data) + if err = HandleInspect(data); err != nil { + finish.Status = "reject" + } + } + } +} +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-js.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-js.md new file mode 100644 index 00000000..dd616cc8 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-js.md @@ -0,0 +1,78 @@ +```javascript +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); + } +})(); +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-py.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-py.md new file mode 100644 index 00000000..1fd5f727 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-py.md @@ -0,0 +1,71 @@ +```python +from os import environ +import traceback +import logging +import requests +from py_expression_eval import Parser + +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 hex2str(hex_value): + return bytes.fromhex(hex_value[2:]).decode("utf-8") + + +def str2hex(text): + return "0x" + text.encode("utf-8").hex() + + +def handle_advance(data): + logger.info(f"Received advance request data {data}") + status = "accept" + try: + expression = hex2str(data["payload"]) + parser = Parser() + output = parser.parse(expression).evaluate({}) + response = requests.post( + rollup_server + "/notice", + json={"payload": str2hex(str(output))} + ) + logger.info(f"Received notice status {response.status_code} body {response.content}") + except Exception: + status = "reject" + msg = f"Error processing data {data}\n{traceback.format_exc()}" + logger.error(msg) + response = requests.post( + rollup_server + "/report", + json={"payload": str2hex(msg)} + ) + logger.info(f"Received report status {response.status_code} body {response.content}") + return status + + +def handle_inspect(data): + logger.info(f"Received inspect request data {data}") + response = requests.post(rollup_server + "/report", json={"payload": data["payload"]}) + logger.info(f"Received report status {response.status_code}") + return "accept" + + +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() + handler = handlers[rollup_request["request_type"]] + finish["status"] = handler(rollup_request["data"]) +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-rs.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-rs.md new file mode 100644 index 00000000..7706365b --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-rs.md @@ -0,0 +1,102 @@ +```rust +use json::{object, JsonValue}; +use std::env; + +fn hex2str(payload: &str) -> Result> { + let normalized = payload.strip_prefix("0x").unwrap_or(payload); + let bytes = hex::decode(normalized)?; + Ok(String::from_utf8(bytes)?) +} + +fn str2hex(text: &str) -> String { + format!("0x{}", hex::encode(text)) +} + +async fn post_payload( + client: &hyper::Client, + server_addr: &str, + route: &str, + payload_hex: String, +) -> Result<(), Box> { + let body = object! { "payload" => payload_hex }; + let request = hyper::Request::builder() + .method(hyper::Method::POST) + .header(hyper::header::CONTENT_TYPE, "application/json") + .uri(format!("{}/{}", server_addr, route)) + .body(hyper::Body::from(body.dump()))?; + let _ = client.request(request).await?; + Ok(()) +} + +pub async fn handle_advance( + client: &hyper::Client, + server_addr: &str, + request: JsonValue, +) -> Result<&'static str, Box> { + println!("Received advance request data {}", &request); + let payload = request["data"]["payload"].as_str().ok_or("missing payload")?; + + match hex2str(payload) { + Ok(expression) => match meval::eval_str(expression) { + Ok(value) => { + post_payload(client, server_addr, "notice", str2hex(&value.to_string())).await?; + Ok("accept") + } + Err(err) => { + let msg = format!("evaluation error: {}", err); + post_payload(client, server_addr, "report", str2hex(&msg)).await?; + Ok("reject") + } + }, + Err(err) => { + let msg = format!("decode error: {}", err); + post_payload(client, server_addr, "report", str2hex(&msg)).await?; + Ok("reject") + } + } +} + +pub async fn handle_inspect( + client: &hyper::Client, + server_addr: &str, + request: JsonValue, +) -> Result<&'static str, Box> { + println!("Received inspect request data {}", &request); + let payload = request["data"]["payload"].as_str().ok_or("missing payload")?; + post_payload(client, server_addr, "report", payload.to_string()).await?; + Ok("accept") +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = hyper::Client::new(); + let server_addr = env::var("ROLLUP_HTTP_SERVER_URL")?; + + let mut status = "accept"; + loop { + println!("Sending finish"); + let finish = 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(finish.dump()))?; + let response = client.request(request).await?; + + if response.status() == hyper::StatusCode::ACCEPTED { + println!("No pending rollup request, trying again"); + continue; + } + + let body = hyper::body::to_bytes(response).await?; + let req = json::parse(std::str::from_utf8(&body)?)?; + let request_type = req["request_type"].as_str().ok_or("request_type is not string")?; + + status = match request_type { + "advance_state" => handle_advance(&client, &server_addr, req).await?, + "inspect_state" => handle_inspect(&client, &server_addr, req).await?, + _ => "reject", + }; + } +} +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-cpp.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-cpp.md new file mode 100644 index 00000000..3ce7a427 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-cpp.md @@ -0,0 +1,251 @@ +```cpp +#include +#include +#include +#include +#include +#include +#include + +#include "3rdparty/cpp-httplib/httplib.h" +#include "3rdparty/picojson/picojson.h" + +static std::string to_lower(std::string s) +{ + std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); }); + return s; +} + +static std::string hex_to_utf8(const std::string &hex) +{ + std::string raw = hex.rfind("0x", 0) == 0 ? hex.substr(2) : hex; + std::string out; + out.reserve(raw.size() / 2); + for (size_t i = 0; i + 1 < raw.size(); i += 2) + { + out.push_back(static_cast(std::stoi(raw.substr(i, 2), nullptr, 16))); + } + return out; +} + +static std::string utf8_to_hex(const std::string &text) +{ + std::ostringstream oss; + oss << "0x"; + for (unsigned char c : text) + { + oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(c); + } + return oss.str(); +} + +struct Marketplace +{ + std::string erc721_portal = "0xc700d52F5290e978e9CAe7D1E092935263b60051"; + std::string erc20_portal = "0xc700D6aDd016eECd59d989C028214Eaa0fCC0051"; + std::string erc721_token = "0xBa46623aD94AB45850c4ecbA9555D26328917c3B"; + std::string erc20_token = "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C"; + std::string app_address = "0x0000000000000000000000000000000000000000"; + std::string list_price_wei = "100000000000000000000"; + + std::vector listed_tokens; + std::map token_owner; + std::map erc20_balance; +}; + +static Marketplace storage; + +static bool is_listed(const std::string &token_id) +{ + return std::find(storage.listed_tokens.begin(), storage.listed_tokens.end(), token_id) != storage.listed_tokens.end(); +} + +static void emit_report(httplib::Client &cli, const std::string &text) +{ + picojson::object body; + body["payload"] = picojson::value(utf8_to_hex(text)); + cli.Post("/report", picojson::value(body).serialize(), "application/json"); +} + +static void emit_notice(httplib::Client &cli, const std::string &text) +{ + picojson::object body; + body["payload"] = picojson::value(utf8_to_hex(text)); + cli.Post("/notice", picojson::value(body).serialize(), "application/json"); +} + +static std::string parse_erc20_or_erc721_amount(const std::string &payload_hex) +{ + std::string hex = payload_hex.rfind("0x", 0) == 0 ? payload_hex.substr(2) : payload_hex; + if (hex.size() < 144) + { + return "0"; + } + std::string amount_hex = hex.substr(80, 64); + while (!amount_hex.empty() && amount_hex[0] == '0') + { + amount_hex.erase(amount_hex.begin()); + } + return amount_hex.empty() ? "0" : amount_hex; +} + +static std::string parse_token_address(const std::string &payload_hex) +{ + std::string hex = payload_hex.rfind("0x", 0) == 0 ? payload_hex.substr(2) : payload_hex; + if (hex.size() < 40) + { + return "0x0000000000000000000000000000000000000000"; + } + return "0x" + hex.substr(0, 40); +} + +static std::string parse_receiver_address(const std::string &payload_hex) +{ + std::string hex = payload_hex.rfind("0x", 0) == 0 ? payload_hex.substr(2) : payload_hex; + if (hex.size() < 80) + { + return "0x0000000000000000000000000000000000000000"; + } + return "0x" + hex.substr(40, 40); +} + +static std::string encode_transfer_from(const std::string &from, const std::string &to, const std::string &token_id_dec) +{ + // transferFrom(address,address,uint256) selector + const std::string selector = "23b872dd"; + const std::string from_padded = "000000000000000000000000" + to_lower(from.substr(2)); + const std::string to_padded = "000000000000000000000000" + to_lower(to.substr(2)); + + std::ostringstream token_id_hex; + token_id_hex << std::hex << std::stoull(token_id_dec); + std::string tid = token_id_hex.str(); + if (tid.size() < 64) + { + tid = std::string(64 - tid.size(), '0') + tid; + } + + return "0x" + selector + from_padded + to_padded + tid; +} + +std::string handle_advance(httplib::Client &cli, picojson::value data) +{ + std::cout << "Received advance request data " << data << std::endl; + + const std::string sender = to_lower(data.get("metadata").get("msg_sender").get()); + const std::string app_contract = to_lower(data.get("metadata").get("app_contract").get()); + const std::string payload = data.get("payload").get(); + + if (to_lower(storage.app_address) == "0x0000000000000000000000000000000000000000") + { + storage.app_address = app_contract; + } + + if (sender == to_lower(storage.erc20_portal)) + { + const std::string token = to_lower(parse_token_address(payload)); + const std::string receiver = to_lower(parse_receiver_address(payload)); + if (token != to_lower(storage.erc20_token)) + { + emit_report(cli, "Unsupported ERC20 token"); + return "accept"; + } + storage.erc20_balance[receiver] = parse_erc20_or_erc721_amount(payload); + return "accept"; + } + + if (sender == to_lower(storage.erc721_portal)) + { + const std::string token = to_lower(parse_token_address(payload)); + const std::string receiver = to_lower(parse_receiver_address(payload)); + if (token != to_lower(storage.erc721_token)) + { + emit_report(cli, "Unsupported ERC721 token"); + return "accept"; + } + const std::string token_id = parse_erc20_or_erc721_amount(payload); + storage.token_owner[token_id] = receiver; + storage.listed_tokens.push_back(token_id); + emit_notice(cli, "Token listed successfully"); + return "accept"; + } + + const std::string user_payload = hex_to_utf8(payload); + picojson::value parsed; + picojson::parse(parsed, user_payload); + const std::string method = parsed.get("method").get(); + if (method != "purchase_token") + { + emit_report(cli, "Unsupported method"); + return "accept"; + } + + std::string token_id; + if (parsed.get("token_id").is()) + { + token_id = parsed.get("token_id").get(); + } + else + { + token_id = std::to_string(static_cast(parsed.get("token_id").get())); + } + + if (!is_listed(token_id)) + { + emit_report(cli, "Token is not listed"); + return "accept"; + } + + const std::string call_data = encode_transfer_from(storage.app_address, sender, token_id); + picojson::object voucher; + voucher["destination"] = picojson::value(storage.erc721_token); + voucher["payload"] = picojson::value(call_data); + voucher["value"] = picojson::value("0x0"); + cli.Post("/voucher", picojson::value(voucher).serialize(), "application/json"); + + storage.token_owner[token_id] = sender; + storage.listed_tokens.erase(std::remove(storage.listed_tokens.begin(), storage.listed_tokens.end(), token_id), storage.listed_tokens.end()); + return "accept"; +} + +std::string handle_inspect(httplib::Client &cli, picojson::value data) +{ + std::cout << "Received inspect request data " << data << std::endl; + const std::string inspect_payload = hex_to_utf8(data.get("payload").get()); + + picojson::value parsed; + picojson::parse(parsed, inspect_payload); + const std::string method = parsed.get("method").get(); + + std::string report_message; + if (method == "get_user_erc20_balance") + { + const std::string user = to_lower(parsed.get("user_address").get()); + const std::string bal = storage.erc20_balance.count(user) ? storage.erc20_balance[user] : "0"; + report_message = "User: " + user + " Balance: " + bal; + } + else if (method == "get_token_owner") + { + const std::string token_id = std::to_string(static_cast(parsed.get("token_id").get())); + const std::string owner = storage.token_owner.count(token_id) ? storage.token_owner[token_id] : "None"; + report_message = "Token_id: " + token_id + " owner: " + owner; + } + else + { + std::ostringstream oss; + oss << "All listed tokens are: ["; + for (size_t i = 0; i < storage.listed_tokens.size(); ++i) + { + oss << storage.listed_tokens[i]; + if (i + 1 < storage.listed_tokens.size()) + { + oss << ","; + } + } + oss << "]"; + report_message = oss.str(); + } + + emit_report(cli, report_message); + return "accept"; +} +``` diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-go.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-go.md new file mode 100644 index 00000000..3be5c92b --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-go.md @@ -0,0 +1,215 @@ +```go +package main + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "math/big" + "strings" + + "dapp/rollups" +) + +const ( + zeroAddress = "0x0000000000000000000000000000000000000000" +) + +type Marketplace struct { + ERC721Portal string + ERC20Portal string + ERC721Token string + ERC20Token string + AppAddress string + ListPriceWei *big.Int + + ListedTokens map[string]bool + Erc721OwnerByTokenID map[string]string + UserErc20BalanceByUser map[string]*big.Int + UserOwnedTokenIDsByUser map[string][]string +} + +func newMarketplace() *Marketplace { + return &Marketplace{ + ERC721Portal: "0xc700d52F5290e978e9CAe7D1E092935263b60051", + ERC20Portal: "0xc700D6aDd016eECd59d989C028214Eaa0fCC0051", + ERC721Token: "0xBa46623aD94AB45850c4ecbA9555D26328917c3B", + ERC20Token: "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C", + AppAddress: zeroAddress, + ListPriceWei: big.NewInt(0).SetUint64(100_000_000_000_000_000), // 100 tokens (18 decimals) + + ListedTokens: map[string]bool{}, + Erc721OwnerByTokenID: map[string]string{}, + UserErc20BalanceByUser: map[string]*big.Int{}, + UserOwnedTokenIDsByUser: map[string][]string{}, + } +} + +func normAddr(a string) string { + return strings.ToLower(strings.TrimSpace(a)) +} + +func parseDepositPayload(payloadHex string) (token string, receiver string, amount *big.Int, err error) { + payloadHex = strings.TrimPrefix(payloadHex, "0x") + raw, err := hex.DecodeString(payloadHex) + if err != nil { + return "", "", nil, fmt.Errorf("invalid hex payload: %w", err) + } + if len(raw) < 72 { + return "", "", nil, fmt.Errorf("payload too short") + } + token = "0x" + hex.EncodeToString(raw[0:20]) + receiver = "0x" + hex.EncodeToString(raw[20:40]) + amount = new(big.Int).SetBytes(raw[40:72]) + return token, receiver, amount, nil +} + +func encodeTransferFrom(from, to, tokenID string) string { + selector := "23b872dd" // transferFrom(address,address,uint256) + fromWord := fmt.Sprintf("%064s", strings.TrimPrefix(normAddr(from), "0x")) + toWord := fmt.Sprintf("%064s", strings.TrimPrefix(normAddr(to), "0x")) + id := new(big.Int) + id.SetString(tokenID, 10) + idWord := fmt.Sprintf("%064x", id) + return "0x" + selector + strings.ReplaceAll(fromWord, " ", "0") + strings.ReplaceAll(toWord, " ", "0") + idWord +} + +func (m *Marketplace) increaseUserBalance(user string, amount *big.Int) { + addr := normAddr(user) + if _, ok := m.UserErc20BalanceByUser[addr]; !ok { + m.UserErc20BalanceByUser[addr] = big.NewInt(0) + } + m.UserErc20BalanceByUser[addr].Add(m.UserErc20BalanceByUser[addr], amount) +} + +func (m *Marketplace) reduceUserBalance(user string, amount *big.Int) bool { + addr := normAddr(user) + bal, ok := m.UserErc20BalanceByUser[addr] + if !ok || bal.Cmp(amount) < 0 { + return false + } + bal.Sub(bal, amount) + return true +} + +func (m *Marketplace) depositErc721(user, tokenID string) { + addr := normAddr(user) + m.Erc721OwnerByTokenID[tokenID] = addr + m.UserOwnedTokenIDsByUser[addr] = append(m.UserOwnedTokenIDsByUser[addr], tokenID) + m.ListedTokens[tokenID] = true +} + +type UserCommand struct { + Method string `json:"method"` + TokenID string `json:"token_id"` +} + +var storage = newMarketplace() + +func HandleAdvance(data *rollups.AdvanceResponse) error { + infolog.Printf("Received advance request data %+v\n", data) + sender := normAddr(data.Metadata.MsgSender) + if normAddr(storage.AppAddress) == normAddr(zeroAddress) { + storage.AppAddress = normAddr(data.Metadata.AppContract) + } + + if sender == normAddr(storage.ERC20Portal) { + token, receiver, amount, err := parseDepositPayload(data.Payload) + if err != nil { + return fmt.Errorf("HandleAdvance: parse ERC20 deposit failed: %w", err) + } + if normAddr(token) != normAddr(storage.ERC20Token) { + return fmt.Errorf("HandleAdvance: unsupported ERC20 token") + } + storage.increaseUserBalance(receiver, amount) + return nil + } + + if sender == normAddr(storage.ERC721Portal) { + token, receiver, amount, err := parseDepositPayload(data.Payload) + if err != nil { + return fmt.Errorf("HandleAdvance: parse ERC721 deposit failed: %w", err) + } + if normAddr(token) != normAddr(storage.ERC721Token) { + return fmt.Errorf("HandleAdvance: unsupported ERC721 token") + } + storage.depositErc721(receiver, amount.String()) + notice := rollups.NoticeRequest{Payload: rollups.Str2Hex("Token listed successfully")} + _, _ = rollups.SendNotice(¬ice) + return nil + } + + decoded, err := rollups.Hex2Str(data.Payload) + if err != nil { + return fmt.Errorf("HandleAdvance: invalid user payload: %w", err) + } + var cmd UserCommand + if err := json.Unmarshal([]byte(decoded), &cmd); err != nil { + return fmt.Errorf("HandleAdvance: invalid JSON payload: %w", err) + } + + if cmd.Method != "purchase_token" { + return fmt.Errorf("HandleAdvance: unsupported method") + } + if !storage.ListedTokens[cmd.TokenID] { + return fmt.Errorf("HandleAdvance: token is not listed") + } + if !storage.reduceUserBalance(sender, storage.ListPriceWei) { + return fmt.Errorf("HandleAdvance: insufficient buyer balance") + } + + seller := storage.Erc721OwnerByTokenID[cmd.TokenID] + storage.increaseUserBalance(seller, storage.ListPriceWei) + delete(storage.ListedTokens, cmd.TokenID) + storage.Erc721OwnerByTokenID[cmd.TokenID] = normAddr(sender) + + voucher := rollups.VoucherRequest{ + Destination: storage.ERC721Token, + Payload: encodeTransferFrom(storage.AppAddress, sender, cmd.TokenID), + Value: "0x0", + } + _, err = rollups.SendVoucher(&voucher) + return err +} + +func HandleInspect(data *rollups.InspectResponse) error { + infolog.Printf("Received inspect request data %+v\n", data) + decoded, err := rollups.Hex2Str(data.Payload) + if err != nil { + return fmt.Errorf("HandleInspect: invalid inspect payload: %w", err) + } + + var cmd map[string]string + if err := json.Unmarshal([]byte(decoded), &cmd); err != nil { + return fmt.Errorf("HandleInspect: invalid inspect JSON: %w", err) + } + + method := cmd["method"] + var reportText string + switch method { + case "get_user_erc20_balance": + addr := normAddr(cmd["user_address"]) + bal := big.NewInt(0) + if v, ok := storage.UserErc20BalanceByUser[addr]; ok { + bal = v + } + reportText = fmt.Sprintf("User: %s Balance: %s", addr, bal.String()) + case "get_token_owner": + owner := storage.Erc721OwnerByTokenID[cmd["token_id"]] + if owner == "" { + owner = "None" + } + reportText = fmt.Sprintf("Token_id: %s owner: %s", cmd["token_id"], owner) + default: + list := make([]string, 0, len(storage.ListedTokens)) + for id := range storage.ListedTokens { + list = append(list, id) + } + reportText = fmt.Sprintf("All listed tokens are: %s", strings.Join(list, ",")) + } + + report := rollups.ReportRequest{Payload: rollups.Str2Hex(reportText)} + _, err = rollups.SendReport(&report) + return err +} +``` From 155f3df601c6a4385201fc9a5013090da596e76a Mon Sep 17 00:00:00 2001 From: Shaheen Date: Thu, 26 Feb 2026 21:40:07 +0530 Subject: [PATCH 5/6] marketplace snippets and improvements --- .../version-2.0/tutorials/marketplace.md | 17 +- .../tutorials/snippets/calculator-go.md | 1 - .../tutorials/snippets/marketplace-cpp.md | 316 +++++++++++------- .../tutorials/snippets/marketplace-go.md | 83 ++++- .../tutorials/snippets/marketplace-js.md | 8 +- .../tutorials/snippets/marketplace-py.md | 8 +- .../tutorials/snippets/marketplace-rs.md | 8 +- 7 files changed, 281 insertions(+), 160 deletions(-) diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/marketplace.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/marketplace.md index 22240466..f523c218 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/tutorials/marketplace.md +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/marketplace.md @@ -337,7 +337,7 @@ Since our app uses the test ERC-721 and ERC-20 contracts automatically deployed - Mint token ID 1: ```bash -cast send 0xBa46623aD94AB45850c4ecbA9555D26328917c3B \ +cast send 0x1c5AB37576Af4e6BEeCB66Fa6a9FdBc608F44B78 \ "safeMint(address, uint256, string)" \ 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 1 "" \ --rpc-url http://127.0.0.1:6751/anvil \ @@ -351,9 +351,9 @@ This command calls the `safeMint` function in the `testNFT` contract deployed by Before an NFT can be deposited into the application, the portal contract must have permission to transfer it on behalf of the owner. Use the following command to grant that approval: ```bash -cast send 0xBa46623aD94AB45850c4ecbA9555D26328917c3B \ +cast send 0x1c5AB37576Af4e6BEeCB66Fa6a9FdBc608F44B78 \ "setApprovalForAll(address,bool)" \ - 0xc700d52F5290e978e9CAe7D1E092935263b60051 true \ + 0x9E8851dadb2b77103928518846c4678d48b5e371 true \ --rpc-url http://127.0.0.1:6751/anvil \ --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 ``` @@ -408,7 +408,7 @@ With the NFT successfully listed for sale, it's time to attempt to purchase this - Transfer required tokens to purchase address. ```bash -cast send 0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C \ +cast send 0x5138f529B77B4e0a7c84B77E79c4335D31938fed \ "transfer(address,uint256)" 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 100000000000000000000 \ --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --rpc-url http://127.0.0.1:6751/anvil @@ -436,13 +436,16 @@ Sending finish ### 5. Purchase Token with ID 1 [advance request] -Now that the buyer has deposited funds, we can proceed to purchase the NFT. To do this we make an advance request to the application using the Cartesi CLI by running the command: +Now that the buyer has deposited funds, we can proceed to purchase the NFT. Use the following Cartesi CLI flow: ```bash - cartesi send "{"method": "purchase_token", "token_id": 1}" --from 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 +cartesi send --from 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 +✔ Input String encoding +✔ Input (as string) {"method": "purchase_token", "token_id": 1} +✔ Input sent: 0x61dad3fb284470ab9e049fd45197062704376c797bdf020a5550f66d0dcec884 ``` -This command notifies the marketplace that the address `0x7099797....0d17dc79C8` which initially deposited 100 tokens, would want to purchase token ID 1, The marketplace proceeds to run necessary checks like verifying that the token is for sale, and that the buyer has sufficient tokens to make the purchase, after which it executes the purchase and finally emits a voucher that transfers the tokens to the buyer's address. On a successful purchase, you should get logs similar to the below. +This sends a purchase request for token ID `1` from the buyer address. The marketplace validates the request (token listed and sufficient balance), executes the purchase, and emits the voucher to transfer the NFT to the buyer. ```bash [INFO rollup_http_server::http_service] received new request of type ADVANCE diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-go.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-go.md index 89b4f0bf..b427a05c 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-go.md +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/calculator-go.md @@ -6,7 +6,6 @@ import ( "fmt" "log" "os" - "dapp/rollups" "github.com/Knetic/govaluate" ) diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-cpp.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-cpp.md index 3ce7a427..0db1859c 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-cpp.md +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-cpp.md @@ -1,5 +1,7 @@ ```cpp #include +#include +#include #include #include #include @@ -12,13 +14,22 @@ static std::string to_lower(std::string s) { - std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); }); + std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); return s; } +static std::string strip_0x_prefix(const std::string &hex) +{ + if (hex.rfind("0x", 0) == 0 || hex.rfind("0X", 0) == 0) + { + return hex.substr(2); + } + return hex; +} + static std::string hex_to_utf8(const std::string &hex) { - std::string raw = hex.rfind("0x", 0) == 0 ? hex.substr(2) : hex; + const std::string raw = strip_0x_prefix(hex); std::string out; out.reserve(raw.size() / 2); for (size_t i = 0; i + 1 < raw.size(); i += 2) @@ -41,12 +52,11 @@ static std::string utf8_to_hex(const std::string &text) struct Marketplace { - std::string erc721_portal = "0xc700d52F5290e978e9CAe7D1E092935263b60051"; - std::string erc20_portal = "0xc700D6aDd016eECd59d989C028214Eaa0fCC0051"; - std::string erc721_token = "0xBa46623aD94AB45850c4ecbA9555D26328917c3B"; - std::string erc20_token = "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C"; + std::string erc721_portal = "0x9E8851dadb2b77103928518846c4678d48b5e371"; + std::string erc20_portal = "0xACA6586A0Cf05bD831f2501E7B4aea550dA6562D"; + std::string erc721_token = "0x1c5AB37576Af4e6BEeCB66Fa6a9FdBc608F44B78"; + std::string erc20_token = "0x5138f529B77B4e0a7c84B77E79c4335D31938fed"; std::string app_address = "0x0000000000000000000000000000000000000000"; - std::string list_price_wei = "100000000000000000000"; std::vector listed_tokens; std::map token_owner; @@ -60,38 +70,26 @@ static bool is_listed(const std::string &token_id) return std::find(storage.listed_tokens.begin(), storage.listed_tokens.end(), token_id) != storage.listed_tokens.end(); } -static void emit_report(httplib::Client &cli, const std::string &text) +static void emit_output(httplib::Client &cli, const std::string &endpoint, const std::string &text) { picojson::object body; body["payload"] = picojson::value(utf8_to_hex(text)); - cli.Post("/report", picojson::value(body).serialize(), "application/json"); + cli.Post(endpoint.c_str(), picojson::value(body).serialize(), "application/json"); } -static void emit_notice(httplib::Client &cli, const std::string &text) +static void emit_report(httplib::Client &cli, const std::string &text) { - picojson::object body; - body["payload"] = picojson::value(utf8_to_hex(text)); - cli.Post("/notice", picojson::value(body).serialize(), "application/json"); + emit_output(cli, "/report", text); } -static std::string parse_erc20_or_erc721_amount(const std::string &payload_hex) +static void emit_notice(httplib::Client &cli, const std::string &text) { - std::string hex = payload_hex.rfind("0x", 0) == 0 ? payload_hex.substr(2) : payload_hex; - if (hex.size() < 144) - { - return "0"; - } - std::string amount_hex = hex.substr(80, 64); - while (!amount_hex.empty() && amount_hex[0] == '0') - { - amount_hex.erase(amount_hex.begin()); - } - return amount_hex.empty() ? "0" : amount_hex; + emit_output(cli, "/notice", text); } static std::string parse_token_address(const std::string &payload_hex) { - std::string hex = payload_hex.rfind("0x", 0) == 0 ? payload_hex.substr(2) : payload_hex; + const std::string hex = strip_0x_prefix(payload_hex); if (hex.size() < 40) { return "0x0000000000000000000000000000000000000000"; @@ -101,7 +99,7 @@ static std::string parse_token_address(const std::string &payload_hex) static std::string parse_receiver_address(const std::string &payload_hex) { - std::string hex = payload_hex.rfind("0x", 0) == 0 ? payload_hex.substr(2) : payload_hex; + const std::string hex = strip_0x_prefix(payload_hex); if (hex.size() < 80) { return "0x0000000000000000000000000000000000000000"; @@ -109,143 +107,211 @@ static std::string parse_receiver_address(const std::string &payload_hex) return "0x" + hex.substr(40, 40); } -static std::string encode_transfer_from(const std::string &from, const std::string &to, const std::string &token_id_dec) +static std::string parse_amount_or_token_id(const std::string &payload_hex) +{ + const std::string hex = strip_0x_prefix(payload_hex); + if (hex.size() < 144) + { + return "0"; + } + std::string word = hex.substr(80, 64); + while (word.size() > 1 && word[0] == '0') + { + word.erase(word.begin()); + } + return word; +} + +static std::string encode_transfer_from(const std::string &from, const std::string &to, const std::string &token_id_hex) { - // transferFrom(address,address,uint256) selector const std::string selector = "23b872dd"; const std::string from_padded = "000000000000000000000000" + to_lower(from.substr(2)); const std::string to_padded = "000000000000000000000000" + to_lower(to.substr(2)); - - std::ostringstream token_id_hex; - token_id_hex << std::hex << std::stoull(token_id_dec); - std::string tid = token_id_hex.str(); - if (tid.size() < 64) + std::string token_id_padded = token_id_hex; + if (token_id_padded.size() < 64) { - tid = std::string(64 - tid.size(), '0') + tid; + token_id_padded = std::string(64 - token_id_padded.size(), '0') + token_id_padded; } - - return "0x" + selector + from_padded + to_padded + tid; + return "0x" + selector + from_padded + to_padded + token_id_padded; } std::string handle_advance(httplib::Client &cli, picojson::value data) { - std::cout << "Received advance request data " << data << std::endl; + try + { + const std::string sender = to_lower(data.get("metadata").get("msg_sender").get()); + const std::string app_contract = to_lower(data.get("metadata").get("app_contract").get()); + const std::string payload = data.get("payload").get(); - const std::string sender = to_lower(data.get("metadata").get("msg_sender").get()); - const std::string app_contract = to_lower(data.get("metadata").get("app_contract").get()); - const std::string payload = data.get("payload").get(); + if (to_lower(storage.app_address) == "0x0000000000000000000000000000000000000000") + { + storage.app_address = app_contract; + } - if (to_lower(storage.app_address) == "0x0000000000000000000000000000000000000000") - { - storage.app_address = app_contract; - } + if (sender == to_lower(storage.erc20_portal)) + { + const std::string token = to_lower(parse_token_address(payload)); + const std::string receiver = to_lower(parse_receiver_address(payload)); + if (token != to_lower(storage.erc20_token)) + { + emit_report(cli, "Unsupported ERC20 token"); + return "accept"; + } + storage.erc20_balance[receiver] = parse_amount_or_token_id(payload); + std::cout << "Token deposit processed successfully" << std::endl; + return "accept"; + } - if (sender == to_lower(storage.erc20_portal)) - { - const std::string token = to_lower(parse_token_address(payload)); - const std::string receiver = to_lower(parse_receiver_address(payload)); - if (token != to_lower(storage.erc20_token)) + if (sender == to_lower(storage.erc721_portal)) { - emit_report(cli, "Unsupported ERC20 token"); + const std::string token = to_lower(parse_token_address(payload)); + const std::string receiver = to_lower(parse_receiver_address(payload)); + if (token != to_lower(storage.erc721_token)) + { + emit_report(cli, "Unsupported ERC721 token"); + return "accept"; + } + + const std::string token_id = parse_amount_or_token_id(payload); + storage.token_owner[token_id] = receiver; + if (!is_listed(token_id)) + { + storage.listed_tokens.push_back(token_id); + } + emit_notice(cli, "Token listed successfully"); + std::cout << "Token deposit and listing processed successfully" << std::endl; return "accept"; } - storage.erc20_balance[receiver] = parse_erc20_or_erc721_amount(payload); - return "accept"; - } - if (sender == to_lower(storage.erc721_portal)) - { - const std::string token = to_lower(parse_token_address(payload)); - const std::string receiver = to_lower(parse_receiver_address(payload)); - if (token != to_lower(storage.erc721_token)) + const std::string user_payload = hex_to_utf8(payload); + picojson::value parsed; + picojson::parse(parsed, user_payload); + const std::string method = parsed.get("method").get(); + if (method != "purchase_token") + { + emit_report(cli, "Unsupported method"); + return "accept"; + } + + std::string token_id; + if (parsed.get("token_id").is()) { - emit_report(cli, "Unsupported ERC721 token"); + token_id = parsed.get("token_id").get(); + } + else + { + token_id = std::to_string(static_cast(parsed.get("token_id").get())); + } + + if (!is_listed(token_id)) + { + emit_report(cli, "Token is not listed"); return "accept"; } - const std::string token_id = parse_erc20_or_erc721_amount(payload); - storage.token_owner[token_id] = receiver; - storage.listed_tokens.push_back(token_id); - emit_notice(cli, "Token listed successfully"); + + const std::string call_data = encode_transfer_from(storage.app_address, sender, token_id); + picojson::object voucher; + voucher["destination"] = picojson::value(storage.erc721_token); + voucher["payload"] = picojson::value(call_data); + voucher["value"] = picojson::value("0x00"); + auto voucher_res = cli.Post("/voucher", picojson::value(voucher).serialize(), "application/json"); + if (!voucher_res || voucher_res->status >= 400) + { + emit_report(cli, "Failed to generate voucher"); + return "accept"; + } + std::cout << "Voucher generation successful" << std::endl; + + storage.token_owner[token_id] = sender; + storage.listed_tokens.erase(std::remove(storage.listed_tokens.begin(), storage.listed_tokens.end(), token_id), storage.listed_tokens.end()); + std::cout << "Token purchased and Withdrawn successfully" << std::endl; return "accept"; } - - const std::string user_payload = hex_to_utf8(payload); - picojson::value parsed; - picojson::parse(parsed, user_payload); - const std::string method = parsed.get("method").get(); - if (method != "purchase_token") + catch (const std::exception &e) { - emit_report(cli, "Unsupported method"); + emit_report(cli, std::string("Error: ") + e.what()); return "accept"; } +} - std::string token_id; - if (parsed.get("token_id").is()) - { - token_id = parsed.get("token_id").get(); - } - else +std::string handle_inspect(httplib::Client &cli, picojson::value data) +{ + try { - token_id = std::to_string(static_cast(parsed.get("token_id").get())); - } + const std::string inspect_payload = hex_to_utf8(data.get("payload").get()); + picojson::value parsed; + picojson::parse(parsed, inspect_payload); + const std::string method = parsed.get("method").get(); - if (!is_listed(token_id)) + std::string report_message; + if (method == "get_user_erc20_balance") + { + const std::string user = to_lower(parsed.get("user_address").get()); + const std::string bal = storage.erc20_balance.count(user) ? storage.erc20_balance[user] : "0"; + report_message = "User: " + user + " Balance: " + bal; + } + else if (method == "get_token_owner") + { + const std::string token_id = std::to_string(static_cast(parsed.get("token_id").get())); + const std::string owner = storage.token_owner.count(token_id) ? storage.token_owner[token_id] : "None"; + report_message = "Token_id: " + token_id + " owner: " + owner; + } + else + { + std::ostringstream oss; + oss << "All listed tokens are: ["; + for (size_t i = 0; i < storage.listed_tokens.size(); ++i) + { + oss << storage.listed_tokens[i]; + if (i + 1 < storage.listed_tokens.size()) + { + oss << ","; + } + } + oss << "]"; + report_message = oss.str(); + } + + emit_report(cli, report_message); + return "accept"; + } + catch (const std::exception &e) { - emit_report(cli, "Token is not listed"); + emit_report(cli, std::string("Inspect error: ") + e.what()); return "accept"; } - - const std::string call_data = encode_transfer_from(storage.app_address, sender, token_id); - picojson::object voucher; - voucher["destination"] = picojson::value(storage.erc721_token); - voucher["payload"] = picojson::value(call_data); - voucher["value"] = picojson::value("0x0"); - cli.Post("/voucher", picojson::value(voucher).serialize(), "application/json"); - - storage.token_owner[token_id] = sender; - storage.listed_tokens.erase(std::remove(storage.listed_tokens.begin(), storage.listed_tokens.end(), token_id), storage.listed_tokens.end()); - return "accept"; } -std::string handle_inspect(httplib::Client &cli, picojson::value data) +int main(int argc, char **argv) { - std::cout << "Received inspect request data " << data << std::endl; - const std::string inspect_payload = hex_to_utf8(data.get("payload").get()); + (void)argc; + (void)argv; - picojson::value parsed; - picojson::parse(parsed, inspect_payload); - const std::string method = parsed.get("method").get(); + std::map handlers = { + {std::string("advance_state"), &handle_advance}, + {std::string("inspect_state"), &handle_inspect}, + }; - std::string report_message; - if (method == "get_user_erc20_balance") - { - const std::string user = to_lower(parsed.get("user_address").get()); - const std::string bal = storage.erc20_balance.count(user) ? storage.erc20_balance[user] : "0"; - report_message = "User: " + user + " Balance: " + bal; - } - else if (method == "get_token_owner") - { - const std::string token_id = std::to_string(static_cast(parsed.get("token_id").get())); - const std::string owner = storage.token_owner.count(token_id) ? storage.token_owner[token_id] : "None"; - report_message = "Token_id: " + token_id + " owner: " + owner; - } - else + httplib::Client cli(getenv("ROLLUP_HTTP_SERVER_URL")); + cli.set_read_timeout(20, 0); + std::string status("accept"); + + while (true) { - std::ostringstream oss; - oss << "All listed tokens are: ["; - for (size_t i = 0; i < storage.listed_tokens.size(); ++i) + const std::string finish = std::string("{\"status\":\"") + status + std::string("\"}"); + auto r = cli.Post("/finish", finish, "application/json"); + if (!r || r->status == 202) { - oss << storage.listed_tokens[i]; - if (i + 1 < storage.listed_tokens.size()) - { - oss << ","; - } + continue; } - oss << "]"; - report_message = oss.str(); + + picojson::value rollup_request; + picojson::parse(rollup_request, r->body); + const std::string request_type = rollup_request.get("request_type").get(); + const picojson::value request_data = rollup_request.get("data"); + status = handlers.find(request_type)->second(cli, request_data); } - emit_report(cli, report_message); - return "accept"; + return 0; } -``` +``` \ No newline at end of file diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-go.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-go.md index 3be5c92b..79fc6732 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-go.md +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-go.md @@ -2,13 +2,19 @@ package main import ( + "dapp/rollups" "encoding/hex" "encoding/json" "fmt" + "log" "math/big" + "os" "strings" +) - "dapp/rollups" +var ( + infolog = log.New(os.Stderr, "[ info ] ", log.Lshortfile) + errlog = log.New(os.Stderr, "[ error ] ", log.Lshortfile) ) const ( @@ -31,10 +37,10 @@ type Marketplace struct { func newMarketplace() *Marketplace { return &Marketplace{ - ERC721Portal: "0xc700d52F5290e978e9CAe7D1E092935263b60051", - ERC20Portal: "0xc700D6aDd016eECd59d989C028214Eaa0fCC0051", - ERC721Token: "0xBa46623aD94AB45850c4ecbA9555D26328917c3B", - ERC20Token: "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C", + ERC721Portal: "0x9E8851dadb2b77103928518846c4678d48b5e371", + ERC20Portal: "0xACA6586A0Cf05bD831f2501E7B4aea550dA6562D", + ERC721Token: "0x1c5AB37576Af4e6BEeCB66Fa6a9FdBc608F44B78", + ERC20Token: "0x5138f529B77B4e0a7c84B77E79c4335D31938fed", AppAddress: zeroAddress, ListPriceWei: big.NewInt(0).SetUint64(100_000_000_000_000_000), // 100 tokens (18 decimals) @@ -100,8 +106,8 @@ func (m *Marketplace) depositErc721(user, tokenID string) { } type UserCommand struct { - Method string `json:"method"` - TokenID string `json:"token_id"` + Method string `json:"method"` + TokenID json.Number `json:"token_id"` } var storage = newMarketplace() @@ -122,6 +128,7 @@ func HandleAdvance(data *rollups.AdvanceResponse) error { return fmt.Errorf("HandleAdvance: unsupported ERC20 token") } storage.increaseUserBalance(receiver, amount) + infolog.Println("Token deposit processed successfully") return nil } @@ -136,6 +143,7 @@ func HandleAdvance(data *rollups.AdvanceResponse) error { storage.depositErc721(receiver, amount.String()) notice := rollups.NoticeRequest{Payload: rollups.Str2Hex("Token listed successfully")} _, _ = rollups.SendNotice(¬ice) + infolog.Println("Token deposit and listing processed successfully") return nil } @@ -151,25 +159,31 @@ func HandleAdvance(data *rollups.AdvanceResponse) error { if cmd.Method != "purchase_token" { return fmt.Errorf("HandleAdvance: unsupported method") } - if !storage.ListedTokens[cmd.TokenID] { + tokenID := cmd.TokenID.String() + if !storage.ListedTokens[tokenID] { return fmt.Errorf("HandleAdvance: token is not listed") } if !storage.reduceUserBalance(sender, storage.ListPriceWei) { return fmt.Errorf("HandleAdvance: insufficient buyer balance") } - seller := storage.Erc721OwnerByTokenID[cmd.TokenID] + seller := storage.Erc721OwnerByTokenID[tokenID] storage.increaseUserBalance(seller, storage.ListPriceWei) - delete(storage.ListedTokens, cmd.TokenID) - storage.Erc721OwnerByTokenID[cmd.TokenID] = normAddr(sender) + delete(storage.ListedTokens, tokenID) + storage.Erc721OwnerByTokenID[tokenID] = normAddr(sender) voucher := rollups.VoucherRequest{ Destination: storage.ERC721Token, - Payload: encodeTransferFrom(storage.AppAddress, sender, cmd.TokenID), - Value: "0x0", + Payload: encodeTransferFrom(storage.AppAddress, sender, tokenID), + Value: "0x00", } _, err = rollups.SendVoucher(&voucher) - return err + if err != nil { + return fmt.Errorf("HandleAdvance: failed sending voucher: %w", err) + } + infolog.Println("Voucher generation successful") + infolog.Println("Token purchased and Withdrawn successfully") + return nil } func HandleInspect(data *rollups.InspectResponse) error { @@ -212,4 +226,43 @@ func HandleInspect(data *rollups.InspectResponse) error { _, err = rollups.SendReport(&report) return err } -``` + +func main() { + finish := rollups.FinishRequest{Status: "accept"} + + for { + infolog.Println("Sending finish") + res, err := rollups.SendFinish(&finish) + if err != nil { + errlog.Panicln("Error calling /finish:", err) + } + + if res.StatusCode == 202 { + infolog.Println("No pending rollup request, trying again") + continue + } + + response, err := rollups.ParseFinishResponse(res) + if err != nil { + errlog.Panicln("Error parsing finish response:", err) + } + + finish.Status = "accept" + if response.Type == "advance_state" { + data := new(rollups.AdvanceResponse) + _ = json.Unmarshal(response.Data, data) + if err = HandleAdvance(data); err != nil { + errlog.Println(err) + finish.Status = "reject" + } + } else if response.Type == "inspect_state" { + data := new(rollups.InspectResponse) + _ = json.Unmarshal(response.Data, data) + if err = HandleInspect(data); err != nil { + errlog.Println(err) + finish.Status = "reject" + } + } + } +} +``` \ No newline at end of file diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-js.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-js.md index 29d87e87..757f1b2a 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-js.md +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-js.md @@ -392,10 +392,10 @@ var handlers = { inspect_state: handle_inspect, }; -let erc721_portal_address = "0xc700d52F5290e978e9CAe7D1E092935263b60051"; -let erc20_portal_address = "0xc700D6aDd016eECd59d989C028214Eaa0fCC0051"; -let erc20_token = "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C"; -let erc721_token = "0xBa46623aD94AB45850c4ecbA9555D26328917c3B"; +let erc721_portal_address = "0x9E8851dadb2b77103928518846c4678d48b5e371"; +let erc20_portal_address = "0xACA6586A0Cf05bD831f2501E7B4aea550dA6562D"; +let erc20_token = "0x5138f529B77B4e0a7c84B77E79c4335D31938fed"; +let erc721_token = "0x1c5AB37576Af4e6BEeCB66Fa6a9FdBc608F44B78"; let list_price = BigInt("100000000000000000000"); var storage = new Storage(erc721_portal_address, erc20_portal_address, erc721_token, erc20_token, list_price); diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-py.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-py.md index e66b3cec..02c7e1d9 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-py.md +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-py.md @@ -234,10 +234,10 @@ class Storage: return True -erc_721_portal_address = "0xc700d52F5290e978e9CAe7D1E092935263b60051" -erc20_portal_address = "0xc700D6aDd016eECd59d989C028214Eaa0fCC0051" -erc20_token = "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C" -erc721_token = "0xBa46623aD94AB45850c4ecbA9555D26328917c3B" +erc_721_portal_address = "0x9E8851dadb2b77103928518846c4678d48b5e371" +erc20_portal_address = "0xACA6586A0Cf05bD831f2501E7B4aea550dA6562D" +erc20_token = "0x5138f529B77B4e0a7c84B77E79c4335D31938fed" +erc721_token = "0x1c5AB37576Af4e6BEeCB66Fa6a9FdBc608F44B78" list_price = 100_000_000_000_000_000_000 storage = Storage(erc_721_portal_address, erc20_portal_address, erc721_token, erc20_token, list_price) diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-rs.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-rs.md index 14911d7d..6e100c7e 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-rs.md +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/snippets/marketplace-rs.md @@ -328,10 +328,10 @@ async fn main() -> Result<(), Box> { let client = hyper::Client::new(); let server_addr = env::var("ROLLUP_HTTP_SERVER_URL")?; - let erc721_portal_address = String::from("0xc700d52F5290e978e9CAe7D1E092935263b60051"); - let erc20_portal_address = String::from("0xc700D6aDd016eECd59d989C028214Eaa0fCC0051"); - let erc20_token = String::from("0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C"); - let erc721_token = String::from("0xBa46623aD94AB45850c4ecbA9555D26328917c3B"); + let erc721_portal_address = String::from("0x9E8851dadb2b77103928518846c4678d48b5e371"); + let erc20_portal_address = String::from("0xACA6586A0Cf05bD831f2501E7B4aea550dA6562D"); + let erc20_token = String::from("0x5138f529B77B4e0a7c84B77E79c4335D31938fed"); + let erc721_token = String::from("0x1c5AB37576Af4e6BEeCB66Fa6a9FdBc608F44B78"); let list_price: u128 = 100_000_000_000_000_000_000; let mut storage = Storage::new(erc721_portal_address, erc20_portal_address, erc721_token, erc20_token, list_price); From 07482d56123e9f02ed45bab711b453842d778986 Mon Sep 17 00:00:00 2001 From: Shaheen Date: Thu, 26 Feb 2026 22:25:04 +0530 Subject: [PATCH 6/6] delegate voucher snippets and cosmetic updates --- .../api-reference/backend/vouchers.md | 101 +++++++++++++++++- .../snippets/asset_withdraw_erc20_cpp.md | 2 +- .../snippets/asset_withdraw_erc20_go.md | 2 +- .../snippets/implementing_outputs_go.md | 2 +- .../version-2.0/tutorials/calculator.md | 6 +- .../version-2.0/tutorials/counter.md | 4 +- .../version-2.0/tutorials/marketplace.md | 4 +- cartesi-rollups_versions.json | 6 +- 8 files changed, 110 insertions(+), 17 deletions(-) diff --git a/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/vouchers.md b/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/vouchers.md index aecafb69..51409abf 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/vouchers.md +++ b/cartesi-rollups_versioned_docs/version-2.0/api-reference/backend/vouchers.md @@ -204,7 +204,7 @@ func HandleAdvance(data *rollups.AdvanceResponse) error { voucher := rollups.VoucherRequest{ Destination: "0x784f0c076CC55EAD0a585a9A13e57c467c91Dc3a", // sample ERC-20 token Payload: callData, - Value: "0x0", + Value: "0x00", } if _, err := rollups.SendVoucher(&voucher); err != nil { @@ -235,7 +235,7 @@ std::string handle_advance(httplib::Client &cli, picojson::value data) picojson::object voucher; voucher["destination"] = picojson::value("0x784f0c076CC55EAD0a585a9A13e57c467c91Dc3a"); voucher["payload"] = picojson::value(call_data); - voucher["value"] = picojson::value("0x0"); + voucher["value"] = picojson::value("0x00"); auto response = cli.Post( "/voucher", @@ -295,6 +295,9 @@ This mechanism, where the Application contract maintains the state and funds whi - **Ordered Vouchers**: Vouchers that must be executed in a specific sequence. For example, voucher A can only be executed after voucher B has been executed. +The examples below emit a DELEGATECALL voucher by posting to `/delegate-call-voucher`. +In these snippets, `destination` is the contract that contains the logic to run, while `payload` is the ABI-encoded function call data (for example, `safeTransfer(address,address,uint256)`). +

@@ -352,6 +355,56 @@ def emit_safe_erc20_transfer(token, to, amount):
 
+ +

+
+```rust
+use ethers_core::abi::{Function, Param, ParamType, StateMutability, Token};
+use json::object;
+
+pub async fn emit_safe_erc20_transfer(
+    client: &hyper::Client,
+    server_addr: &str,
+    token: &str,
+    to: &str,
+    amount: u64,
+) -> Result<(), Box> {
+    let safe_transfer = Function {
+        name: "safeTransfer".to_string(),
+        inputs: vec![
+            Param { name: "token".to_string(), kind: ParamType::Address, internal_type: None },
+            Param { name: "to".to_string(), kind: ParamType::Address, internal_type: None },
+            Param { name: "amount".to_string(), kind: ParamType::Uint(256), internal_type: None },
+        ],
+        outputs: vec![],
+        constant: None,
+        state_mutability: StateMutability::NonPayable,
+    };
+
+    let payload_bytes = safe_transfer.encode_input(&[
+        Token::Address(token.parse()?),
+        Token::Address(to.parse()?),
+        Token::Uint(amount.into()),
+    ])?;
+
+    let voucher = object! {
+        destination: "0xfafafafafafafafafafafafafafafafafafafafa", // address of the contract containing the logic
+        payload: format!("0x{}", hex::encode(payload_bytes)),
+    };
+
+    let request = hyper::Request::builder()
+        .method(hyper::Method::POST)
+        .header(hyper::header::CONTENT_TYPE, "application/json")
+        .uri(format!("{}/delegate-call-voucher", server_addr))
+        .body(hyper::Body::from(voucher.dump()))?;
+    client.request(request).await?;
+    Ok(())
+}
+```
+
+
+
+

 
@@ -409,6 +462,50 @@ func emitSafeERC20Transfer(token, to common.Address, amount *big.Int) error {
 }
 ```
 
+
+
+ + +

+
+```cpp
+#include 
+
+#include "3rdparty/cpp-httplib/httplib.h"
+#include "3rdparty/picojson/picojson.h"
+
+std::string emit_safe_erc20_transfer(
+    httplib::Client &cli,
+    const std::string &token,
+    const std::string &to,
+    const std::string &amount_hex_32bytes
+)
+{
+    // safeTransfer(address,address,uint256)
+    const std::string selector = "eb795549";
+    const std::string token_word = "000000000000000000000000" + token.substr(2);
+    const std::string to_word = "000000000000000000000000" + to.substr(2);
+    const std::string payload = "0x" + selector + token_word + to_word + amount_hex_32bytes;
+
+    picojson::object voucher;
+    voucher["destination"] = picojson::value("0xfafafafafafafafafafafafafafafafafafafafa");
+    voucher["payload"] = picojson::value(payload);
+
+    auto response = cli.Post(
+        "/delegate-call-voucher",
+        picojson::value(voucher).serialize(),
+        "application/json"
+    );
+
+    if (!response || response->status >= 400)
+    {
+        return "error";
+    }
+
+    return "ok";
+}
+```
+
 
diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_cpp.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_cpp.md index 6661bf1d..19a6b402 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_cpp.md +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_cpp.md @@ -20,7 +20,7 @@ std::string handle_advance(httplib::Client &cli, picojson::value data) picojson::object voucher; voucher["destination"] = picojson::value(kErc20TokenAddress); voucher["payload"] = picojson::value(call_data); - voucher["value"] = picojson::value("0x0"); + voucher["value"] = picojson::value("0x00"); auto response = cli.Post( "/voucher", diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_go.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_go.md index a6060182..e3b7fba8 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_go.md +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/asset_withdraw_erc20_go.md @@ -26,7 +26,7 @@ func HandleAdvance(data *rollups.AdvanceResponse) error { voucher := rollups.VoucherRequest{ Destination: erc20TokenAddress, Payload: callData, - Value: "0x0", + Value: "0x00", } if _, err := rollups.SendVoucher(&voucher); err != nil { diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/implementing_outputs_go.md b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/implementing_outputs_go.md index acd1c037..a7a64176 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/development/snippets/implementing_outputs_go.md +++ b/cartesi-rollups_versioned_docs/version-2.0/development/snippets/implementing_outputs_go.md @@ -73,7 +73,7 @@ func HandleAdvance(data *rollups.AdvanceResponse) error { } voucher := rollups.VoucherRequest{ Destination: tokenAddress, - Value: "0x0", + Value: "0x00", Payload: voucherPayload, } if _, err = rollups.SendVoucher(&voucher); err != nil { diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/calculator.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/calculator.md index 5daf93fd..fc8faff3 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/tutorials/calculator.md +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/calculator.md @@ -1,12 +1,12 @@ --- id: calculator -title: Build a calculator dApp +title: Build a Calculator Application resources: - url: https://github.com/Mugen-Builders/calculator - title: Source code for the Calculator dApp + title: Source code for the Calculator App --- -In this tutorial, we will build a simple Calculator dApp to illustrate how requests are sent and processed within Cartesi Rollups Infrastructure. +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. diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/counter.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/counter.md index f42c87e0..226c3399 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/tutorials/counter.md +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/counter.md @@ -1,9 +1,9 @@ --- id: counter -title: Build a counter Application +title: Build a Counter Application resources: - url: https://github.com/Mugen-Builders/Counter-X-Marketplace-apps - title: Source code for the counter Application + title: Source code for the Counter Application --- This tutorial aims to guide you through creating and interacting with a basic Cartesi application, it'll take you through setting up your dev environment, creating a project then finally running and interacting with your application locally. diff --git a/cartesi-rollups_versioned_docs/version-2.0/tutorials/marketplace.md b/cartesi-rollups_versioned_docs/version-2.0/tutorials/marketplace.md index f523c218..7b08cfc9 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/tutorials/marketplace.md +++ b/cartesi-rollups_versioned_docs/version-2.0/tutorials/marketplace.md @@ -1,9 +1,9 @@ --- id: marketplace -title: Build a marketplace Application +title: Build a Marketplace Application resources: - url: https://github.com/Mugen-Builders/Counter-X-Marketplace-apps - title: Source code for the marketplace Application + title: Source code for the Marketplace Application --- In this tutorial we'll be building a simple NFT Marketplace application, where users are able to deposit a unique token to be sold at a fixed price, then other users are able to purchase and withdraw these purchased tokens to their wallet. diff --git a/cartesi-rollups_versions.json b/cartesi-rollups_versions.json index 4af9e6da..b2e77332 100644 --- a/cartesi-rollups_versions.json +++ b/cartesi-rollups_versions.json @@ -1,8 +1,4 @@ [ "2.0", - "1.5", - "1.3", - "1.0", - "0.9", - "0.8" + "1.5" ]