hopper_sdk/lib.rs
1//! # Hopper SDK. Off-chain companion for the Hopper framework
2//!
3//! This crate is the **symmetric off-chain half** of Hopper. Where `hopper-core`,
4//! `hopper-runtime`, and `hopper-macros-proc` own the on-chain safety surface,
5//! `hopper-sdk` owns the off-chain consumer surface: indexers, explorers,
6//! wallets, back-ends, and clients.
7//!
8//! ## Why this exists
9//!
10//! Neither Pinocchio, Anchor zero-copy, nor Quasar ships a symmetric off-chain
11//! SDK that understands the framework's own wire shapes. Clients for those
12//! frameworks tend to re-implement borsh/IDL decoders from scratch and always
13//! lag on-chain semantics. Hopper closes that loop:
14//!
15//! - **Receipts are a first-class wire format** (64-byte fixed, documented in
16//! the program manifest). This crate parses them and narrates them.
17//! - **Layout fingerprints are mutual**. A client can verify the on-chain
18//! account header matches the layout_id it was compiled against before any
19//! decoding. No "surprise layout change" incidents.
20//! - **Segment-aware partial reads**. Because Hopper knows field offsets at the
21//! segment level, clients can load just the bytes they need. the same
22//! property the on-chain side uses to minimize CU cost.
23//! - **Manifest-driven builders**. Instructions and account lists come out of
24//! the `ProgramManifest` so the on-chain definition is the single source of
25//! truth.
26//!
27//! ## Module map
28//!
29//! - [`receipt`]. Decode the Hopper 64-byte receipt wire format and convert
30//! it into structured data or a human-readable narrative.
31//! - [`reader`]. Segment-aware partial account readers that only pull the
32//! fields the caller asked for. Rejects mismatched `layout_id`.
33//! - [`builder`]. Instruction and account-list builder driven by the
34//! `ProgramManifest`. Zero borsh dependency.
35//! - [`diff`]. Snapshot-to-snapshot diff producer symmetric with
36//! `hopper-core::diff`.
37//! - [`fingerprint`]. Runtime layout_id verification helpers.
38//!
39//! ## Relationship to `hopper-schema`
40//!
41//! This crate is a **consumer** of the `ProgramManifest` types defined in
42//! `hopper-schema`. It does not duplicate the schema. it operates over it.
43
44#![cfg_attr(not(feature = "std"), no_std)]
45#![deny(unsafe_op_in_unsafe_fn)]
46#![warn(missing_docs)]
47
48// `alloc` is always available. the SDK ships zero-copy primitives in the
49// hot path (`SegmentReader`, `DecodedReceipt::parse`), but the optional
50// narrative / diff / builder surfaces allocate for `String` and `Vec`. We
51// pull `alloc` in unconditionally so those modules compile cleanly in
52// `no_std + alloc` targets (the default deployment shape for indexers).
53extern crate alloc;
54
55pub mod diff;
56pub mod fingerprint;
57pub mod reader;
58pub mod receipt;
59
60#[cfg(feature = "builder")]
61pub mod builder;
62
63// Surface the most commonly used types at the crate root.
64pub use fingerprint::{FingerprintCheck, FingerprintError};
65pub use reader::{ReaderError, SegmentReader};
66pub use receipt::{DecodedReceipt, ReceiptError, ReceiptWire};
67
68#[cfg(feature = "narrate")]
69pub use receipt::narrative::{Narrator, ReceiptNarrative};
70
71/// SDK-level error surface. All sub-errors lift into this enum for easy
72/// `?`-propagation in consumer code.
73#[derive(Debug)]
74pub enum SdkError {
75 /// Receipt decode failure.
76 Receipt(ReceiptError),
77 /// Segment-aware reader failure.
78 Reader(ReaderError),
79 /// Layout fingerprint mismatch.
80 Fingerprint(FingerprintError),
81}
82
83impl core::fmt::Display for SdkError {
84 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
85 match self {
86 SdkError::Receipt(e) => write!(f, "receipt: {:?}", e),
87 SdkError::Reader(e) => write!(f, "reader: {:?}", e),
88 SdkError::Fingerprint(e) => write!(f, "fingerprint: {:?}", e),
89 }
90 }
91}
92
93#[cfg(feature = "std")]
94impl std::error::Error for SdkError {}
95
96impl From<ReceiptError> for SdkError {
97 fn from(e: ReceiptError) -> Self {
98 SdkError::Receipt(e)
99 }
100}
101impl From<ReaderError> for SdkError {
102 fn from(e: ReaderError) -> Self {
103 SdkError::Reader(e)
104 }
105}
106impl From<FingerprintError> for SdkError {
107 fn from(e: FingerprintError) -> Self {
108 SdkError::Fingerprint(e)
109 }
110}