1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
//! Decode, reconstruct, replay, and mutate Solana transactions.
//!
//! Point svmscope at a mainnet signature and it (1) **decodes** the transaction —
//! the full cross-program invocation tree with every instruction named from its
//! on-chain Anchor IDL or known native layout, balance and token changes, and
//! compute units per program; (2) **replays** it locally in an embedded SVM
//! ([LiteSVM](https://docs.rs/litesvm)), re-executing the real program binaries
//! against the transaction's reconstructed state; and (3) **mutates** — change an
//! account's lamports or data, warp the clock, flip feature gates, then replay
//! again to ask *"what if this had been different?"*.
//!
//! # Installation
//!
//! svmscope is a testing tool, so add it as a dev-dependency:
//!
//! ```text
//! cargo add --dev svmscope
//! ```
//!
//! or in `Cargo.toml`:
//!
//! ```toml
//! [dev-dependencies]
//! svmscope = "0.2"
//! ```
//!
//! # Quickstart
//!
//! Two nouns: a [`Scope`] fetches and caches, a [`Replay`] is one transaction's
//! reconstructed world. All RPC happens in [`Scope::replay`]; every run after
//! that is local and free.
//!
//! ```no_run
//! use svmscope::{Mutation, Scope};
//!
//! let scope = Scope::new("https://api.mainnet-beta.solana.com");
//! let sig = "your transaction signature";
//!
//! // 1. Decode: CPI tree, named instructions, balance/token diffs, CU per program.
//! let analysis = scope.analyze(sig)?;
//! println!("fee: {} lamports, {} top-level instructions",
//! analysis.overview.fee, analysis.cpi_tree.len());
//!
//! // 2. Replay the real programs locally in LiteSVM.
//! let mut replay = scope.replay(sig)?;
//! println!("replay success: {}", replay.run()?.result.success);
//!
//! // 3. What-if: zero out an account, jump 30 days ahead, and replay again —
//! // zero further RPC.
//! replay.advance_seconds(30 * 86_400);
//! let what_if = replay.simulate(&[Mutation::lamports("SomeAccount111...", 0)])?;
//! println!("mutated replay success: {}", what_if.result.success);
//! # Ok::<(), svmscope::Error>(())
//! ```
//!
//! # Building and submitting transactions
//!
//! You don't have to start from an existing signature. Against a local
//! validator, [`Scope::program_with_idl`] gives an IDL-driven builder: pick a
//! method, supply accounts and JSON arguments, and
//! [`MethodBuilder::send_and_capture`] signs, submits, waits for the
//! transaction to land, and returns a [`CapturedTransaction`] whose replay
//! holds the exact pre-transaction world — ready for mutation and time travel
//! with no further RPC.
//!
//! ```no_run
//! # use serde_json::json;
//! # use std::str::FromStr;
//! # let scope = svmscope::Scope::new("http://127.0.0.1:8899");
//! # let program_id = solana_address::Address::from_str("11111111111111111111111111111111")?;
//! # let idl = json!({});
//! # let payer = solana_keypair::Keypair::new();
//! # let state = program_id;
//! let captured = scope
//! .program_with_idl(program_id, idl)
//! .method("setValue")?
//! .payer(&payer)
//! .account("state", state)
//! .args(json!({ "value": 42 }))?
//! .send_and_capture()?;
//! println!("landed: {}", captured.signature);
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! # Hermetic testing
//!
//! Replays against live RPC state drift as the chain moves on. To pin a
//! transaction's world forever, [`Scope::capture`] snapshots the transaction,
//! every account it touched, and every program ELF into a [`Fixture`];
//! [`Replay::from_fixture`] then rebuilds the world and runs what-if scenario
//! suites (mutations + expectations + named-field assertions) against that
//! frozen state — offline, deterministic, CI-friendly.
//!
//! # Public API
//!
//! Consumer-facing types are re-exported from the crate root. Prefer imports
//! such as `svmscope::{Scope, Replay, Mutation, Check, Fixture}`; implementation
//! modules are private. The [`idl`], [`report`], and [`spec`] modules expose the
//! specialized IDL, HTML-report, and JSON-suite APIs.
//!
//! # Errors
//!
//! A **reverting replay is not an error** — it comes back as a successful
//! observation with `result.success == false`. [`Error`] always means svmscope
//! itself couldn't do what was asked (RPC failure, unknown transaction, a
//! mutation targeting an account that isn't loaded, an unknown field name…).
//!
//! # Feature flags
//!
//! - `server` — builds the HTTP API server binary (axum); library consumers
//! don't need it and it is off by default.
//!
//! This same library powers the svmscope CLI, the HTTP API, and the hosted UI
//! at <https://svmscope.vercel.app> — identical results in all four.
pub
pub
pub use ;
pub use ;
pub use CuUsage;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use CapturedTransaction;
/// Compile-checks every Rust example in the README as part of `cargo test`.
;
/// Resolve a cluster name or explicit RPC URL to an endpoint. Precedence:
/// explicit `rpc` URL > `cluster` name > `default`.
///
/// Clusters: `mainnet`, `devnet`, `testnet`, `localnet` (127.0.0.1:8899). A value
/// starting with `http` in either field is used verbatim; an unknown cluster
/// name is an error.