Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d0cae75
feat: upgrade to node v2. Remove dapp rellay, change dapp->app
lynoferraz Oct 14, 2024
b783393
feat: use eth_abi_lite instead of eth_abi
lynoferraz Oct 14, 2024
b49372b
feat: Use 2.0.0-rc.10 portal address
lynoferraz Oct 18, 2024
22829cd
feat: Add delegated vouchers and fixes
lynoferraz Feb 12, 2025
e905dfd
fix: delegated call logs
lynoferraz Feb 12, 2025
fd9b5d0
feat: Add gio request
lynoferraz Feb 12, 2025
d18e279
feat: decode raw evm advance
lynoferraz Feb 13, 2025
aeeb9fd
Merge branch 'main' into feat/gio-request
lynoferraz Mar 12, 2025
df60a05
fix: fix testing and type declarations
lynoferraz May 7, 2025
13ad6c0
Added OO approach to create outputs
jplgarcia May 19, 2025
53381b0
Merge pull request #2 from prototyp3-dev/feat/gio-request
lynoferraz May 29, 2025
a7f96a4
Merge pull request #1 from jplgarcia/feature/upgrade-to-node-v2
lynoferraz May 29, 2025
8ea5836
feat: Use 2.0.0-rc.18 ether portal address
lynoferraz May 29, 2025
efd043c
fix: utils imports
lynoferraz Jul 1, 2025
77a2e0d
feat: Add cmpy rollup
lynoferraz Jan 28, 2026
427438d
feat: use pycmt
lynoferraz Feb 4, 2026
f4391f8
feat: use pycmt fix
lynoferraz Feb 4, 2026
33c9ecf
feat: added pycmt file, updated ether wallet
lynoferraz Feb 4, 2026
3a2de38
feat: added optional dependencies
lynoferraz Feb 4, 2026
896712c
feat: pycmt rollup fix
lynoferraz Feb 5, 2026
725db06
feat: Add pycma
lynoferraz May 12, 2026
9cc83d2
chore: bump pycma
lynoferraz Jun 23, 2026
f1af24a
chore: bump pycmt and pycma
lynoferraz Aug 14, 2026
cc70e62
fix: dep version
lynoferraz Aug 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 143 additions & 71 deletions README.md

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions cartesi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
"""Framework for building distributed applications for Cartesi Rollups"""

from .dapp import DApp # noqa
from .app import App # noqa
from .models import ( # noqa
ABIFunctionSelectorHeader,
ABILiteralHeader,
RollupData,
RollupMetadata,
RollupResponse
RollupResponse,
)
from .rollup import Rollup, HTTPRollupServer # noqa

from .outputs import Notice, Report, Voucher # noqa

from .router import ( # noqa
Router,
JSONRouter,
Expand Down
8 changes: 4 additions & 4 deletions cartesi/_eth_abi_packed.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from eth_abi.codec import (
from eth_abi_lite.codec import (
ABICodec,
)
from eth_abi.registry import (
from eth_abi_lite.registry import (
registry_packed,
BaseEquals
)
from eth_abi.decoding import (
from eth_abi_lite.decoding import (
BooleanDecoder,
AddressDecoder,
UnsignedIntegerDecoder,
Expand Down Expand Up @@ -64,4 +64,4 @@ def read_data_from_stream(self, stream):

default_codec_packed = ABICodec(registry_packed)

decode_packed = default_codec_packed.decode
decode_abi_packed = default_codec_packed.decode_abi
18 changes: 9 additions & 9 deletions cartesi/abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
from typing import Annotated, get_type_hints, TypeVar, get_args, get_origin
from dataclasses import dataclass

import eth_abi
import eth_abi.packed
from eth_abi_lite import decode_abi, encode_abi
import eth_abi_lite.packed
import pydantic

from . import _eth_abi_packed
from cartesi._eth_abi_packed import decode_abi_packed


# Type Aliases for ABI encoding
Expand Down Expand Up @@ -199,14 +199,14 @@ def encode_model(obj: pydantic.BaseModel, packed: bool = False) -> bytes:
Serialized version of the model
"""
if packed:
encode = eth_abi.packed.encode_packed
encode_fn = eth_abi_lite.packed.encode_abi_packed
else:
encode = eth_abi.encode
encode_fn = encode_abi

data = _get_values_from_model(obj)
types = get_abi_types_from_model(obj)

return encode(types, data)
return encode_fn(types, data)


M = TypeVar('M', bound=pydantic.BaseModel)
Expand Down Expand Up @@ -282,11 +282,11 @@ def decode_to_model(data: bytes, model: M, packed: bool = False) -> M:
Object containing decoded data
"""
if packed:
decode = _eth_abi_packed.decode_packed
decode_fn = decode_abi_packed
else:
decode = eth_abi.decode
decode_fn = decode_abi

types = get_abi_types_from_model(model)
decoded = decode(types, data)
decoded = decode_fn(types, data)

return _parse_to_model(model, decoded)
31 changes: 22 additions & 9 deletions cartesi/dapp.py → cartesi/app.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import os
import logging
from os import environ
from logging import getLogger, debug

from .models import RollupResponse
from .rollup import Rollup, HTTPRollupServer
from .rollup import Rollup
from .router import Router

LOGGER = logging.getLogger(__name__)
ROLLUP_SERVER = os.environ.get('ROLLUP_HTTP_SERVER_URL')
LOGGER = getLogger(__name__)
ROLLUP_SERVER = environ.get('ROLLUP_HTTP_SERVER_URL')


class DApp:
class App:

def __init__(self):
def __init__(self, raw_input = False, use_pycmt = False, use_pycma = False):
self.routers: list[Router] = []
self.default_advance_handler = lambda rollup, data: False
self.default_inspect_handler = lambda rollup, data: False
self.rollup: Rollup | None = None
self.raw_input = raw_input
self.use_pycmt = use_pycmt
self.use_pycma = use_pycma

def advance(self):
"""Decorator for inserting handle advance"""
Expand All @@ -32,9 +35,11 @@ def inspect(self):
"""Decorator for inserting handle advance"""

def decorator(func):
LOGGER.debug("Adding func %s to inspect_handler", repr(func))
self.default_inspect_handler = func
return func

LOGGER.debug('Returning an Inspect Decorator')
return decorator

def _get_default_handler(self, request: RollupResponse):
Expand All @@ -59,7 +64,7 @@ def _handle(self, request: RollupResponse) -> bool:
if handler is None:
handler = self._get_default_handler(request)

logging.debug("Handler: %s", repr(handler))
debug("Handler: %s", repr(handler))
try:
status = handler(self.rollup, request.data)
except Exception:
Expand All @@ -73,6 +78,14 @@ def add_router(self, router: Router):

def run(self):
if self.rollup is None:
self.rollup = HTTPRollupServer()
if self.use_pycmt:
from .pycmt_rollup import CmtRollupApp
self.rollup = CmtRollupApp()
elif self.use_pycma:
from .pycma_rollup import CmaRollupApp
self.rollup = CmaRollupApp()
else:
from .rollup import HTTPRollupServer
self.rollup = HTTPRollupServer(raw_input=self.raw_input)
self.rollup.set_handler(self._handle)
self.rollup.main_loop()
26 changes: 22 additions & 4 deletions cartesi/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from Crypto.Hash import keccak
from pydantic import BaseModel
from .abi import UInt256, Bytes, Address, get_abi_types_from_model


def _hex2str(hex):
Expand All @@ -20,11 +21,13 @@ def _str2hex(str):


class RollupMetadata(BaseModel):
chain_id: int
app_contract: str
msg_sender: str
epoch_index: int
input_index: int
block_number: int
timestamp: int
block_timestamp: int
prev_randao: str


class RollupData(BaseModel):
Expand All @@ -37,7 +40,7 @@ def bytes_payload(self) -> bytes:
def str_payload(self, encoding='utf-8') -> str:
return bytes.fromhex(self.payload[2:]).decode(encoding)

def json_payload(self) -> bytes:
def json_payload(self) -> dict:
return json.loads(self.str_payload())


Expand All @@ -49,7 +52,7 @@ class RollupResponse(BaseModel):
class ABIHeader(BaseModel, abc.ABC):

@abc.abstractmethod
def to_bytes(self):
def to_bytes(self) -> bytes:
"""Get the bytes representation for this header"""
pass

Expand All @@ -74,3 +77,18 @@ def to_bytes(self) -> bytes:

selector = sig_hash.digest()[:4]
return selector

class EvmAdvance(BaseModel):
chain_id: UInt256
app_contract: Address
msg_sender: Address
block_number: UInt256
block_timestamp: UInt256
prev_randao: UInt256
input_index: UInt256
payload: Bytes

evm_advance_header = ABIFunctionSelectorHeader(
function=EvmAdvance.__name__,
argument_types=get_abi_types_from_model(EvmAdvance)
)
144 changes: 144 additions & 0 deletions cartesi/outputs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
from abc import ABC, abstractmethod
from os import environ
from logging import getLogger
from requests import post

import json
from typing import List
from .util import encode_values, encode_function_call

LOGGER = getLogger(__name__)

DEFAULT_ROLLUP_URL = 'http://127.0.0.1:5004'

def encode(d):
return "0x" + d.encode("utf-8").hex()

class Output(ABC):
"""
Base class representing a result generated by processing an input.

It is resposible for converting its payload to the format expected by
Cartesi rollups.

Parameters:
payload(str): the actual data generated after processing the input
"""

def __init__(self, payload: str, address: str = None):
self.endpoint = "/output"
if payload[:2] == "0x":
self.payload = payload
else:
self.payload = "0x" + payload

if address is None:
address = environ.get(
'ROLLUP_HTTP_SERVER_URL',
DEFAULT_ROLLUP_URL
)
self.address = address

@classmethod
def from_hex(cls, hex_value:str):
return cls(hex_value)

def create(self, rollup_server_address=None):
rollup_server = self.address
if rollup_server_address is not None:
rollup_server = rollup_server_address

return post(rollup_server + self.endpoint, json={"payload": self.payload})

class Voucher(Output):
"""
A Voucher is an `Output` representing a transaction
that can be carried out on the base layer blockchain,
such as a transfer of assets.

Parameters:
destination(str): destination of the contract who will execute the payload
payload(bytes): an ABI encoded contract function call
"""

def __init__(self, destination: str, payload: bytes|str, value = None, address: str = None):
self.endpoint = "/voucher"
self.destination = destination
self.value = value

if value is None:
self.value = '0x' + encode_values(["uint256"], [0]).hex()

if payload[:2] == "0x":
super().__init__(payload, address=address)
else:
hexpayload = "0x" + payload.hex()
super().__init__(hexpayload, address=address)

@classmethod
def from_hex(cls, destination:str, hex_value:str, value:int=None):
return cls(destination, hex_value, value)

@classmethod
def from_function_selector(cls, destination:str, selector:str, types:List[str], values:List[int]):
transfer_payload = encode_function_call(selector, types, values)
return cls(destination, transfer_payload)

def create(self, rollup_server_address=None):
rollup_server = self.address

if rollup_server_address is not None:
rollup_server = rollup_server_address

return post(rollup_server + "/voucher", json={"payload": self.payload, "destination": self.destination, "value": self.value})

class Notice(Output):
"""
A Notice is an `Output` representing an informational statement
that can be validated in the base layer blockchain.

Parameters:
payload(str): a string containing arbitrary data
"""

def __init__(self, payload: str):
super().__init__(payload)
self.endpoint = "/notice"

@classmethod
def from_json(cls, json_object):
if (isinstance(json_object, str)):
return cls.from_string(json_object)

j_string = json.dumps(json_object)
return cls.from_string(j_string)

@classmethod
def from_string(cls, string:str):
hex_encoded = encode(string)
return cls(hex_encoded)

class Report(Output):
"""
A Report is an `Output` representing an application log.

Parameters:
payload(str): a string containing arbitrary data
"""

def __init__(self, payload: str):
super().__init__(payload)
self.endpoint = "/report"

@classmethod
def from_json(cls, json_object):
if (isinstance(json_object, str)):
return cls.from_string(json_object)

j_string = json.dumps(json_object)
return cls.from_string(j_string)

@classmethod
def from_string(cls, string:str):
hex_encoded = encode(string)
return cls(hex_encoded)
Loading