Skip to main content

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}