Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
---
id: getting-started
title: Getting started
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

This page shows you how to add the CMA library to a Cartesi application and build a small wallet that accepts Ether deposits and answers balance queries.

## Start from a template

The fastest way to start is with the official [application templates](https://github.com/Mugen-Builders/libcma-app-templates). The repository contains ready to build projects for Python, Rust and C++. Each one already wires the library, the build files and the portal addresses together.

```shell
git clone https://github.com/Mugen-Builders/libcma-app-templates
```

Copy the folder for your language into your workspace and use it as the base of your application.

## Add the library to an existing application

You can also add CMA to an application you already have.

<Tabs groupId="language">
<TabItem value="python" label="Python" default>

Install `pycma` from the prebuilt RISC-V wheels:

```shell
pip3 install pycma --find-links https://prototyp3-dev.github.io/pip-wheels-riscv/wheels/
```

Or build it from the repository:

```shell
pip3 install pycma@git+https://github.com/Mugen-Builders/libcma-binding-python
```

The package is compiled for the RISC-V target, so run the install step inside the Dockerfile that builds your application's machine image.

</TabItem>
<TabItem value="rust" label="Rust">

Add the binding as a git dependency in your `Cargo.toml`:

```toml
[dependencies]
libcma_binding_rust = { git = "https://github.com/Mugen-Builders/libcma_binding_rust", branch = "main" }
```

</TabItem>
<TabItem value="cpp" label="C++">

Download a prebuilt release from the [releases page](https://github.com/Mugen-Builders/machine-asset-tools/releases). Releases contain runtime and development artifacts for musl and glibc systems. In a Dockerfile:

```dockerfile
ARG MACHINE_ASSET_TOOLS_VERSION
ADD https://github.com/Mugen-Builders/machine-asset-tools/releases/download/v${MACHINE_ASSET_TOOLS_VERSION}/machine-asset-tools_glibc_riscv64_v${MACHINE_ASSET_TOOLS_VERSION}.tar.gz /tmp/
RUN tar -xzf /tmp/machine-asset-tools_glibc_riscv64_v${MACHINE_ASSET_TOOLS_VERSION}.tar.gz -C / \
&& rm /tmp/machine-asset-tools_glibc_riscv64_v${MACHINE_ASSET_TOOLS_VERSION}.tar.gz
```

This installs the shared library. Use the `_dev` artifact when you also need the headers and the static library.

</TabItem>
</Tabs>

## Your first wallet

The example below accepts Ether deposits, records them in the ledger and answers balance queries. It is a trimmed version of the [wallet sample application](https://github.com/Mugen-Builders/libcma-binding-python/tree/main/sample_apps/wallet_app) that ships with the Python binding.

<Tabs groupId="language">
<TabItem value="python" label="Python" default>

```python
from pycma import RollupCma, Ledger, decode_ether_deposit, decode_inspect

ETHER_PORTAL_ADDRESS = "0xA632c5c05812c6a6149B7af5C56117d1D2603828"[2:].lower()

rollup = RollupCma()
ledger = Ledger()

# Create the asset entry for Ether once, at startup
ether = ledger.retrieve_asset(base_token=True)
ETHER_ID = ether["asset_id"]

def handle_advance(rollup, ledger):
advance = rollup.read_advance_state()
msg_sender = advance["msg_sender"].hex().lower()

if msg_sender == ETHER_PORTAL_ADDRESS:
deposit = decode_ether_deposit(advance)
account = ledger.retrieve_account(account=deposit["sender"])
ledger.deposit(ETHER_ID, account["account_id"], deposit["amount"])
return True

return False

def handle_inspect(rollup, ledger):
inspect = rollup.read_inspect_state()
query = decode_inspect(inspect)

if query["type"] == "BALANCE":
account = ledger.retrieve_account(account=query["account"])
balance = ledger.balance(ETHER_ID, account["account_id"])
rollup.emit_report(balance.to_bytes(32, "big"))
return True

return False

handlers = {"advance": handle_advance, "inspect": handle_inspect}

accept = True
while True:
next_request_type = rollup.finish(accept)
accept = handlers[next_request_type](rollup, ledger)
```

</TabItem>
<TabItem value="rust" label="Rust">

The Rust template uses [libcmt-binding-rust](https://github.com/Mugen-Builders/libcmt-binding-rust) to read inputs from the machine and the CMA parser to decode them:

```rust
use libcma_binding_rust::parser::{cma_decode_advance, CmaParserInputType, CmaParserInputData};
use libcma_binding_rust::Ledger;
use json::object;

const ETHER_PORTAL: &str = "0xA632c5c05812c6a6149B7af5C56117d1D2603828";

fn handle_ether_deposit(
ledger: &mut Ledger,
msg_sender: &str,
payload_hex: &str,
) -> Result<(), Box<dyn std::error::Error>> {
// The parser reads the payload from data.payload
// and the sender from data.metadata.msg_sender
let request = object! {
data: {
metadata: { msg_sender: msg_sender },
payload: payload_hex
}
};

let decoded = cma_decode_advance(
CmaParserInputType::CmaParserInputTypeEtherDeposit,
request,
)?;

if let CmaParserInputData::EtherDeposit(deposit) = decoded.input {
let ether_id = ledger.retrieve_ether_assets()?;
let account_id = ledger.retrieve_account_via_address(deposit.sender)?;
ledger.deposit(ether_id, account_id, deposit.amount)?;
}
Ok(())
}
```

Compare the sender of each advance request against the portal addresses to pick the right input type, as shown in the [Rust template](https://github.com/Mugen-Builders/libcma-app-templates/blob/main/rust/src/main.rs).

</TabItem>
<TabItem value="cpp" label="C++">

In C++ you read inputs with libcmt and pass them straight to the parser:

```cpp
extern "C" {
#include <libcmt/rollup.h>
#include <libcma/parser.h>
#include <libcma/ledger.h>
}

// input is a cmt_rollup_advance_t filled by cmt_rollup_read_advance_state(rollup, &input)
cma_parser_input_t parser_input;

const int err = cma_parser_decode_advance(CMA_PARSER_INPUT_TYPE_ETHER_DEPOSIT, &input, &parser_input);
if (err < 0) {
printf("unable to decode deposit: %d - %s\n", -err, cma_parser_get_last_error_message());
}

// parser_input.ether_deposit.sender -> who deposited
// parser_input.ether_deposit.amount -> how much
```

The [C++ template](https://github.com/Mugen-Builders/libcma-app-templates/blob/main/cpp/app.cpp) shows the complete request loop.

</TabItem>
</Tabs>

## Build and run it

Applications using the CMA library build and run like any other Cartesi application. Follow [Building an application](../../development/building-an-application.md) to produce the machine image, then [Running an application](../../development/running-an-application.md) to start a local node. Use [Sending inputs and assets](../../development/send-inputs-and-assets.md) to make a deposit and watch your wallet handle it.

## Next steps

- [Parsing inputs](./parsing-inputs.md) covers every input the parser can decode.
- [Managing balances](./managing-balances.md) covers the full ledger API.
- [Vouchers and withdrawals](./vouchers.md) shows how users take their assets back to the base layer.
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
---
id: ledger-reference
title: Ledger reference
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

This page lists every ledger operation with its signature per language. For a guided walk through, see [Managing balances](./managing-balances.md).

## Lifecycle

Create, persist, reset and release a ledger.

<Tabs groupId="language">
<TabItem value="python" label="Python" default>

| Method | Description |
| :--- | :--- |
| `Ledger()` | Creates an in memory ledger |
| `Ledger(memory_filename, offset, mem_length, n_accounts, n_assets, n_balances, initialize_memory)` | Creates or opens a file backed ledger. Pass `initialize_memory=True` only the first time, to create and size the file |
| `reset()` | Clears all accounts, assets and balances |

</TabItem>
<TabItem value="rust" label="Rust">

| Method | Returns | Description |
| :--- | :--- | :--- |
| `Ledger::new()` | `Result<Ledger, LedgerError>` | Creates an in memory ledger |
| `Ledger::init_from_file(config)` | `Result<Ledger, LedgerError>` | Opens or creates a file backed ledger from a `LedgerFileConfig` |
| `Ledger::init_from_buffer(config)` | `Result<Ledger, LedgerError>` | Creates a ledger backed by a caller provided buffer from a `LedgerBufferConfig` |
| `reset()` | `Result<(), LedgerError>` | Clears all records |
| `get_last_error_message()` | `Result<String, LedgerError>` | Returns the text of the last error |

</TabItem>
<TabItem value="cpp" label="C++">

| Function | Description |
| :--- | :--- |
| `cma_ledger_init(ledger)` | Creates an in memory ledger |
| `cma_ledger_init_file(ledger, memory_file_name, mode, offset, mem_length, n_accounts, n_assets, n_balances)` | Opens (`CMA_LEDGER_OPEN_ONLY`) or creates (`CMA_LEDGER_CREATE_ONLY`) a file backed ledger |
| `cma_ledger_init_buffer(ledger, buffer, mem_length, n_accounts, n_assets, n_balances)` | Creates a ledger backed by a caller provided buffer |
| `cma_ledger_reset(ledger)` | Clears all records |
| `cma_ledger_fini(ledger)` | Releases the ledger |
| `cma_ledger_get_last_error_message()` | Returns the text of the last error |

</TabItem>
</Tabs>

## Retrieving accounts and assets

These calls find an entry and return its internal ID. With the find or create operation they also create missing entries, which is the common case when handling deposits.

<Tabs groupId="language">
<TabItem value="python" label="Python" default>

| Method | Description |
| :--- | :--- |
| `retrieve_account(account_id=None, account=None)` | Finds or creates an account. `account` is a hex string: a 20 byte wallet address or a 32 byte account ID. Returns a dict with `account_id` and `account` |
| `retrieve_asset(asset_id=None, token=None, token_id=None, token_id_with_amount=None, base_token=None, force_find=None)` | Finds or creates an asset. Use `base_token=True` for Ether, `token` for ERC20, `token` plus `token_id` for ERC721, and add `token_id_with_amount=True` for ERC1155. `force_find=True` fails instead of creating. Returns a dict with `asset_id`, `token`, `token_id` and `total_supply` |

</TabItem>
<TabItem value="rust" label="Rust">

| Method | Returns | Description |
| :--- | :--- | :--- |
| `retrieve_account_via_address(address)` | `Result<LedgerAccountId, LedgerError>` | Finds or creates an account from a wallet address |
| `retrieve_account(account_id, account_type, operation, addr_or_id)` | `Result<LedgerAccountId, LedgerError>` | Generic account retrieval with explicit `AccountType` and `RetrieveOperation` |
| `retrieve_ether_assets()` | `Result<LedgerAssetId, LedgerError>` | Finds or creates the Ether asset |
| `retrieve_erc20_asset_via_address(token_address)` | `Result<LedgerAssetId, LedgerError>` | Finds or creates an ERC20 asset from its contract address |
| `retrieve_erc721_assets_via_address(token_address, token_id)` | `Result<LedgerAssetId, LedgerError>` | Finds or creates an ERC721 asset from its contract address and token ID |
| `retrieve_asset(asset_id, token_address, token_id, asset_type, operation)` | `Result<LedgerAssetId, LedgerError>` | Generic asset retrieval with explicit `AssetType` and `RetrieveOperation` |

</TabItem>
<TabItem value="cpp" label="C++">

| Function | Description |
| :--- | :--- |
| `cma_ledger_retrieve_account(ledger, account_id, account, addr_accid, n_balances, account_type, operation)` | Finds or creates an account. When `account_id` is set the call fills the account details, otherwise it fills the ID |
| `cma_ledger_retrieve_asset(ledger, asset_id, token_address, token_id, out_total_supply, asset_type, operation)` | Finds or creates an asset. Also returns the asset's total supply through `out_total_supply` |

</TabItem>
</Tabs>

## State changes

Each operation either completes fully or fails with an error, for example when funds are insufficient.

<Tabs groupId="language">
<TabItem value="python" label="Python" default>

| Method | Description |
| :--- | :--- |
| `deposit(asset_id, account_id, amount)` | Adds `amount` of the asset to the account and raises the total supply |
| `withdraw(asset_id, account_id, amount)` | Removes `amount` from the account and lowers the total supply |
| `transfer(asset_id, from_account_id, to_account_id, amount)` | Moves `amount` between two accounts |

</TabItem>
<TabItem value="rust" label="Rust">

| Method | Returns | Description |
| :--- | :--- | :--- |
| `deposit(asset_id, to_account_id, amount)` | `Result<(), LedgerError>` | Adds `amount` (a `U256`) to the account |
| `withdraw(asset_id, from_account_id, amount)` | `Result<(), LedgerError>` | Removes `amount` from the account |
| `transfer(asset_id, from_account_id, to_account_id, amount)` | `Result<(), LedgerError>` | Moves `amount` between two accounts |

</TabItem>
<TabItem value="cpp" label="C++">

| Function | Description |
| :--- | :--- |
| `cma_ledger_deposit(ledger, asset_id, to_account_id, amount)` | Adds the amount to the account |
| `cma_ledger_withdraw(ledger, asset_id, from_account_id, amount)` | Removes the amount from the account |
| `cma_ledger_transfer(ledger, asset_id, from_account_id, to_account_id, amount)` | Moves the amount between two accounts |

</TabItem>
</Tabs>

## Queries

Read only calls. They never change the ledger, so they are safe to use in inspect handlers.

<Tabs groupId="language">
<TabItem value="python" label="Python" default>

| Method | Description |
| :--- | :--- |
| `balance(asset_id, account_id)` | Returns the account's balance of the asset as an int |
| `supply(asset_id)` | Returns the total supply of the asset as an int |

</TabItem>
<TabItem value="rust" label="Rust">

| Method | Returns | Description |
| :--- | :--- | :--- |
| `get_balance(asset_id, account_id)` | `Result<U256, LedgerError>` | Returns the account's balance of the asset |
| `get_total_supply(asset_id)` | `Result<U256, LedgerError>` | Returns the total supply of the asset |

</TabItem>
<TabItem value="cpp" label="C++">

| Function | Description |
| :--- | :--- |
| `cma_ledger_get_balance(ledger, asset_id, account_id, out_balance, account_balance_info)` | Fills `out_balance` with the account's balance of the asset |
| `cma_ledger_retrieve_asset(...)` | Returns the asset's total supply through its `out_total_supply` parameter |

</TabItem>
</Tabs>

## Errors

Every operation reports failures with a typed error: an exception in Python, a `LedgerError` in Rust, and a negative code in C and C++. The full list, including insufficient funds, missing accounts and storage limits, is in [Types and selectors](./types-and-selectors.md#error-codes).
Loading
Loading