Metadata-Version: 2.4
Name: rialo-py-cdk
Version: 0.8.0a0
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Rust
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Distributed Computing
Classifier: Typing :: Typed
Requires-Dist: typing-extensions
Requires-Dist: rialo-py-cdk[test,lint] ; extra == 'dev'
Requires-Dist: maturin==1.12.6 ; extra == 'dev'
Requires-Dist: black ; extra == 'lint'
Requires-Dist: isort ; extra == 'lint'
Requires-Dist: mypy ; extra == 'lint'
Requires-Dist: pytest==8.3.3 ; extra == 'test'
Requires-Dist: pytest-asyncio==0.25.0 ; extra == 'test'
Requires-Dist: pytest-cov==6.0.0 ; extra == 'test'
Requires-Dist: psutil==6.1.0 ; extra == 'test'
Provides-Extra: dev
Provides-Extra: lint
Provides-Extra: test
Summary: Python CDK for Rialo blockchain - wallet management, transactions, and RPC client
Keywords: blockchain,cryptocurrency,wallet,rialo,web3,crypto
Author-email: Subzero Labs <build@subzero.xyz>
License-Expression: Apache-2.0
Requires-Python: >=3.9
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Bug Tracker, https://github.com/SubzeroLabs/rialo/issues
Project-URL: Discord, https://discord.gg/RialoProtocol
Project-URL: changelog, https://github.com/SubzeroLabs/rialo/blob/main/CHANGELOG.md
Project-URL: documentation, https://docs.rialo.xyz
Project-URL: homepage, https://github.com/SubzeroLabs/rialo
Project-URL: repository, https://github.com/SubzeroLabs/rialo

# rialo-py-cdk

Python bindings for the Rialo CDK, generated with [PyO3](https://pyo3.rs) and built via
[Maturin](https://maturin.rs). The extension wraps `rialo-cdk` and exposes its full
client-side surface — keyring management, transaction building, JSON-RPC communication,
program deployment, and TEE secret encryption — as a synchronous Python API. All async
Rust operations are bridged internally through a shared Tokio runtime; callers do not
use `async`/`await`. Requires Python ≥ 3.9 (targets the Python Stable ABI `abi3-py39`,
so a single wheel per platform covers CPython 3.9 and later).

## Installation

```bash
pip install rialo-py-cdk
```

Pre-built wheels are published for Linux (x86\_64/aarch64), macOS (x86\_64/arm64), and
Windows. See [Building from source](#building-from-source) if no wheel is available for
your platform.

## Key public types

### Primitive types

| Python name | Purpose |
|---|---|
| `PublicKey` (`PyPublicKey`) | Ed25519 public key; construct from 32 bytes or base58 string |
| `Hash` (`PyHash`) | 32-byte hash (config hash prefix); construct from bytes or base58 string |
| `Signature` (`PySignature`) | Ed25519 signature (64 bytes); construct from bytes or base58 string |
| `AccountMeta` (`PyAccountMeta`) | Account reference in an instruction (`pubkey`, `is_signer`, `is_writable`) |
| `Instruction` (`PyInstruction`) | Custom instruction: program ID, accounts, and opaque data bytes |
| `RialoException` | Base exception for all CDK errors; carries `.code` and `.message` |

### Keyring management

| Python name | Purpose |
|---|---|
| `Keyring` | Named collection of Ed25519 keypairs; one is the active signing key |
| `DerivedKeypair` | Single keypair with index, public key, and optional HD derivation path |
| `InMemoryKeyringProvider` | Ephemeral keyring storage (testing; no persistence) |
| `FileKeyringProvider` | Encrypted on-disk keyring storage (`file-storage` feature) |

### Transaction and RPC

| Python name | Purpose |
|---|---|
| `TransactionBuilder` | Builds and optionally signs serialised transactions |
| `HttpRpcClient` | HTTP JSON-RPC client for Rialo nodes (`rpc-client` feature) |
| `RialoRPCClient` | Typed wrapper over `HttpRpcClient` with dataclass return types |
| `ProgramDeployment` | Deploys an eBPF or RISC-V program binary (`bincode` feature) |
| `ProgramInvocation` | Builds and submits a program-call instruction |
| `Config` | Lightweight network/RPC URL configuration helper |

> **Note:** `Wallet`, `InMemoryWalletProvider`, `FileWalletProvider`, and `Account` are
> retained as deprecated aliases; prefer the `Keyring`/`KeyringProvider` names in new code.

> **Warning:** Keypair bytes in the low-level API are always **64 bytes**: 32-byte
> Ed25519 secret key followed by 32-byte public key. Provider methods that accept a
> `secret_key` argument take the raw 32-byte secret only. Passing the wrong length raises
> `RialoException` at call time.

## Usage

### Create a keyring and send a transfer

```python
import time
from rialo_py_cdk import (
    InMemoryKeyringProvider,
    HttpRpcClient,
    TransactionBuilder,
    get_devnet_url,
    rlo_to_kelvin,
)

provider = InMemoryKeyringProvider()
keyring, mnemonic = provider.create_with_mnemonic("main", 128, "password")
print("Save mnemonic:", mnemonic)

client = HttpRpcClient(get_devnet_url())
config_hash = client.get_config_hash_prefix()   # required by every transaction

recipient = keyring.get_public_key()            # replace with the real recipient

valid_from = int(time.time() * 1000)
tx_bytes = (
    TransactionBuilder(keyring.get_public_key(), valid_from, config_hash)
    .add_transfer_instruction(keyring.get_public_key(), recipient, int(rlo_to_kelvin(1.0)))
    .sign_with_keyring(keyring)   # signs and serialises; use .build() for unsigned bytes
)
sig = client.send_transaction(tx_bytes)
print("Transaction signature:", sig)            # base58 string
```

### Recover a keyring from a mnemonic

```python
recovered = provider.recover_from_mnemonic("main", mnemonic, "password")
print("Recovered public key:", recovered.get_public_key())
```

### Manage multiple keypairs

Each keyring can hold several derived keypairs. Use `derive_keypair` to add more at the
next available BIP44 index, `set_active_keypair` to switch between them, and
`sign_with_keypair` to sign with a specific one without switching the active key.

```python
# Derive an additional keypair (index assigned automatically)
info = provider.derive_keypair("main", 1, "password")
print("New keypair pubkey:", info["pubkey_string"])

# Switch the active signing key
keyring.set_active_keypair(1)
print("Now signing with:", keyring.get_public_key_string())

# Sign with a specific keypair without changing the active key
sig_bytes = keyring.sign_with_keypair(message_bytes, 1)

# List all keypairs in the keyring
for kp in keyring.get_keypairs_info():
    print(f"  index={kp['index']}  pubkey={kp['pubkey_string']}")

# Import an existing 32-byte secret key at a custom derivation path
provider.import_secret_key("main", secret_key_bytes, "m/44'/756'/5'", "password")
```

### Deploy a program

```python
from rialo_py_cdk import ProgramDeployment, PyLoaderType

deployment = ProgramDeployment("/path/to/program.so")
deployment.with_loader_type(PyLoaderType.RiscV)   # default is LoaderV4
program_id = deployment.deploy_with_keyring(client, keyring)
print("Deployed program:", program_id)
```

### Invoke a program

```python
from rialo_py_cdk import ProgramInvocation

invocation = ProgramInvocation(program_id)
invocation.add_writable_account(state_account_pubkey)
invocation.add_readonly_account(config_account_pubkey)
invocation.with_data(instruction_data_bytes)

sig = invocation.invoke_with_keyring(client, keyring)
print("Invocation signature:", sig)

# Build an instruction without submitting, e.g. to include in a larger transaction
ix = invocation.into_instruction()
```

### Encrypt a secret for TEE execution

```python
from rialo_py_cdk import encrypt_secret, PublicKey

sk_pub = PublicKey.from_string(client.get_secret_sharing_pubkey())
ciphertext = encrypt_secret("Bearer sk-…", keyring.get_public_key(), sk_pub)
# Pass ciphertext in your instruction data; the TEE program decrypts it.
```

### Configure the network

`Config` lets you persist the active network URL without threading a `client` everywhere.
It defaults to localnet.

```python
from rialo_py_cdk import Config

cfg = Config("devnet")          # sets rpc_url automatically
print(cfg.rpc_url)              # https://api.devnet.rialo.xyz
cfg.set_network("testnet")      # switch network; rpc_url updates
print(cfg.network)              # "testnet" (read-only)

d = cfg.to_dict()
cfg2 = Config.from_dict(d)
```

### Subscription workflows (REX)

```python
subscription = client.get_subscription(subscriber_pubkey_str, nonce)
print("Topic:", subscription["topic"])

triggered = client.get_triggered_transactions(subscription_account_str, 10)
for tx in triggered:
    print(f"sig={tx['signature']}  block={tx['block_number']}")
```

### Workflow lineage

```python
lineage = client.get_workflow_lineage("5UfDuX...")
# lineage["nodes"] contains the DAG of related transactions.
```

## Typed RPC client

`rialo_py_cdk.generated` ships a fully typed `RialoRPCClient` that wraps the PyO3
`HttpRpcClient` with dataclass return types — useful when you want IDE autocompletion
and static analysis with mypy/pyright.

```python
from rialo_py_cdk import HttpRpcClient, get_devnet_url
from rialo_py_cdk.generated import RialoRPCClient
from rialo_py_cdk.generated.types import AccountInfo, EpochInfo

raw_client = HttpRpcClient(get_devnet_url())
client = RialoRPCClient(raw_client)

# Return types are dataclasses, not raw dicts
epoch: EpochInfo = client.get_epoch_info()
print(f"Epoch {epoch.epoch}, slot {epoch.absolute_slot}")

account: AccountInfo = client.get_account_info(pubkey_string)
print(f"Balance: {account.kelvin} kelvin, owner: {account.owner}")
```

> **Tip:** The underlying `HttpRpcClient` is still accessible via `client._client` when
> you need methods not yet promoted to the typed layer.

## Error handling

All CDK errors surface as `RialoException`. The `.code` attribute carries the numeric
error code for programmatic handling.

```python
from rialo_py_cdk import RialoException

try:
    sig = client.send_transaction(tx_bytes)
except RialoException as exc:
    if exc.code == 1001:                    # example: insufficient funds
        print("Funding required before sending")
    else:
        raise                               # re-raise unexpected errors
```

> **Tip:** `exc.message` contains a human-readable description with the error category
> and code, e.g. `[Transaction:1001] insufficient funds`.

### Error code ranges

| Range | Category |
|---|---|
| 1000–1999 | Transaction errors |
| 2000–2999 | RPC errors |
| 3000–3999 | Configuration errors |
| 4000–4999 | Keyring/wallet errors |
| 5000–5999 | Encryption errors |

## RPC reference

| Method | Returns | Notes |
|---|---|---|
| `get_config_hash_prefix()` | `int` | Required for every `TransactionBuilder` |
| `get_balance(pubkey)` | `int` | Balance in kelvin |
| `send_transaction(tx_bytes)` | `PySignature` | Submit signed bytes |
| `send_and_confirm_transaction(tx_bytes)` | `dict` | Submit and poll for confirmation |
| `confirm_transaction(sig_str)` | `dict` | Poll for an already-submitted transaction |
| `get_account_info(pubkey)` | `dict` | Raw account data |
| `get_multiple_accounts(pubkeys)` | `list` | Batch query |
| `get_accounts_by_owner(owner)` | `list` | Program accounts |
| `get_block_height()` | `int` | Finalized block height |
| `get_transaction(sig)` | `dict` | By signature |
| `get_epoch_info()` | `dict` | Current epoch |
| `get_signatures_for_address(address)` | `list` | Transaction history for an account |
| `request_airdrop(pubkey, kelvin)` | `PySignature` | Devnet/testnet only |
| `request_airdrop_and_confirm(pubkey, kelvin)` | `dict` | Airdrop + wait |
| `get_fee_for_message(msg_base64)` | `dict` | Estimate fee before sending |
| `get_minimum_balance_for_rent_exemption(size)` | `int` | Rent-exempt threshold |
| `get_secret_sharing_pubkey()` | `str` | TEE HPKE public key (base58) |
| `get_subscription(subscriber, nonce)` | `dict` | REX subscription lookup |
| `get_triggered_transactions(account, limit?)` | `list` | Subscription-triggered txs |
| `get_workflow_lineage(request)` | `dict` | Workflow DAG traversal |

> **Tip:** Use `RialoRPCClient` from `rialo_py_cdk.generated` to get typed dataclass
> return values instead of raw `dict`/`list` from `HttpRpcClient`.

## Low-level utilities

The module exposes a stateless, provider-free API for scripts and tools:

| Function | Purpose |
|---|---|
| `generate_keypair()` | Generate a random Ed25519 keypair (64 bytes) |
| `keypair_from_seed(seed)` | Derive a keypair from a 32-byte seed |
| `keypair_from_mnemonic(phrase, passphrase?)` | Derive a keypair from a BIP39 mnemonic |
| `generate_mnemonic()` | Generate a fresh BIP39 mnemonic phrase |
| `keypair_from_file(path)` | Load a Solana-compatible JSON keypair file |
| `public_key_from_keypair(keypair)` | Extract `PublicKey` from a 64-byte keypair |
| `sign(keypair, message)` | Sign a message; returns `Signature` |
| `verify(pubkey, message, sig)` | Verify an Ed25519 signature; returns `bool` |
| `encrypt_secret(secret, sender_pubkey, tee_pubkey)` | HPKE-encrypt a string secret for TEE programs |
| `transfer_instruction(from, to, kelvin)` | Build a system-program transfer `Instruction` |
| `rlo_to_kelvin(rlo)` | Convert RLO (float) to kelvin (int) |
| `kelvin_to_rlo(kelvin)` | Convert kelvin (int) to RLO (float) |
| `airdrop_rlo(client, pubkey, rlo_amount)` | Request airdrop in RLO units |

`airdrop_rlo` is a convenience wrapper that converts RLO to kelvin before calling
`request_airdrop`. Prefer it over calling `request_airdrop` with a manual conversion.

```python
from rialo_py_cdk import airdrop_rlo, HttpRpcClient, get_devnet_url

client = HttpRpcClient(get_devnet_url())
sig = airdrop_rlo(client, pubkey, 2.5)    # request 2.5 RLO; devnet/testnet only
print("Airdrop:", sig)
```

## Network helpers

```python
from rialo_py_cdk import (
    get_localnet_url,    # "http://127.0.0.1:4104"
    get_devnet_url,      # "https://api.devnet.rialo.xyz"
    get_testnet_url,     # "https://api.testnet.rialo.xyz"
    get_mainnet_url,     # "https://api.mainnet.rialo.xyz"
    get_rpc_url_for_network,
    list_available_networks,
)

url = get_rpc_url_for_network("devnet")
networks = list_available_networks()   # ["localnet", "devnet", "testnet", "mainnet"]
```

## Constants

```python
from rialo_py_cdk import KELVIN_PER_RLO, FILE_STORAGE_AVAILABLE

# Currency: 1 RLO = 1_000_000_000 kelvin
kelvin = int(1.5 * KELVIN_PER_RLO)     # 1_500_000_000

# True when the wheel was compiled with the file-storage feature
if FILE_STORAGE_AVAILABLE:
    from rialo_py_cdk import FileKeyringProvider
```

## Feature flags

All features are enabled by default in the shipped wheel. They can be toggled on the
underlying `rialo-cdk` dependency when building from source with
`default-features = false`.

| Feature | Enables |
|---|---|
| `file-storage` | `FileKeyringProvider` |
| `encryption` | AES-GCM keyring encryption at rest |
| `hd-wallet` | BIP32/SLIP-10 HD key derivation |
| `mnemonic` | BIP39 mnemonic generation and recovery |
| `rpc-client` | `HttpRpcClient` |
| `bincode` | `ProgramDeployment`, `ProgramInvocation`, `PyLoaderType` |

`FILE_STORAGE_AVAILABLE` is exposed as a module-level boolean constant reflecting
whether `file-storage` was compiled in.

## Building from source

```bash
cd developer-frameworks/cdk/rialo-py-cdk
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"     # triggers maturin via PEP 517; requires Rust stable
pytest tests/
```

For faster incremental Rust rebuilds use `maturin develop` (included in the `[dev]`
extras).

## Caveats

- **`config_hash_prefix`** — every `TransactionBuilder` requires a config hash prefix
  fetched live from `client.get_config_hash_prefix()`. There is no in-process example
  constant that live nodes will accept.

- **Currency units** — the smallest unit is *kelvin*; 1 RLO = `KELVIN_PER_RLO`
  (1 000 000 000) kelvin. All RPC amounts are in kelvin. Use `rlo_to_kelvin` and
  `kelvin_to_rlo` to convert.

- **Derivation path** — Rialo uses BIP44 coin type 756 (`m/44'/756'/{index}'`). Keys
  derived on other coin types are not compatible.

- **Synchronous API** — all calls block the calling thread. Wrap with
  `asyncio.get_event_loop().run_in_executor` or use a thread pool if you need to drive
  multiple operations concurrently from an async Python application.

- **Keypair bytes** — keypair bytes in the low-level API are 64 bytes (secret ++ public).
  Provider methods that accept `secret_key` take the raw 32-byte secret only. Passing
  the wrong length raises `RialoException`.

- **`wallet` module** — `Wallet`, `InMemoryWalletProvider`, `FileWalletProvider`, and
  `Account` are deprecated aliases retained for backward compatibility. Prefer
  `Keyring`/`KeyringProvider` names in new code.

## See also

- [`rialo-cdk`](../rialo-rs-cdk/README.md) — the underlying Rust crate; source of truth for types and error codes
- [`rialo-ts-cdk`](../rialo-ts-cdk/README.md) — TypeScript CDK with equivalent API surface
- `developer-frameworks/cdk/rialo-py-cdk/pyproject.toml` — Python package metadata and Maturin build configuration
- `docs/developer/` — contributor setup guide including Rust toolchain requirements

