# PaperProof SDK for Rust

Rust SDK for the PaperProof protocol on Sui. This release focuses on typed
configuration, input validation, transaction-plan construction, high-level service
APIs, event parsing, canonical event filtering, gRPC-first reads, deployment
verification, robust retry helpers and lightweight Walrus content helpers.

It is designed to mirror the public surface of `@paperproof/sdk-ts` while giving
Rust applications a conservative integration point: builders return neutral
`TransactionPlan` values that can be adapted to the official Sui Rust SDK, a CLI
pipeline, a backend signer, or a custom transaction service.

The high-level SDK constructor follows the TypeScript SDK transport policy:
gRPC is the default object/read transport, and GraphQL is the default event/history
query transport. The official Sui Rust SDK path does not support JSON-RPC. This
crate keeps a small `reqwest` JSON-RPC compatibility adapter only for explicit
short-term historical backfills and migration paths.

## Install

```toml
[dependencies]
paperproof-sdk-rs = { git = "https://github.com/PaperProofLabs/paperproof-sdk-rs" }
```

## License and Identity

This SDK is licensed under Apache-2.0 to encourage broad integration,
experimentation, commercial use, audits, forks, indexers, services, and tooling
around PaperProof and Sui. The Apache-2.0 license applies to this SDK code. It
does not grant rights to PaperProof trademarks, official status, official
deployment authority, protocol governance authority, protected PaperProof
contract source, protected official app source, or protected PaperProof
documentation and brand materials.

PaperProof Protocol refers to the open protocol layer and official deployed
protocol instances. PaperProof Labs refers to the originating team and
maintainer of the official interface, SDKs, reference indexer, documentation,
and brand identity.

You may use, fork, modify, and redistribute this SDK under Apache-2.0. If you
publish a fork, wrapper, or integration, make its modified or unofficial status
clear and do not imply endorsement by PaperProof Labs unless separately
authorized.

After the crate is published to crates.io:

```toml
[dependencies]
paperproof-sdk-rs = "0.1"
```

Optional native Sui RPC integration:

```toml
[dependencies]
paperproof-sdk-rs = { version = "0.1", features = ["sui-native"] }
```

## Quick Start

```rust
use paperproof_sdk_rs::{PaperProofClient, types::{CommonContentInput, PreprintInput}};

fn main() -> paperproof_sdk_rs::Result<()> {
    let client = PaperProofClient::mainnet();
    let reservation_id = "0x1234";
    let plan = client.publishing.finalize_reserved_preprint(reservation_id, &PreprintInput {
        title: "Example preprint".into(),
        abstract_text: "A minimal PaperProof Rust SDK example.".into(),
        authors: vec!["PaperProof Labs".into()],
        keywords: vec!["example".into()],
        field: "computer science".into(),
        license: "CC-BY-4.0".into(),
        page_count: 1,
        content: CommonContentInput {
            content_hash: "sha256:example".into(),
            walrus_blob_id: "example-blob".into(),
            walrus_blob_object_id: "0x6".into(),
            content_type: "application/pdf".into(),
        },
        series_metadata: vec![],
        version_metadata: vec![],
        payment_coin_id: None,
    })?;
    println!("{plan:#?}");
    Ok(())
}
```

High-level gRPC-first SDK:

```rust
use paperproof_sdk_rs::{PaperProofSdk, PaperProofTransport};

fn main() -> paperproof_sdk_rs::Result<()> {
    let sdk = PaperProofSdk::mainnet()?;
    assert_eq!(sdk.transport, PaperProofTransport::Grpc);
    Ok(())
}
```

Deprecated JSON-RPC compatibility fallback:

```rust
use paperproof_sdk_rs::{CreatePaperProofSdkOptions, PaperProofTransport, create_paperproof_sdk};

let sdk = create_paperproof_sdk(CreatePaperProofSdkOptions {
    transport: Some(PaperProofTransport::JsonRpc),
    ..Default::default()
})?;
# Ok::<(), paperproof_sdk_rs::PaperProofError>(())
```

This fallback is not backed by the official Sui Rust SDK and should not be used
as the default transport for new services.

## Capabilities

- Deployment constants for the current PaperProof mainnet deployment.
- Deployment drift checking against a remote deployment manifest.
- Protocol constants for artifact types, statuses, fee levels and governance actions.
- Strong local validation for addresses, object ids, metadata, content fields,
  comments and proposal inputs.
- Transaction builders for publishing, versioning, comments, likes, governance
  voting and operational controls.
- Typed `TransactionPlan`, `MoveCall` and `MoveArgument` structures for build-only
  workflows and batch/PTB preparation.
- A `SuiCliExecutor` adapter that can preview, dry-run, dev-inspect or execute a
  `TransactionPlan` through the official Sui CLI. This gives Rust users real
  signing and mainnet write support while keeping the core plan format stable.
- Deprecated JSON-RPC compatibility helper for object reads and event queries
  during migration/backfill work. It is a direct HTTP adapter, not official
  `sui-rust-sdk` JSON-RPC support.
- Full `PaperProofReadClient` for canonical objects, series, versions, comments,
  likes, governance proposals, dynamic fields, balances and coin pages.
- `PaperProofQueryClient` for paginated event queries, canonical event filtering,
  raw/canonical/verified trust reports, all-page collection helpers,
  GraphQL-first historical queries, governance history helpers and typed event
  extraction.
- `PaperProofWatchClient` for polling event watchers with cursors, dedupe, and
  high-level publishing/comment/governance/status/owner event helpers.
- Typed view structs for common PaperProof on-chain objects.
- `PaperProofService` for script-friendly high-level operations backed by the
  Sui CLI executor.
- Provider traits for custom data and execution backends:
  `PaperProofDataProvider`, `PaperProofExecutionProvider` and `PaperProofProvider`.
- Feature-gated native Sui adapter scaffolding based on `sui-rpc` and
  `sui-sdk-types`; `create_paperproof_sdk` defaults to this gRPC transport when
  the `sui-native` feature is enabled and wires GraphQL for event queries. CLI
  execution remains the conservative fallback for write flows.
- Deployment verification that checks canonical object/package bindings before
  indexers or services trust a configuration.
- Checkpoint ingestion abstractions for high-throughput indexers:
  `CheckpointDataProvider`, `CheckpointScanOptions`, persistent cursor traits,
  `EventId` idempotency keys and canonical-by-default filtering.
- Indexer sinks and schemas: `PaperProofEventSink`, `JsonlEventSink`,
  feature-gated `SqliteEventSink` / `PostgresEventSink`, feature-gated
  `SqliteCursorStore` / `PostgresCursorStore`, `POSTGRES_SCHEMA_SQL`,
  `SQLITE_SCHEMA_SQL`, plus SQL parameter helpers.
- Domain reducer output via `PaperProofDomainChange` for production-oriented
  upsert/update pipelines.
- Deployment drift hard-fail policy for long-running services.
- Optional `tracing` metrics for indexer scan batches.
- Robust retry helpers and Move abort explanations for friendlier failure logs.
- Event parser and trust filter so indexers can reject events from fake packages
  or wrong canonical objects. Verified query helpers can also read referenced
  PaperProof objects and surface `incomplete` instead of letting apps mistake
  provider/read failures for empty protocol state.
- Walrus HTTP helper for blob reads, writes and SHA-256 verification.
- Coin amount helpers and coin selection utilities for PPRF/SUI/WAL workflows.

See [docs/API.md](docs/API.md) for the API map.
See [docs/ENTERPRISE_DEPLOYMENT.md](docs/ENTERPRISE_DEPLOYMENT.md) for Docker,
systemd and Kubernetes deployment examples.

## Local Validation

```powershell
cargo fmt --check
cargo test
cargo clippy --all-targets --all-features -- -D warnings
cargo doc --no-deps
```

The default tests are local/offline and do not require private keys or mainnet
funds.

Optional mainnet read-only verification:

```powershell
$env:PAPERPROOF_RS_MAINNET_READ='1'
cargo test --test integration_mainnet
cargo run --example verify_deployment
cargo run --example mainnet_read
cargo run --example check_deployment_update
cargo run --example indexer_backfill
```

Indexer deployment examples:

```powershell
docker build -t paperproof-sdk-rs:local .
cargo run --example indexer_sql_sinks
```

Enable database-backed sinks and cursor stores explicitly:

```powershell
$env:PAPERPROOF_RS_SINK='sqlite'
$env:PAPERPROOF_RS_SQLITE_PATH='examples/artifacts/indexer/paperproof-indexer.sqlite'
cargo run --features sqlite --example indexer_backfill
cargo run --features sqlite --example indexer_tail

$env:PAPERPROOF_RS_SINK='postgres'
$env:PAPERPROOF_RS_POSTGRES_URL='postgres://user:pass@localhost/paperproof'
cargo run --features postgres --example indexer_backfill
cargo run --features postgres --example indexer_tail
```

Enable checkpoint ingestion with concurrent workers, resume, rate limiting and
retry metrics:

```powershell
$env:PAPERPROOF_RS_CHECKPOINT='1'
$env:PAPERPROOF_RS_CHECKPOINT_COUNT='500'
$env:PAPERPROOF_RS_CHECKPOINT_BATCH='25'
$env:PAPERPROOF_RS_CHECKPOINT_WORKERS='8'
$env:PAPERPROOF_RS_CHECKPOINTS_PER_SECOND='50'
cargo run --features sui-native,sqlite,tracing --example indexer_backfill
```

Checkpoint mode persists `StreamId::checkpoint()` through the selected cursor
store and prints processed events, rejected events, checkpoint lag, DB write
latency, retry count and duplicate skips.

Production operators can start from:

- `Dockerfile`
- `deploy/systemd/paperproof-indexer-tail.service`
- `deploy/systemd/paperproof-indexer-backfill.service`
- `deploy/kubernetes/paperproof-indexer.yaml`
- `docs/ENTERPRISE_DEPLOYMENT.md`

Local fuzzy builder exercise:

```powershell
cargo run --example fuzzy_build
```

## Sui Execution and Mainnet Writes

The SDK exposes two layers:

- `TransactionPlan` for build-only and custom PTB workflows.
- `SuiCliExecutor` for real signing and execution through `sui client ptb`.
- `PaperProofExecutionProvider` for native, CLI, remote signer or custom backend
  integrations.

Example:

```rust
use paperproof_sdk_rs::{CliExecutionOptions, ExecutionMode, PaperProofClient, SuiCliExecutor};

let client = PaperProofClient::mainnet();
let executor = SuiCliExecutor::mainnet();
let plan = client.ops.set_paused(false);
let output = executor.run(&plan, &CliExecutionOptions {
    mode: ExecutionMode::DryRun,
    sender: Some("0x...".into()),
    gas_budget: Some(30_000_000),
    ..Default::default()
})?;
println!("{:?}", output.digest);
# Ok::<(), paperproof_sdk_rs::PaperProofError>(())
```

The executor supports:

- `ExecutionMode::Preview`
- `ExecutionMode::DryRun`
- `ExecutionMode::DevInspect`
- `ExecutionMode::Execute`

Real writes use the active Sui CLI keystore. Make sure `sui client active-env`
is `mainnet` and `sui client active-address` or `CliExecutionOptions.sender`
is the intended signer.

The `mainnet_write_smoke` example is opt-in:

```powershell
$env:PAPERPROOF_RS_MAINNET_WRITE='1'
$env:PAPERPROOF_RS_SENDER='0x...'
cargo run --example mainnet_write_smoke
```

The command above dry-runs by default. Add `-- --execute` to send a real mainnet
transaction:

```powershell
$env:PAPERPROOF_RS_MAINNET_WRITE='1'
$env:PAPERPROOF_RS_SENDER='0x...'
cargo run --example mainnet_write_smoke -- --execute
```

This double guard is deliberate: examples and tests should never write to
mainnet by accident. To only comment on an existing tree, set
`PAPERPROOF_RS_TREE_ID`; otherwise the example builds a preprint publish
transaction.

## Query API

`PaperProofQueryClient` is the event/indexer-friendly read surface.

```rust
use paperproof_sdk_rs::{EventQueryInput, PaginationInput, PaperProofQueryClient};

# async fn run() -> paperproof_sdk_rs::Result<()> {
let query = PaperProofQueryClient::mainnet();
let page = query.query_canonical_events(EventQueryInput {
    package_id: Some(query.deployment.packages.publishing.clone()),
    module: Some("publishing".into()),
    pagination: PaginationInput {
        limit: Some(10),
        descending_order: Some(true),
        ..Default::default()
    },
    ..Default::default()
}).await?;
println!("events={}", page.data.len());
# Ok(())
# }
```

For stronger trust boundaries, query with an explicit trust level. `raw` only
parses provider output, `canonical` checks package/root and event shape, and
`verified` also reads referenced PaperProof objects such as series, versions,
comment trees, likes books and governance proposals.

Use `canonical` for ordinary display feeds. Use `verified` for statistics,
governance history, rewards, airdrop snapshots, and trusted indexer state. On
those paths, call `require_verified_page(page)` or `assert_no_incomplete(&page)`
before deriving business state.

```rust
use paperproof_sdk_rs::{EventTrustLevel, TrustedEventQueryInput};

# async fn run() -> paperproof_sdk_rs::Result<()> {
let query = PaperProofQueryClient::mainnet();
let page = query.query_trusted_events(TrustedEventQueryInput {
    query: EventQueryInput {
        move_event_type: Some(format!(
            "{}::publishing::ArtifactPublishedEvent",
            query.deployment.packages.publishing
        )),
        ..Default::default()
    },
    trust: EventTrustLevel::Verified,
    include_rejected: true,
    verify_walrus: false,
}).await?;

for report in &page.verification {
    println!("{:?} {:?}", report.status, report.issues);
}
let page = paperproof_sdk_rs::require_verified_page(page)?;
# Ok(())
# }
```

For long-running indexers, prefer the dedicated indexer helpers:

```rust
use paperproof_sdk_rs::{PaperProofIndexerClient, PaperProofIndexerState};

# async fn run() -> paperproof_sdk_rs::Result<()> {
let indexer = PaperProofIndexerClient::mainnet();
let filters = PaperProofIndexerClient::canonical_module_filters(&indexer.query.deployment);
let batch = indexer.scan_module_once(filters[0].clone(), None, Some(100)).await?;

let mut state = PaperProofIndexerState::default();
state.apply_batch(&batch);
println!(
    "accepted={} rejected={}",
    batch.progress.accepted_events,
    batch.progress.rejected_events
);
# Ok(())
# }
```

Indexer guidance:

- Treat official package ids plus canonical root/registry ids as the trust boundary.
- Prefer `CheckpointDataProvider` and `scan_checkpoint_range_once` for production
  gRPC/checkpoint ingestion; keep JSON-RPC event queries for compatibility
  backfill only.
- Persist `StoredIndexerCursor` with an implementation of `IndexerCursorStore`.
  The SDK ships `SqliteCursorStore` behind the `sqlite` feature and
  `PostgresCursorStore` behind the `postgres` feature.
- Use `EventId::key()` as the default idempotency key for database upserts.
- Use `PaperProofEventSink` to write accepted/rejected batches. The SDK ships a
  JSONL sink, `SqliteEventSink`, `PostgresEventSink`, and SQL schemas with
  primary-key idempotency. Production users can still implement the trait with
  their own connection pool and upsert strategy.
- Map `PaperProofDomainChange` into your application schema instead of relying on
  raw events alone.
- Enable `features = ["tracing"]` to emit batch metrics through `tracing`.
- Keep `IndexerProgress.cursor` per module stream so services can resume safely.
- Persist rejected events and reasons; they help diagnose fake events and integration mistakes.
- Use `PaperProofIndexerState` as a reference reducer, not as a replacement for a durable database.

Sui event filters have protocol-level combination limits. The SDK returns a
clear `EventParse` error when a user combines incompatible filters such as
`sender` with package/module/event-type filters.

For new indexers, prefer checkpoint/subscription ingestion through the native
Sui gRPC stack or a custom provider. Use JSON-RPC only as an explicitly chosen,
deprecated backfill compatibility path.

## Watch API

`PaperProofWatchClient` is a lightweight polling layer over `PaperProofQueryClient`. It gives Rust bots, services, and
simple indexers a protocol-aware way to poll events without hand-building Move event type strings.

```rust
use paperproof_sdk_rs::{PaperProofWatchClient, WatchOptions};

# async fn run() -> paperproof_sdk_rs::Result<()> {
let watch = PaperProofWatchClient::mainnet();
let mut watcher = watch.watch_comment_added_events(WatchOptions {
    limit: Some(20),
    ..Default::default()
});

let page = watcher.next().await?;
for event in page.data {
    println!("{} {:?}", event.transaction_digest.unwrap_or_default(), event.parsed_json);
}
# Ok(())
# }
```

Watchers retain their cursor and deduplicate by transaction digest plus event sequence. High-level helpers cover
publishing, add-version, comments, likes, status changes, owner transfers, and governance lifecycle events. Governance
watchers query both the current and original governance packages, so upgraded deployments do not look like they have no
historical votes.

Use `watch_verified_events` when the consumer must avoid accepting events whose
object bindings cannot be confirmed. The returned `TrustedEventPage` includes
`verification`, `rejected`, and `incomplete` reports.

## Deployment Drift Checks

Use deployment drift checks when running a website, indexer or long-lived service
that might outlive an SDK release.

```rust
use paperproof_sdk_rs::{check_deployment_update_from_url, format_deployment_update_check};

# async fn run() {
let result = check_deployment_update_from_url(None, None).await;
println!("{}", format_deployment_update_check(&result));
# }
```

If the manifest differs from the compiled deployment constants, update the SDK or
pass the latest `Deployment` override explicitly.

Services can enforce this at startup:

```rust
use paperproof_sdk_rs::{DeploymentDriftPolicy, enforce_deployment_update_policy};

# async fn run() -> paperproof_sdk_rs::Result<()> {
let check = paperproof_sdk_rs::check_deployment_update_from_url(None, None).await;
enforce_deployment_update_policy(&check, DeploymentDriftPolicy::HardFailOnAnyProblem)?;
# Ok(())
# }
```

Benchmarks:

```powershell
cargo bench --bench indexer
```

## Deployment Verification

```rust
use paperproof_sdk_rs::{PaperProofReadClient, verify_deployment};

# async fn run() -> paperproof_sdk_rs::Result<()> {
let read = PaperProofReadClient::mainnet();
let verification = verify_deployment(&read).await?;
assert!(verification.ok);
# Ok(())
# }
```

This checks that the configured root, type registry, fee manager, governance
vault and governance config are readable, come from expected packages, and point
back to the canonical PaperProof deployment objects.

## Robust Retry And Diagnostics

Use `robust_execute_plan` for transient CLI/RPC failures and
`abort_explainer::explain_paperproof_error` for user-facing Move abort messages.
The SDK classifies common failures such as insufficient balance, wrong object
types, duplicate votes, duplicate likes, paused publishing and fake object
bindings.

Provider-based robust helpers also expose normalized results:

- `normalize_provider_execution_output`
- `robust_execute_plan_normalized`
- `RobustProviderExecuteOptions { expect_failure, rebuild_retry, retry }`

`expect_failure` is useful for negative integration tests. `rebuild_retry`
classifies common object-version and transaction-rebuild failures so callers can
rebuild a fresh transaction plan before retrying.

## Walrus

There is no official Rust Walrus SDK at the time this crate was created. The
crate supports HTTP aggregator/publisher endpoints and a local `walrus` CLI
backend. Mainnet writes require a funded/authenticated publisher or a local
Walrus CLI configured with your wallet; do not assume a free unauthenticated
mainnet publisher exists. The helper always exposes digest checks so callers can
verify content before binding it to PaperProof metadata.

For application code, prefer `PaperProofContentService`. It gives PaperProof
users a single content lifecycle API: publish bytes, read and verify bytes,
extend storage, and transfer owned blob objects. That makes the PaperProof SDK a
practical Walrus simplification layer instead of forcing users to learn a
separate toolchain before they can publish.

```rust
use paperproof_sdk_rs::{
    ContentPublishOptions, PaperProofContentService, WalrusClient,
};

let walrus = WalrusClient::new(
    "https://aggregator.walrus-mainnet.walrus.space",
    Some(std::env::var("PAPERPROOF_WALRUS_PUBLISHER_URL")?),
);
let content = PaperProofContentService::new(walrus);
let published = content
    .publish_content(b"paperproof content".to_vec(), ContentPublishOptions::default())
    .await?;
```

For signed mainnet writes without an HTTP publisher, use a local Walrus CLI
configured with your wallet:

```rust
use paperproof_sdk_rs::{PaperProofContentService, WalrusCliClient};

let content = PaperProofContentService::new(WalrusCliClient::new("walrus"));
```

Native Rust read/verify and owned/shared preflight checks are implemented in
this crate. Fully native Walrus write/certify is intentionally tracked as a
separate backend milestone because it requires the full Walrus encoding,
storage-node upload, certificate, and Sui register/certify flow. The
`PaperProofContentService` API is stable so that backend can be swapped in
without changing downstream application code.

## Security Notes

- Treat `MAINNET_DEPLOYMENT` as the trusted entry point for package and canonical
  object ids.
- For indexers, filter by official package id and canonical object ids. Event
  names alone are not sufficient.
- Local validation is a developer-experience and safety layer. The Move
  contracts remain the source of truth.
- Do not commit private keys, `.env` files, or signer material into applications
  using this SDK.

## Release Checklist

Before publishing:

```powershell
.\scripts\publish-check.ps1
```

The script runs:

- `cargo fmt --check`
- `cargo test`
- `cargo test --all-features`
- `cargo clippy --all-targets --all-features -- -D warnings`
- `cargo doc --no-deps --all-features`
- `cargo package --allow-dirty`

For crates.io publication:

```powershell
cargo login
cargo publish --dry-run
cargo publish
```

Keep mainnet write examples opt-in and never commit signer material.
