# sui-sandbox

Python bindings for Sui Move package analysis, transaction replay, view function execution, and Move function fuzzing.

Built on [sui-sandbox](../../README.md) — runs the real Sui Move VM locally via PyO3. **All functions are standalone** — `pip install sui-sandbox` is all you need.

## Installation

### From PyPI

```bash
pip install sui-sandbox
```

### From source (requires Rust toolchain)

```bash
cd crates/sui-python
pip install maturin
maturin develop --release
```

## Quick Start

```python
import sui_sandbox

# Extract the full interface of the Sui framework
interface = sui_sandbox.extract_interface(package_id="0x2")
for mod_name, mod_data in interface["modules"].items():
    print(f"{mod_name}: {len(mod_data.get('functions', {}))} functions")

# Replay a historical transaction via Walrus (no API key needed)
result = sui_sandbox.replay(
    "At8M8D7QoW3HHXUBHHvrsdhko8hEDdLAeqkZBjNSKFk2",
    checkpoint=239615926,
)
print(f"Success: {result['local_success']}")

# Fuzz a Move function
report = sui_sandbox.fuzz_function("0x1", "u64", "max", iterations=50)
print(f"Successes: {report['outcomes']['successes']}")
```

## API Reference

#### `extract_interface(*, package_id=None, bytecode_dir=None, rpc_url="https://fullnode.mainnet.sui.io:443")`

Extract the complete interface JSON for a Move package — all modules, structs, functions, type parameters, abilities, and fields.

Provide either `package_id` (fetched via GraphQL) or `bytecode_dir` (local directory containing `bytecode_modules/*.mv` files), but not both.

**Returns:** `dict` with full interface tree.

```python
interface = sui_sandbox.extract_interface(package_id="0x1")
for mod_name, mod_data in interface["modules"].items():
    print(f"{mod_name}: {len(mod_data.get('functions', {}))} functions")
```

#### `get_latest_checkpoint()`

Get the latest archived checkpoint number from Walrus.

**Returns:** `int`

```python
cp = sui_sandbox.get_latest_checkpoint()
print(f"Latest checkpoint: {cp}")
```

#### `get_checkpoint(checkpoint)`

Fetch a checkpoint from Walrus and return a summary.

**Returns:** `dict` with `checkpoint`, `epoch`, `timestamp_ms`, `transaction_count`, `transactions` (list), and `object_versions_count`.

```python
data = sui_sandbox.get_checkpoint(239615926)
for tx in data["transactions"]:
    print(f"  {tx['digest']}: {tx['commands']} commands, {tx['input_objects']} inputs")
```

#### `fetch_package_bytecodes(package_id, *, resolve_deps=True)`

Fetch package bytecodes via GraphQL, optionally resolving transitive dependencies.

**Returns:** `dict` with `packages` (map of package ID to list of base64-encoded module bytecodes) and `count`.

```python
pkgs = sui_sandbox.fetch_package_bytecodes("0x2", resolve_deps=True)
print(f"Fetched {pkgs['count']} packages")
```

#### `json_to_bcs(type_str, object_json, package_bytecodes)`

Convert a Sui object JSON representation to BCS bytes using Move type layout.

**Returns:** `bytes`

#### `call_view_function(package_id, module, function, *, type_args=None, object_inputs=None, pure_inputs=None, child_objects=None, package_bytecodes=None, fetch_deps=True)`

Execute a Move function in the local VM with full control over object and pure inputs.

**Returns:** `dict` with `success`, `error`, `return_values`, `return_type_tags`, `gas_used`.

#### `fuzz_function(package_id, module, function, *, iterations=100, seed=None, sender="0x0", gas_budget=50_000_000_000, type_args=[], fail_fast=False, max_vector_len=32, dry_run=False, fetch_deps=True)`

Fuzz a Move function with randomly generated inputs.

Use `dry_run=True` to check parameter classification without executing.

**Returns:** `dict` with `target`, `classification`, `outcomes` (successes/errors), `gas_profile`.

```python
# Dry run — check if function is fuzzable
info = sui_sandbox.fuzz_function("0x1", "u64", "max", dry_run=True)
print(f"Fuzzable: {info['classification']['is_fully_fuzzable']}")

# Full fuzz run
report = sui_sandbox.fuzz_function("0x1", "u64", "max", iterations=50, seed=42)
print(f"Successes: {report['outcomes']['successes']}")
```

#### `replay(digest, *, rpc_url=..., source="hybrid", checkpoint=None, allow_fallback=True, prefetch_depth=3, prefetch_limit=200, auto_system_objects=True, no_prefetch=False, compare=False, analyze_only=False, verbose=False)`

Replay a historical Sui transaction locally with the Move VM.

When `checkpoint` is provided, uses Walrus as the data source (no API key needed). Otherwise uses gRPC/hybrid (requires `SUI_GRPC_API_KEY` env var).

Use `analyze_only=True` to inspect state hydration without executing the transaction.

Use `compare=True` to compare local execution results with on-chain effects.

**Returns:** `dict` — replay results (with `local_success`, `effects`, `execution_path`, optionally `comparison`) or analysis summary (with `commands`, `inputs`, `objects`, `packages`, `input_summary`).

```python
# Analyze state hydration only (no VM execution)
analysis = sui_sandbox.replay(
    "At8M8D7QoW3HHXUBHHvrsdhko8hEDdLAeqkZBjNSKFk2",
    checkpoint=239615926,
    analyze_only=True,
)
print(f"Commands: {analysis['commands']}, Objects: {analysis['objects']}")

# Full replay via Walrus (no API key needed)
result = sui_sandbox.replay(
    "At8M8D7QoW3HHXUBHHvrsdhko8hEDdLAeqkZBjNSKFk2",
    checkpoint=239615926,
)
print(f"Success: {result['local_success']}")

# Full replay via gRPC with comparison
import os
os.environ["SUI_GRPC_API_KEY"] = "your-key"
result = sui_sandbox.replay("DigestHere...", compare=True)
if result.get("comparison"):
    print(f"Status match: {result['comparison']['status_match']}")
```

## Platform Support

Pre-built wheels are available for:
- Linux x86_64 (glibc 2.17+)
- Linux aarch64 (glibc 2.17+)
- macOS x86_64 (10.12+)
- macOS aarch64 (11.0+)

Building from source requires Rust 1.80+ and Python 3.9+.

## License

Apache 2.0
