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/asset-handling.md b/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md index cc859b62..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 @@ -40,6 +40,67 @@ 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'; +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'; + + + +

+
+
+
+ + +

+
+
+
+ + +

+
+
+
+ + +

+
+
+
+
+
+ + +

+
+
+
+
+
+ +
+ +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. @@ -54,49 +115,43 @@ 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"; - -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 - } -}; -``` + + +

+
+
+
+ + +

+
+
+
+ + +

+
+
+
+ + +

+
+
+
+
+
+ + +

+
+
+
+
+
+
+ +For a full guide, see the Tutorial: [ERC-20 Token Wallet](../tutorials/erc-20-token-wallet.md). ### Withdrawing Ether @@ -104,43 +159,44 @@ 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"; - -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 - } -}; -``` + + +

+
+
+
+ + +

+
+
+
+ + +

+
+
+
+ + +

+
+
+
+
+
+ + +

+
+
+
+
+
+ +
+ +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/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/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 new file mode 100644 index 00000000..5ede64a6 --- /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 voucher_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) +{ + // 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" + voucher_config::kVoucherRecipientAddress.substr(2); + const std::string amount_padded = + "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(voucher_config::kVoucherTokenAddress); + 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/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 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 +} +``` 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) + } + } +} +```